【番外】AWS Lambda / API Gateway / DynamoDB を使ったサーバレスなネットワークロック機構
2016年4月14日木曜日 | Published in AWS, 番外 | 0 コメント
あいも変わらず Evernote 用の iOSアプリを作っているのだが、複数端末からの更新競合を防ぐ為にロックが欲しくなった。Evernote の APIには欲しい機能が見つからず、苦肉の作としてネットワーク越しにロックを管理する仕組みを試してみた。今回 iOSの話ではないが番外編として記録を残す。
AWSの LambdaとAPI Gateway、そして DynamoDB を使ったサーバレスアーキテクチャで組み立てる。
サーバ(EC2)を立てる必要がなく楽ちん。
Lambda Function は JavaScript (node.js)でコーディングする。
ロックを保管管理するストレージ(DB)が排他的な操作に対応しているかどうかが鍵になる。今回の用途を考えると RDSのような大げさなものは使いたくない。またLambdaから容易に操作できるものということで、 DynamoDBを採用する。最小構成で使えば安価に運用できるのも魅力的。この DynamoDBについて排他制御を調べいるとまったく同じことを考えている人が居た。参考になる。
DynamoDBをロックマネージャーとして使う
DynamoDBではアイテム(レコード)作成時の前提条件を定義することができて、これが成り立たないと更新処理を失敗させることができる。この前提条件に、これから操作を行いたいリソースのIDの非存在を使えば、排他制御が可能になる。
アイテム作成実行(情報:リソースID)
前提条件:リソースIDの非存在
もし前提条件が
満たされる場合:
リソースIDを主キーとするアイテムの作成成功
満たされない場合(既にリソースIDが存在する場合):
アイテムの作成失敗(エラー)
さらにアイテムにタイムアウト時刻を入れて、前提条件に時刻も盛り込めばタイムアウトが働くようになる。最終的にはこんな感じ。
アイテム作成実行(情報:リソースID、タイムアウト時刻)
前提条件:リソースIDの非存在 OR タイムアウト時刻 < 現在時刻
もし前提条件が
満たされる場合:
リソースIDを主キーとするアイテムの作成成功
満たされない場合(既にリソースIDが存在する場合):
アイテムの作成失敗(エラー)
なお expectedは Deprecated扱いの様で ConditionExpression が推奨らしい。
設定の流れは次の通り。※細かいところは省く。
先ほどの排他制御をJavaScriptで実装する。1つの関数でロック取得とロック解放に対応する。
ロック取得のところだけ抜粋するとこんな感じ。
ConditionExpression の箇所がキモ。DynamoDBは予約語が多くて、それを回避するために #の別名を定義したり、プレースホルダとして :を定義したりと、慣れていないとハマる。他にも ”N"は数値だが、文字列で数値を渡さないと行けないとか...。
- - - -
しばらく iOSアプリからこのネットワークロックを使ってみることにする。
おわり。
ネットワークロックサービス
便宜上勝手に名付けた。排他制御を持たないリソースへのアクセス競合を防ぐため、ネットワーク上にロックサービスを用意する。既存のリソースを更新する場合、最初にネットワークロックの取得を試みて、その後読み出し→更新→ロック解放と処理を行う。ロック取得に失敗した場合は後でリトライする。もしロック取得したプログラムが落ちてしまう場合は解放されないロックが残ってしまう。この問題を最小限にする目的でロックにタイムアウトの機能を持たせる。時間が経過したロックは無視して、再取得できるようにする。
アーキテクチャ
AWSの LambdaとAPI Gateway、そして DynamoDB を使ったサーバレスアーキテクチャで組み立てる。
サーバ(EC2)を立てる必要がなく楽ちん。
Lambda Function は JavaScript (node.js)でコーディングする。
ロック取得に対する排他制御
ロックを保管管理するストレージ(DB)が排他的な操作に対応しているかどうかが鍵になる。今回の用途を考えると RDSのような大げさなものは使いたくない。またLambdaから容易に操作できるものということで、 DynamoDBを採用する。最小構成で使えば安価に運用できるのも魅力的。この DynamoDBについて排他制御を調べいるとまったく同じことを考えている人が居た。参考になる。
DynamoDBをロックマネージャーとして使う
DynamoDBではアイテム(レコード)作成時の前提条件を定義することができて、これが成り立たないと更新処理を失敗させることができる。この前提条件に、これから操作を行いたいリソースのIDの非存在を使えば、排他制御が可能になる。
アイテム作成実行(情報:リソースID)
前提条件:リソースIDの非存在
もし前提条件が
満たされる場合:
リソースIDを主キーとするアイテムの作成成功
満たされない場合(既にリソースIDが存在する場合):
アイテムの作成失敗(エラー)
さらにアイテムにタイムアウト時刻を入れて、前提条件に時刻も盛り込めばタイムアウトが働くようになる。最終的にはこんな感じ。
アイテム作成実行(情報:リソースID、タイムアウト時刻)
前提条件:リソースIDの非存在 OR タイムアウト時刻 < 現在時刻
もし前提条件が
満たされる場合:
リソースIDを主キーとするアイテムの作成成功
満たされない場合(既にリソースIDが存在する場合):
アイテムの作成失敗(エラー)
なお expectedは Deprecated扱いの様で ConditionExpression が推奨らしい。
設定
設定の流れは次の通り。※細かいところは省く。
1. DynamoDB テーブル作成
2. Lambda Function 作成
先ほどの排他制御をJavaScriptで実装する。1つの関数でロック取得とロック解放に対応する。
ロック取得のところだけ抜粋するとこんな感じ。
dynamo.putItem({
"TableName" : tableName,
"Item" : {
"hash" : {"S" : hash},
"expirationTime" : {"N" : expirationTime.toString()},
// "created" : {"S" : now.toString()}
},
"ConditionExpression" : "expirationTime < :nowTime OR attribute_not_exists(#hash)",
"ExpressionAttributeNames" : {
"#hash" : "hash",
},
"ExpressionAttributeValues": {
":nowTime": {"N": nowTime.toString()},
},
}, function(err, data) {
:
ConditionExpression の箇所がキモ。DynamoDBは予約語が多くて、それを回避するために #の別名を定義したり、プレースホルダとして :を定義したりと、慣れていないとハマる。他にも ”N"は数値だが、文字列で数値を渡さないと行けないとか...。
3. API Gateway作成
作成したLambda FunctionをAPI経由で呼び出せるようにする。GETでロック取得、DELETEでロック解放としてみた。
API Keyを有効にし、URLのクエリパラメータ "hash"を受け取るようにする。
hashパラメータの内容をJSON形式に変換して Lambdaへ渡す(Body Mapping Templatesで定義)。GET、DELETE共に同じ設定を行う。Lambda Functionではプログラム内で HTTP メソッドを元に処理を分岐させている。
設定が終わったらデプロイする。
デプロイ先の環境を複数作成できる。デフォルトでは prod(恐らくproductionの略)が選べる。
今回 API 認証を有効にしたので忘れずに API Keyを作成して、上記で作った API(と環境 prod)に紐付けておく。
API Gateway でデプロイするとAPIを利用するためのエンドポイント(URL)が環境ごとに用意される。このURLとAPI Key(文字列)を使ってアプリからこの APIを呼び出す。
- - - -
しばらく iOSアプリからこのネットワークロックを使ってみることにする。
おわり。
登録:
コメント (Atom)
人気の投稿(過去 30日間)
-
公式リファレンスの Q&A に解説がある。 Technical Q&A QA1551: Detecting the start and end edit sessions of a cell in NSTableView. 方法は Delegate と Not...
-
2011年06月09日 追記 UITableViewCell の Identifier 設定を忘れてたので追記しました。 UINib を使うと簡単に Nib で定義した UITableViewCell が使える。 今回のサンプル: [関連] Cocoaの日々: [iO...
-
Asset Catalogには画像以外のデータも置ける。サウンドファイル(.aif)を置いてみた。 取り出すには NSDataAsset を使う。 let sound = NSDataAsset(name: name) // use sound.data 取り出したサウ...
-
パスワードを暗号化して安全に iPhone/iPad へ保管したい。iOS はこの用途の為に Keychain Services を提供している。今回は Keychain Services について調べてみた。リファレンスの内容に加え、独自に調査・検証した結果をまとめてある。動作...
-
Core Data を使ったアプリケーションで下のような検索機能を実装している。 設定された値を元に NSPredicate を作成し、Core Data に対して検索をかけるのだが、こういう場合に NSCompoundPredicate が役に立つ。 NSCompound...