CloudSearchのdomainを複数環境で共有したい
この記事は VALU Advent Calendar 2019 20日目のエントリです。
株式会社VALUのバックエンドエンジニアのMito Memelです。前回のエントリで、渾身のギャグ「これでキューが急に詰まっても安心ですね!」が誰にも拾ってもらえなくて悲しいです。
VALUのサーバインフラはAWSですが、AWSで全文検索システムを構築する場合、Elasticsearch ServiceとCloudSearchどちらを使えば良いか迷いますよね。Elasticsearchにしか無い機能を使いたい場合は選択の余地はありませんが、そうでない場合、CloudSearchのオートスケーリングは魅力的です。Elasticsearch Serviceは自動フェイルオーバーはしてくれるものの、インスタンスタイプ・ノード数の計画は自分で立てる必要があります。何も考えずにdomain(CloudSearchクラスタの設定単位)を1個作るだけで良いCloudSearchはとてもお手軽です。
しかし、CloudSearchにも欠点があります。スケールアウトは得意なんですが、性能がそれほど必要では無い場合、スケールダウンしたくても最小構成で1domainにつき$60/月程度(東京リージョンの場合)掛かります。1domainだけならまだ良いんですが、例えば開発環境が複数あったりすると環境ごとにdomainを作らなければならず、リソースは有り余っているのに料金がかさむという状況になりがちです。その点Elasticsearchだと1domain内でもindexで分けることができるので、複数環境でdomainを共有するのも簡単です。残念ながらCloudSearchにはElasticsearchで言うindexのようなものは無いのですが、どうにかして1domainを複数環境で共有できるようにしたいものです。そこで、実際に試してみました。
fieldでがんばる
まず最初に思い付くのは、index
みたいな名前のfieldを追加して、検索時に毎回そのfieldを条件に含めるというやり方です。この方法でもできなくは無いのですが、いくつか欠点があります。
idフィールドの被り
CloudSearchにはMySQLで言うauto_incrementのような自動採番は無く、documentのアップロード時にユニークなidを明示的に指定する必要があります。このidは例えばユーザー検索ならRDB側で振っているユーザーIDを使うなどすれば良いのですが、1domainを複数環境で共有する場合、idが被ってしまう可能性があります。なので、idのprefixに環境名を付けるなどの工夫が必要となります。
field名のルールが増える
「index
みたいな名前のフィールドを追加する」と先ほど書きましたが、documentの検索対象fieldでうっかり同じ名前を使ってしまうみたいな事故が考えられます。なので環境識別のための名前空間として使用するfieldはsuffixに _
を付けて index_
みたいな名前にし、検索対象fieldではそのsuffixを避ける、みたいなルールが必要になります。これでも良いんですが、なんかこう、もうちょっとスマートにやりたいですよね。
idを構造化する
idフィールドの被りを避けるためにidにprefixを付けるなどの工夫が必要なのであれば、いっそのことidフィールドのprefix検索だけで名前空間を分けられないか?と考えました。
idフィールドに使える記号は _ - = # ; : / ? @ です。これを使って、例えば
{環境名}/{データ種別}/{連番}
のような階層構造のidを付ければ、idの被りを防いだ上で、余分なfieldを追加しなくてもidのprefix検索で環境を特定することが可能です。実際のコードは以下のようになります。
documentのアップロード
public function uploadDocuments(CloudSearchDomainClient $client, string $index, string $type, array $documents) { // idを置換 $formatted_documents = array_map(function($document) use ($index, $type){ $document['id'] = "{$index}/{$type}/{$document['id']}"; return $document; }, $documents); $client->uploadDocuments([ 'contentType' => 'application/json', 'documents' => json_encode($formatted_documents), ]); }
documentの検索
public function search(CloudSearchDomainClient $client, string $index, string $type, string $query) { // クエリにidのprefix条件を追加 $formatted_query = "$query _id:{$index}/{$type}/*"; $response = $client->search([ 'query' => $formatted_query, 'queryParser' => 'lucene', ]); // id部分のみを返す return array_map(function($id){ return explode('/', $id)[2]; }, array_column(array_get($response, 'hits.hit', []), 'id')); }
まとめ
idのprefix検索を利用することで、CloudSearchのdomainを複数環境で共有することができました。これで開発環境では料金を抑えつつ、本番環境ではCloudSearchのオートスケールのメリットを享受することができます。