Go言語で書かれた、Amazon DynamoDBのプロキシサーバの紹介をします。
もともと、他言語のPHPで書かれたウェブアプリケーションで、AWS SDK for PHPを利用してDynamoDBを使おうとしていたのですが、Class Preloader for PHPがうまく動かせず、全然パフォーマンスが出なくて困っていました。EC2 c4.xlarge nginx + php-fpm でベンチマークして800 req/s ほどでした。(PHP with Amazon DynamoDB)
そのため、他言語でDynamoDBのプロキシサーバを立てて、そのAPIを経由してデータのSet / Getをしようと考えて、色々探しまわった結果、SmugMugという企業がオープンソースで公開している、Goで書かれた bbpd というソフトウェアを見つけました。これを試してみたところ、思いのほか簡単にパフォーマンスの良いプロキシサーバを建てることができたので、紹介したいと思います。同じように困っている方を想定して、Goをインストールするところから書いていきます。
環境: EC2 Instance Type: c4.xlarge / AMI ID: Amazon Linux AMI 2015.09.1 (HVM), SSD Volume Type - ami-383c1956
Goをインストール
$ sudo yum -y install golang zsh $ go version go version go1.4.2 linux/amd64 $ mkdir ~/gocode $ echo 'export GOPATH=$HOME/gocode' >> ~/.bashrc $ source $HOME/.bashrc
bbpdをインストール
$ go get github.com/smugmug/bbpd $ sudo cp $GOPATH/bin/bbpd /usr/bin/ $ sudo cp $GOPATH/src/github.com/smugmug/bbpd/bin/bbpd/bbpd_ctl /usr/bin/ $ sudo cp $GOPATH/src/github.com/smugmug/bbpd/bin/bbpd/bbpd_daemon /usr/bin/ $ sudo chmod 755 /usr/bin/bbpd*設定ファイルを編集。
アクセスキー、シークレットキーを入力。Tokyoリュージョンの場合は、us-east-1をap-northeast-1に変更。
$ cp $GOPATH/src/github.com/smugmug/godynamo/conf_file/test_aws-config.json ~/.aws-config.json $ emacs ~/.aws-config.json { "extends":[], "services": { "default_settings":{ "params":{ "access_key_id":"myAccessKey", "secret_access_key":"mySecret", "use_sys_log":true } }, "dynamo_db": { "host":"dynamodb.us-east-1.amazonaws.com", "zone":"us-east-1", "scheme":"http", "port":80, "keepalive":false, "iam": { "use_iam":false, "role_provider":"", "access_key":"", "secret_key":"", "token":"", "base_dir":"", "watch":false } } } }bbpdデーモンをスタート。デフォルトでは、ポート番号12333と12334の2つを受け付けるようになります。
$ bbpd_ctl start **** starting bbpd as daemon bbpd - started 2015/11/10 07:40:12 global conf.Vals initialized 2015/11/10 07:40:12 not using iam, assume credentials hardcoded in conf file 2015/11/10 07:40:12 starting bbpd... 2015/11/10 07:40:12 induce panic with ctrl-c (kill -2 2888) or graceful termination with kill -[1,3,15] 2888 2015/11/10 07:40:12 trying to bind to port:12333 2015/11/10 07:40:12 init routing on port 12333 2015/11/10 07:40:12 global conf.Vals initialized 2015/11/10 07:40:12 not using iam, assume credentials hardcoded in conf file 2015/11/10 07:40:12 starting bbpd... 2015/11/10 07:40:12 induce panic with ctrl-c (kill -2 2894) or graceful termination with kill -[1,3,15] 2894 2015/11/10 07:40:12 trying to bind to port:12333 2015/11/10 07:40:12 port 12333 already in use 2015/11/10 07:40:12 trying to bind to port:12334 2015/11/10 07:40:12 init routing on port 12334プロセスを確認。
$ pgrep -l bbpd 2888 bbpd 2894 bbpdCurlでAPIを叩いてステータスを確認。
$ curl -H "X-Bbpd-Indent: true" "http://localhost:12333/Status"
{ "Status": "ready", "AvailableHandlers": [ "/DescribeTable/", "/DeleteItem", "/ListTables", "/CreateTable", "/UpdateTable", "/StatusTable/", "/PutItem", "/PutItemJSON", "/GetItem", "/GetItemJSON", "/BatchGetItem", "/BatchGetItemJSON", "/BatchWriteItem", "/BatchWriteItemJSON", "/UpdateItem", "/Query", "/Scan", "/RawPost/", "/" ], "Args": { "X-Bbpd-Indent": "set '-H \"X-Bbpd-Indent: True\" ' to indent the top-level json", "X-Bbpd-Verbose": "set '-H \"X-Bbpd-Verbose: True\" ' to get verbose output" }, "Summary": { "StartTime": "2015-11-10 07:40:12.993368854 +0000 UTC", "RunningTime": "1m17.069634284s", "LongestResponse": "0.00ms", "AverageResponse": "0.00ms", "LastResponse": "no requests made yet", "ResponseCount": "0" } }bbpdが立ち上がりました。
テーブルを作成
$ curl -H "X-Amz-Target: DynamoDB_20120810.CreateTable" -X POST -d ' { "AttributeDefinitions": [ { "AttributeName": "user_id", "AttributeType": "S" } ], "KeySchema": [ { "AttributeName": "user_id", "KeyType": "HASH" } ], "ProvisionedThroughput": { "ReadCapacityUnits": 100, "WriteCapacityUnits": 100 }, "TableName": "bbpd-test" } ' http://localhost:12333/
{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"user_id","AttributeType":"S"}],"CreationDateTime":1.447141682984E9,"ItemCount":0,"KeySchema":[{"AttributeName":"user_id","KeyType":"HASH"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":100,"WriteCapacityUnits":100},"TableArn":"arn:aws:dynamodb:ap-northeast-1:891959351900:table/bbpd-test","TableName":"bbpd-test","TableSizeBytes":0,"TableStatus":"CREATING"}}
Put
$ curl -H "X-Amz-Target: DynamoDB_20120810.PutItem" -X POST -d ' { "Item": { "num": 1, "numlist": [ 6, 7, 1, 2, 3, 9, -7234234234.234234 ], "stringlist": [ "pk1_a", "pk1_b", "pk1_c" ], "user_id": "my_user_id1" }, "TableName": "bbpd-test" } ' http://localhost:12333/PutItemJSON
Get
$ curl -H "X-Amz-Target: DynamoDB_20120810.GetItem" -X POST -d ' { "Key": { "user_id": { "S": "my_user_id1" } }, "TableName": "bbpd-test" } ' "http://localhost:12333/GetItemJSON"
{"Item":{"num":1,"numlist":[6,3,9,7,2,1,-7.234234234234234e+09],"stringlist":["pk1_b","pk1_c","pk1_a"],"user_id":"my_user_id1"}うまく動いていますね!
ベンチマーク
DynamoDB: Provisioned Read Capacity Units: 10000 / Provisioned Write Capacity Units: 10000
EC2: Instance type: c4.large, c4.xlarge, c4.2xlarge
Getのパフォーマンスを計測。
# emacs postdata.file { "Key": { "user_id": { "S": "my_user_id1" } }, "TableName": "bbpd-test" }
# ab -n 100000 -c 100 -p postdata.file -T "application/json" http://localhost:12333/GetItemJSON c4.large Requests per second: 4754.25 [#/sec] (mean) c4.xlarge Requests per second: 8591.35 [#/sec] (mean) c4.2xlarge Requests per second: 8540.63 [#/sec] (mean)8000 req/s くらいあれば、殆どのウェブアプリケーションでは、要件を満たせるのではないでしょうか。
注意
更に、コネクション数を増やしていくと、DynamoDBのキャパシティを超えて、APIから400エラーが返ってきます。その場合は下記のコードでリトライ処理。
https://github.com/smugmug/godynamo/blob/master/authreq/authreq.go#L152
デフォルトでは、7回までリトライします。もし、"authreq.retryReq: failed retries on %s:%s"などといったログ出力されていなければ、リクエストが成功していますが。念のため、ログの出力をモニタリングしておくと良いでしょう。
ほかに、ファイルディスクリプタ上限も上げておくと良いです。
PHPからbbpdを使ってみる
PHPからbbpdを叩いたときのベンチマークです。phpのコードはこちら。
Instance type: c4.xlarge
# ab -c 100 -n 10000 http://127.0.0.1/get_go.php
Requests per second: 4260.64 [#/sec] (mean)AWS SDK for PHPを使った時は、800req/sくらいだったので、5倍パフォーマンスが良いです。これで、要件によっては、その範疇に収まるようにできるのではないでしょうか。選択肢のひとつとして考えてみるのもありかと思います。
GoのコードでGoDynamoを使ってみる
GoからGoDynamoを使うコードはこのような感じになります。プロダクション用に書くならば、テストコード(https://github.com/smugmug/godynamo/tree/master/tests)を参考にすると良いと思います。
まとめ
bbpd・GoDynamoを使ってDynamoDBにアクセスしてみました。
GoDynamoの内部では、公式のAWS SDKと同じく、自動再試行ロジックを実装するなど、丁寧に作られている印象があって好感が持てました。とくに、bbpdに関しては、必要な機能だけを持たせたAPIサーバということで、マイクロサービス感がありました。今回は、PHPからbbpdを叩いてDynamoDBにアクセスするといった使い方をしていて、個人的にはAWS SDKのコードを修正したり、PHP ExtensionをCで頑張って書いたりするよりは楽だったのが良かったところです。
See Also.
SmugMug: From MySQL to Amazon DynamoDB (DAT204) | AWS re:Invent 2013 http://www.slideshare.net/AmazonWebServices/smugmug-from-mysql-to-amazon-dynamodb-dat204-aws-reinvent-2013
Handling Errors in DynamoDB Operations - Amazon DynamoDB https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ErrorHandling.html
GoDynamo
https://github.com/smugmug/godynamo
AWS SDK for Go - General Availability
https://aws.amazon.com/about-aws/whats-new/2015/11/aws-sdk-for-go-general-availability/
Amazon DynamoDB logo / Amazon Web Services LLC - http://aws.typepad.com/aws/2011/12/introducing-aws-simple-icons-for-your-architecture-diagrams.html
gopher-side_color.{ai,svg,png} was created by Takuya Ueda (http://u.hinoichi.net). Licensed under the Creative Commons 3.0 Attributions license. - https://github.com/golang-samples/gopher-vector#gopher-side_color
The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)