とりあえず、これで一件落着なのですが、ブロックローカル変数を使うと、yieldするだけに比べて遅くなってしまう、という問題が生じます。というのも、ブロックローカル変数は Procオブジェクトを受け取るのですが、この Procオブジェクトの生成が重いためです。なぜ遅いかを大雑把に言うと、関連するローカル変数領域などをメソッドフレームをたどって芋ずる的にヒープに確保しなければならないためです(この理由をより深く理解するには、Rubyのしくみ Ruby Under a Microscopeなどをご参照ください)。
ところで、$SAFEってそもそもご存じですかね? 知らない方もいらっしゃるかと思いますが、これからも知らないでもあまり困らないのではないでしょうか。外部からの入力を用いて systemメソッドなどで外部コマンドを呼ぶ、といった危険な機能を検知するかどうかを決めるための機能ですが、現在ではあまり利用するということは聞きません(危険なことができないようにするには、もっと OS レベルのセキュリティ機能を使うことを検討してください)。
クックパッドに入って、Ruby のテスト環境を新たに整備しました([ruby-core:81043] rapid CI service for MRI trunk)。いわゆる普通のテストを定期的に行う CI は rubyciというものがあるのですが、結果が出るまで時間がかかる、通知がないなど不満がありました。そこで、最短で2分で結果が出る環境を整備することにしました。計算機はクラウド環境では無く、実機を利用しています。私が主催した東京Ruby会議11の予備費を用いて購入したマシンと、ある企業様から Ruby Association へ寄贈頂いたマシン、それからクックパッドで確保できたマシンを利用しています。マシン調達・運用にお世話になった/なっている皆様に深く感謝いたします。
今回は、AWS の S3 から Google の GCS のように、クラウドサービスをまたいだバックアップのことをマルチクラウドバックアップと呼ぶことにします。そして、それを実現するためのイベント駆動 Lambda function を中心としたサーバーレスアプリケーションを、AWS SAM のもとで構築するためのノウハウを解説していきます。
At it's core, SAM is a powerful open source specification built on AWS CloudFormation that makes it easy to keep your serverless infrastructure as code –and they have the cutest mascot.
source-s3-bucket-notification SNS topic を subscribe する s3-multicloud-backup function が起動する。
s3-multicloud-backup function は、SNS 経由で届いた通知を開封し、対象のオブジェクトを source-s3-bucket から取り出して dest-bucket にアップロードする。
ただし、s3-multicloud-backup function によるオブジェクトのダウンロードとアップロードは、何らかの理由で失敗することがあり、堅牢なバックアップシステムを構築するためには失敗した事実を適切にハンドリングする必要があります。ここで起こりうる失敗の原因としては、以下の 3 点が上げられます。
replicateObject()がバックアップ処理の本体で、GCS にアクセスするための暗号化されたクレデンシャル (JSON) を環境変数 ENCRYPTED_GC_CREDENTIALから読み出し、KMS API を呼ぶことで復号します。そして、S3 バケットから対象のオブジェクトをダウンロードし、復号したクレデンシャルを用いて GCS バケットにアップロードします。クレデンシャルの暗号化や、この function につけるべき IAM role については後述します。
SAM では、基本的に Resourcesに各種リソースを記述していくことになります。Resourcesのキーは各リソースの論理名で、別の場所で !Refや !GetAttでリソースの ARN や名前を取得することが可能です。
ここでは、リトライを通知する s3-multicloud-backup-retry SNS topic および DLQ である s3-multicloud-backup-dlq SQS queue のみを SAM テンプレートに定義します。Lambda function にアタッチする IAM role や、S3 バケットからの通知を受ける source-s3-bucket-notification SNS topic は SAM テンプレートの中に定義しません。
以下のスクリーンショットのように、Deployment provider に AWS CloudFormation を指定し、Action mode を Create or replace a change set を指定し、Role name には事前に用意しておいた CloudFormation 用のサービスロールを指定します。
以下に SAM テンプレートの例を示します。今回はシンプルに Lambda function が 1 コしかないため、テンプレートの内容もシンプルです。SLACK_から始まる各環境変数は、お手持ちの環境に合わせて適宜書き換えてください。
AWSTemplateFormatVersion:'2010-09-09'Transform:'AWS::Serverless-2016-10-31'Description:'A serverless application to notify whether it succeeded or not.'Resources:CodePipelineNotify:Type:'AWS::Serverless::Function'Properties:CodeUri:'../../main.zip'Handler:'main'Runtime:'go1.x'FunctionName:'codepipeline-notify'MemorySize:128Timeout:8Events:CodeCommitStateChanged:Type:'CloudWatchEvent'Properties:Pattern:source:- 'aws.codepipeline'detail-type:- 'CodePipeline Pipeline Execution State Change'Environment:Variables:SLACK_WEBHOOK_URL:'Slack の incoming webhook URL をここに入れる'SLACK_CHANNEL:'#serverless'SLACK_EMOJI_ICON:':samtaro1:'
aws-sam-local を用いてローカルで function を動かす
今回は以下のような簡単な Makefile を用意したので、make コマンドを使ってビルドし aws-sam-local コマンドを使ってローカルで function を動かすことができます。s3-multicloud-backup の場合とは違い、Golang はクロスコンパイルが容易なため、手持ちの環境が Linux でない場合も Linux コンテナを使わずに GOOS=linuxとしてビルドすれば十分でしょう。
main: main.go handler/handler.go
go build -o main
main.zip: main
zip main.zip main
clean:
rm -f main main.zip
$ GOOS=linux make main.zip
$ aws-sam-locallocal invoke CodePipelineNotify -e sample-event.json --template=deploy/template/production.yml
$ make clean
SAM を用いてサーバーレスアプリケーションの構成をテキストファイルに記述することで、いわゆる Infrastructure as Code の恩恵を受けることができ、加えて CodePipeline をはじめとする AWS の開発者ツールを利用することにより、ビルド及びデプロイの自動化が簡単になります。
今回は扱いませんでしたが、SAM と aws-sam-local の組み合わせによって API Gateway を用いた Web アプリケーションの開発やデプロイが容易になるといったメリットもあります。また、今回はテストコードを含めていませんが、buildspec にテストを起動するためのコマンドを含めれば、ビルドのパイプラインにテストを組み込むことも可能です。
近年の Web サービスの開発ではマイクロサービスに代表されるように分散アーキテクチャが採用されるようになってきました。大規模でも素早いプロダクト開発をするために、クックパッドでもマイクロサービスを採用し分散アーキテクチャへの移行を進めています*1。今回は、そのような分散アーキテクチャを利用したシステム構築において必須のコンポーネントになりつつある分散トレーシングについて、クックパッドでの事例を紹介したいと思います。
*2:M. K. Aguilera, J. C. Mogul, J. L. Wiener, P. Reynolds, and A. Muthitacharoen. Performance Debugging for Dis- tributed Systems of Black Boxes. In Proceedings of the 19th ACM Symposium on Operating Systems Principles, December 2003.
料理きろくのプロジェクトを通じて、やはり継続的な試行錯誤に基づいた適切な改善策を講じることが重要であることが再認識できました。
それゆえに、これで完成ではなく、日進月歩の Deep Learning 技術を取り込んだり、データを拡充したりして、よりユーザにとって有用なモデルを構築し続けるのが大事だと考えています。
サーバが米国東部リージョンに集約されていることで、米国や米国近辺に住んでいるユーザはレイテンシが小さくなります。一方で、アジアや中東など地理的に遠い国に住むユーザにとってはレイテンシを悪化させる要因の一つとなっています。例えばリクエストがネットワークを伝わる速度を光速( 300,000 km / sec)とし、日本から米国東部までの距離を 11,000 km としたとき、レイテンシは約 73.3 ms になります。現実には、サーバまでのネットワークの経路は一直線ではありません。加えて、伝送において 300,000 km / sec もの速度が出ることはないためレイテンシはさらに大きくなります。*1
この TLS 接続が成立するまでの時間を先程例に上げた東京と東京リージョンの往復で換算すると、22.2 x 3 = 66.6 msの時間がかかることになります。一方で、東京と米国東部リージョンの往復で換算すると、186.2 x 3 = 558.6 msの時間がかかることになります。
その差は 492 ms です。 この数値は一見問題にならないようにも感じられますが、Jakob Nielsen の著書 Usability Engineering ではレスポンスタイムには3つの境界値が存在すると言われています。
0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.
1.0 second is about the limit for the user’s flow of thought to stay uninterrupted, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 0.1 but less than 1.0 second, but the user does lose the feeling of operating directly on the data.
10 seconds is about the limit for keeping the user’s attention focused on the dialogue. For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely to be highly variable, since users will then not know what to expect.
Catchpoint では計測対象のエンドポイント、監視サーバ群、監視の頻度など監視に関する条件を定義するテストを作る必要があります。この記事では https://cookpad.com/usに対して複数の国の監視サーバからアクセスする “Global top page” というテストを用意しました。
パフォーマンス解析の機能を使うことでこの Global top page テストで定義した各国の監視ノードから https://cookpad.com/usにアクセスした際のレスポンスタイム(ms)を確認できます。例えば、2017年2月25 - 27日の3日間を対象に横軸を日時、縦軸をレスポンスタイムにして描画するとこのようなグラフが得られます。
また、 Geo chart という機能を使うと監視サーバそれぞれでパフォーマンスメトリクスの一つを地図上で可視化できます。以下ではサーバにリクエストを行って最初の1バイトが到着するまでの時間を示す Time To First Byte(TTFB)を表示しています。インドネシアは TTFB が他国と比較して長いことがわかります。物理的な距離が影響しているかもしれません。
Waterfall chart の画面では特定のエンドポイントに特定の監視サーバからアクセスしたときのパフォーマンスメトリクスの詳細な分析ができます。例えば、2017年2月26日12時にインドネシアのジャカルタの監視サーバから https://cookpad.com/us ( Global top page テストを利用)にアクセスした時の Waterfall chart の画面はこのような感じです。ご覧の通り、名前解決や TLS 接続などを含む TTFB 、レンダリング開始、対象のページのレンダリングが完了したことを表す Document Complete などの時間が確認できます。
Image may be NSFW. Clik here to view.
また Waterfall chart を見るとどのリクエストがパフォーマンスに悪影響を与えているかが簡単にわかります。 Global top page テストの場合 cookpad.com/us にアクセスした際の最初のリクエストの TTFB で全体の約1/3の時間を要しています。さらに、TTFB の内訳を確認すると名前解決や TLS 接続に多くの時間を割いている事がわかります。本稿の最初に TLS ハンドシェイクについて言及しましたが、ここで計測された TLS 接続に必要な時間を短くする事は、すなわちレイテンシの改善につながります。
Image may be NSFW. Clik here to view.
またいわゆるクリティカルレンダリングパスが赤く塗りつぶされているのでどのリクエストをパフォーマンス改善のターゲットにすれば良いのかが分かりやすくなっています。以上が Cacthpoint の基本的な機能と使い方の紹介になります。他にもダッシュボードを作って public url で共有できたり、トランザクション処理のあるリクエストのボトルネックを解析したりと様々なことができます。
余談ですが、2017年7月中頃までは Fastly を導入するとアルゼンチンのユーザのレスポンスタイムは悪化するという計測結果が出ていました。その時点で Fastly もブラジルにデータセンタ、 いわゆる Point of Presence(POP) を持っていたのですが期待した結果が得られなかったため原因を調べました。 すると、当時アルゼンチンからのトラフィックではブラジルの POP が使えない状態であることがわかりました。その代わりに米国西部の POP が使われていたのです。計測せずに導入していた場合、大きなコストをかけてユーザ体験を悪化させていた可能性がありました。現在は Fastly でもブラジルの POP のキャパシティが拡張*4されて POP 数も増えました*5。南米のユーザのレスポンスタイムを改善する際の一つの手段となり得ると思います。
Catchpoint で日々レイテンシの計測をしているため問題の解決に集中できる環境が整いましたが、グローバルサービスのパフォーマンス改善はまだ始まったばかりです。今後は RUM の導入やパフォーマンス計測によって得た結果をレイテンシやレスポンスタイムの改善に活かし、世界中のユーザがサービスをより快適に使えるようにしていきたいと思っています。