機雷がなんだ! 全速前進!

SEというかプログラマというか、日々のエンジニア生活の中で体験したことなどを中心に書き残しています。

AWS Lambda で実行の冪等性を考慮しておくことの重要性

背景

AWS Lambda を使うと様々な処理を簡単かつサーバレスに実現することができます。お手軽に使える一方でいくつか考慮しておかないと実運用時に痛い目を見ることも沢山あります。いくつかある考慮ポイントの中で、実際にハマった冪等性の観点について少しまとめておこうと思います。

 

発生したこと

S3にオブジェクトをPutしたのをトリガとして S3 Object Lambda でオブジェクトの種類ごとに様々な処理を行っていました。当初は冪等性を考慮した作りになっていなかったのですが、多くないオブジェクト数(10~100)でテストした限りでは特段問題なく動作していたので本番環境へデプロイしました。実際の本番環境では、膨大な数のオブジェクト数(数百万~数億)を処理することになるのですが、次のような問題が発生してしまいした。

 

<問題点> 

  • 1.なぜかLambdaが複数回発火する場合がある
    • 対象データ処理済の場合は、後で発火したLambdaの実行がエラーになる
      • 前に発火したLambdaが正常終了でも最終ステータスがエラーになる
    • ほぼ同時だと複数処理がデータを取り合って待ちやデータ破損が発生する
  • 2.無駄なLambdaのリトライが発生(初期設定では2回)してコストが嵩む
    • データ起因なので、何度リトライしても確実に同じエラーが発生する
    • リトライ対象のオブジェクト(画像)が巨大な場合は、ハイスペックで高単価なLambda(OOMが発生しないようスペックアップしてある)が複数回実行されることでコストが嵩む
    • リトライ対象のオブジェクトが動画の場合は、後続の変換処理で高単価なAWS Elemental MediaConvertを使用していたためコストが嵩む

 

分析と対策

  • その1

今回はS3にオブジェクトをPutしたのをトリガとして S3 Object Lambda を発火させていますが、AWS Lambdaでは発火イベントが意図せず複数回発生してしまうことがあります。これは「At Least Once(最低一回)」という仕様なので、これを前提にする必要があります。

alt text

【AWS Black Belt Online Seminar】Serverless モニタリング 【P.48】から抜粋

 

AWS Lambdaのベストプラクティス「Lambda 関数を冪等にする | AWS re:Post」では、次のように記載されています。

DynamoDB など、スケーリングが容易でスループットが高いサービスを使用してセッションデータを保存します。

このベストプラクティスに従ってDynamoDBを用いてLambdaの冪等性を確保するよう対策しました。具体的な実装のサンプルのソースコードはGithubに置いておきます。

github.com

こちらDynamoDBの操作【新規登録:put_item()、更新:put_item() 】の条件の記載方法が若干ややこしいので詳細は公式ドキュメントを参照してください。

 

  • その2

無駄なLambdaのリトライについては単純にリトライしないようLambdaの「非同期呼び出し」の設定で「再試行(関数がエラーを返すときに再試行する最大回数)」を 0 に設定するだけで対策できます。

 

結果

以上の対策によりLambdaが複数回発火した場合でも、DynamoDBに登録したステータスのレコードをチェックすることで、直ちにLambdaを終了させることができるようになり、無事にAWS Lambdaの冪等性を確保することができました。これにより、前述の問題点をすべて解消することができました。

 

おわりに

前述したとおりAWS Lambdaでは発火イベントが意図せず複数回発生してしまう「At Least Once(最低一回)」という仕様なので、Lambdaが複数回発火すること自体を防ぐことはできません。そのため、Lambdaが複数回発火することを前提に冪等性を確保するよう実装する必要があります。また、Lambdaのリトライ回数や実行時間などの設定についても用途に合わせて適切にチューニングすることも併せて重要です。個々の処理は微々たるコストでも、今回のように取り扱うオブジェクト数が膨大になるとチリツモで、とんでもないコストを請求されることになりますので、くれぐれもご注意ください。(と、自戒の念を込めて…🙏)

 

参考

日の出・日の入を計算するWebアプリをMercuryで作成して公開してみた

今回も小技ネタですが、一応サンプルWebアプリ公開までやったのでブログに書いておこうと思います。

背景

以前から国立天文台公開データを手動でExcelに取り込んで可視化するということをやっていました。(※下図のイメージ:日の出&日の入の時刻の可視化.xlsx

手動で作成して運用していたExcelファイル

年に1回のことなので、自動化するまでもないと思っていたのですが、それすら面倒くさくなってしまったので Jupyter NotebookGoogle Colab)で自動化することにしました。

課題

Jupyter NotebookGoogle Colab)を使えば可視化まで実現できそうではありますが、次のような懸念点がありました。

  • 国立天文台公開データと比較して精度が大きく劣化する可能性がある
  • 作成したノートブックを いつでもどこでもだれでも 簡単に実行できない

結論

今回は、以下のソリューションを使って作成したノートブックをWebアプリとして公開することで前述の懸念点を解決しました。

公開したWebアプリのURLは次のとおりです。

https://daytime.runmercury.com/app/sample

Webアプリの画面イメージ
続きを読む

Amazon S3 ファイルのMD5ハッシュ値を効率的に計算するLambda

ちょっとした小技です。検索しても情報があまり無かったので一応書き留めておくことにします。

背景

S3にアップロードしたファイルが破損していないことを確認するため、アップロードしたファイルのMD5ハッシュ値を計算してチェックする仕組みが必要でした。S3に配置したタイミングで発火する S3 Object Lambda を用いて実現しようとしたところ、巨大なファイルをうまく処理できないという課題に遭遇しました。

課題

S3には大小さまざまなファイル(最低 0 バイトから最大 5 TB)を配置することができるため、場合によっては超巨大なファイルが処理対処となることもあり得ます。また、AWS Lambda は割り当てるメモリ量(128 MB から 10,240 MB までの任意の量のメモリを 1 MB 単位で関数に割り当て可能)によって単価が高くなるため、コストの観点からも不用意に高めることもできません。

やったこと

以下のように処理方法を変えました。

  • Before
 # S3から一気に全てダウンロードして計算する方式(メモリ不足の懸念あり)
s3_object2 = s3c.get_object(Bucket=src_bucket_name, Key=cpy_object_name)
all_body = s3_object2["Body"].read()
md5_hash_value2 = hashlib.md5(all_body).hexdigest().upper()
  • After
# S3から少しずつダウンロードしながら計算する方式(使用済みデータは捨てる)
s3_object1 = s3c.get_object(Bucket=src_bucket_name, Key=cpy_object_name)
md5 = hashlib.md5()
for chunk in s3_object1["Body"].iter_chunks(chunk_size=10240):
    md5.update(chunk)
md5_hash_value1 = md5.hexdigest().upper()

コピペで動作するサンプルコードとパフォーマンス比較計測結果をGithubにPushしておいたので実際に試してみたい人は以下をご覧ください。

github.com

まとめ

S3に格納されたオブジェクトには巨大なものもあり得るため、特別な理由がない限りは、基本的には【After】のようにMD5ハッシュ値の計算を実装すべきと考えられます。

 

Amazon S3で高速にクロスリージョン間ファイルコピーをする方法

背景

現在はマネージドサービスであるS3 クロスリージョンレプリケーション (CRR) があるのであまりニーズがないかもしれませんが、とある事情でクロスリージョン(東京R→大阪R)間でサイズの大きいS3オブジェクトをコピーし、かつ、コピー先のオブジェクトに既存のオブジェクトのメタデータを引き継ぐだけでなく、新しいユーザー定義のメタデータ(x-amz-meta-xxxx)を追加付与しなくてはいけない要件がありました。

やったこと

S3にオブジェクトをPutしたのをトリガとして S3 Object Lambda から

  • boto3.client('s3').copy_object()
  • boto3.resource('s3').meta.client.copy()

などを使ってリージョン間ファイルコピーを試してみました。しかし、ファイルサイズ制限に引っかかったり、転送速度に難があったり、メタデータを引き継げなかったりと、いずれもやりたい要件をうまく満たすことができませんでした。なるべく独自実装したくなかったのですが、やむを得ず

  • boto3.client('s3').upload_part_copy()

を用いて、マルチスレッドかつマルチパートな通信を実装することで、今回の要件を満たすことができました。処理のサンプルのソースコードはGithubに置いておきます。

github.com

達成したこと

前述した方式は、僅かな差ですが最も速くデータを転送できました。(下図)

alt text

また、コピー元オブジェクトのタグやメタデータを引き継ぎ、新しいユーザー定義のメタデータ(x-amz-meta-xxxx)を追加付与することもできました。

参考

XP祭り2023登壇メモ:日本初のスクラム本「アジャイルソフトウェア開発スクラム」から20年の節目に翻訳者にいろいろ聞く

今更ながら、やりっぱなしでとっ散らかってままになっていた昨年のXP祭り2023の資料や情報を少しまとめて記録しておくことにします。

XP祭り2023について

プロポーザル

XP祭り2023 - 日本初のスクラム本「アジャイルソフトウェア開発スクラム」から20年の節目に翻訳者にいろいろ聞く | ConfEngine - Conference Platform

当日の資料

speakerdeck.com

当日の動画

日本初のスクラム本「アジャイルソフトウェア開発スクラム」から20年の節目に翻訳者にいろいろ聞く - Fumihiro Sunada / akon highfive - YouTube

※動画は公開期限が切れたら見れなくなってしまうかも…

ブログなど

その他

おわりに

ふりかえってみれば準備も覚束ないまま当日を迎えてしまい、下手くそファシリで akon さんの良いところや深い話をうまく引き出し切れなかったな、と反省しつつも、個人的には念願叶って一緒に登壇できてとても楽しかったのでまた挑戦したいです。ご協力&ご参加ありがとうございました🙇(打ち上げ🍶も最高でした😀)

九頭龍蕎麦 本店(飯田橋)

AWS Sorry Page定番パターン整理(ソーリーページ/メンテナンスページ)

はじめに

Webシステムを利用していて「メンテナンス中」や「アクセスが集中しています」などと表示された経験はないでしょうか?あまり頻繁に目にするものでは(というより目にしたく)ありませんが、このようにサービスが一時的に利用できない場合に表示するページを一般的にソーリーページ(SorryPage)と呼ぶことが多いと思います。ソーリーページがないと無骨でそっけないエラー画面が表示されることになるので利用者に現在の状況が伝わらず、サービスへの印象も悪くなってしまいます。

しかし、計画されたメンテナンスと突発的な障害の両方を一緒くたにソーリーページと呼ぶことには個人的には違和感があります。そこで、ここでは次のように区別して呼ぶことにします。

  1. 突発的な障害:ソーリーページ(SorryPage)
  2. 計画的な停止:メンテナンスページ(MaintenancePage)

記事の目的

AWS環境でソーリーページ/メンテナンスページを表示する定番の方式について調査したところ、まとまった情報がなかなか見つからず苦労したので、再度同じ苦労をしなくて済むよう、調査した内容を今後のためナレッジとしてまとめておくことが目的です。

注意点

本記事でまとめた以外にも様々な実現方式が存在すると思います。また、本内容は現時点(2024年2月時点)の情報に基づいた内容になっている点についても併せてご承知おきください。

実現方式

以下に定番の実現方式を示します。

1.突発的な障害:ソーリーページ(SorryPage)

以下の方式は、突発的な障害が発生した際、マネージドサービスのフェイルオーバー機能を利用して、自動的にセカンダリであるバックアップサイトのソーリーページに遷移させます。

続きを読む

*[本]「ソフトウェアアーキテクチャメトリクス」: アーキテクチャ品質を改善する10のアドバイス

昨年の夏頃に @snoozer05 さんこと島田さんがX(旧Twitter)でソフトウェアアーキテクチャに関する翻訳書籍の原稿レビュアーを募集しているのを偶然お見かけしました。ここ最近ソフトウェアアーキテクチャに興味があって、いろいろと書籍を読み漁っていたこともあり、これは良い機会だと直感してすぐにエントリーしました。その後、こちらのレビューに参加させていただけることになりました。

ちなみに原著はこちらの書籍(Software Architecture Metrics)です。

そして、今年の2024年1月24日についに翻訳書が発売されました👏👏👏

www.oreilly.co.jp

以上の経緯から、今回やったこと、感じたことなどを書き留めておくことにします。

期間と実績

約1ヶ月強という比較的短い期間の平日の仕事終わりの時間と週末の空き時間を使って翻訳レビューの作業をしました。〆切のレビュー期限までに書籍全体100%を網羅することができず申し訳なかったです。最終レビュー実績は6割超(188/308ページ)でした。大変お粗末様でした🙇 

作業の内容

いろいろやり方を考えたのですが、今回はGoogle翻訳とDeepLに適切な長さの原著の英文をそれぞれ入力して翻訳文を比較したり、ツール翻訳文を書籍の翻訳案と見比べて分かりにくい点がないかなどをチェックしました。翻訳ツールへの入力の工夫としては、入力する英文をいくつかのパターンに区切って翻訳させて、より確からしい意味を模索するなどしました。気付いた点は、作業用GithubリポジトリにIssueチケットとして起票して翻訳者へお伝えしました。チケット粒度は特に明確なルールはなかったのですが、私は章や節などある程度の単位ことにチケットを起票して、分かりにくいと思った理由と英文を箇条書きで翻訳文と併記しました。また、より分かりやすいと思える翻訳文のアイデアを思いついた箇所は、翻訳文案も併記してお伝えするようにしました。

感じたこと

今回の作業で感じたことは、昨今ではGoogle翻訳やDeepLなどの優れた翻訳ツールがあって本当に助かるなぁ、ということでした。とはいえ、自分が理解するためだけなら、翻訳文の日本語が多少変でも良いと思うのですが、書籍としてできるだけ分かりやすく、なるべく日本語として不自然でなく、原著の意図を汲み取りながら、数百ページの長文を翻訳するのは、やはり大変な労力が必要だなと改めて感じました。況や今日のような便利な翻訳ツールがなかった頃、大量の翻訳をやられていた @a_konno さん達のような先達の苦労は如何ばかりだったかと思うと本当に頭が上がりません。

さいごに

一昨年の2022年には 自分が寄稿した書籍を技術書典13で自ら販売するという機会 を得ましたが、商用本の翻訳レビューのお手伝いは初めての経験でした。実際にやってみると便利な翻訳ツールが整っている今日でも、思った以上に大変な作業でしたが、前述したように先達の苦労を少しだけ窺い知れたことや、技術書の内容を先んじて読んで理解を深めることができたという点において貴重な機会でした。そして謝辞に名前を入れていただけたのが嬉しかったです。ご献本いただきありがとうございました🙇