複数クエリは1つにまとめよう
ソーシャルサービスを運用しているですが、ユーザがコンテンツを作る、かつ見せ方は時系列ということでスケーラビリティを考慮してDynamoDBを使う選択をしました。
当初タイムライン形式でフォローしている人の投稿の表示とハッシュタグ検索するくらいの仕様だったんですが複数タグで検索したりとかコメントも検索対象にしたりなど検索を強化していきたいという声もあり今はElasticsearch+DynamoDBにしています。
しばらく運用していたのですが、いかんせん検索のレスポンスがおっそい・・・ということで
調べたところ以下のような実装になっていました。
- Elasticsearchから投稿ID列を取得
- 1で取得した投稿ID分getItemを繰り返して投稿データを取得
2が遅い・・・20件取得しようとすると20回クエリ投げてる・・・これはいかん。
ということでgetItemではなくbatchGetItemでまとめて取得するように変更しました。
プログラムは下記のような感じです。
変更前
sample.php
$db = AWS::get('DynamoDb');
$es = new Elasticsearch\Client($config);
$result = [];
$params = ['index' => 'feed',
'fields' => ['_id'],
'size' => 20,
'query' => ['query_string' => $query]
];
$feed_ids = $es->search($params);
# getItemで1件ずつ取得
foreach($feed_ids['hits'] as $feed_id){
$result[] = $db->getItem([
'TableName' => 'feed',
'Key' => ['Id' => [ 'S' => $feed_id ]]]);
}
return $result;
変更後
sample.php
$db = AWS::get('DynamoDb');
$es = new Elasticsearch\Client($config);
$result = [];
$params = ['index' => 'feed',
'fields' => ['_id'],
'size' => 20,
'query' => ['query_string' => $query]
];
$feed_ids = $es->search($params);
$params = [];
foreach($feed_ids['hits'] as $feed_id){
$params[] = [
'id' => ['S' => $feed_id]
];
}
# getItemでまとめて取得
$result = $db->batchGetItem([
'RequestItems' => [
'feed' => [
'Keys' => $params
]
]
]);
return $result;
まとめ
batchGetItemでは1クエリでまとめて取得できない場合もあるので
「UnprocessedKeys」が返ってくるまで再帰的に取得するようにしていたりします。
レスポンス速度も大体1/6程度までになりました。
何回クエリ投げてんだって話ですね・・・ちゃんちゃん