Cloud Searchのドキュメントを全削除するためにドメインを削除・再作成するRubyスクリプト

[自社サイト](http://www.homes.co.jp/)のURLとキーワードのマッチングする必要する必要があり、フルマネージドで簡単に使える検索エンジンである[AWS CloudSearch](https://aws.amazon.com/jp/cloudsearch/)を利用しています。

APIとして簡単に利用できる上、負荷に応じてオートスケールしてくれるため、「簡単に検索ドメインを作りたいし運用で楽したい」という要件にピッタリでした。

ところが、新しい地名が削除・追加されるたびに、必要な自社サイトのURLのリストも少しずつ変わってしまいます。そのため、それほど頻繁ではないものの、「定期的にドキュメントを全削除して入れ直す」という処理が必要でした。

しかし、[公式ドキュメント](http://docs.aws.amazon.com/ja_jp/cloudsearch/latest/developerguide/troubleshooting.html#ts.cleardomain)を確認したところ、ドメイン内すべてのドキュメントを削除するAPIは用意されていません。

> Amazon CloudSearch ドメイン内のすべてのドキュメントの削除
> 
> Amazon CloudSearch には現在、ドメイン内のすべてのドキュメントを削除するメカニズムが用意されていません。

# ドメインを削除・再作成するRubyスクリプト

そこで、一度ドメイン自体を全削除して再作成することにしました。

いろいろ試行錯誤した結果、以下のようなメソッドを用意したクラスを作りました。```Lib::CloudSearch```は、Rubyの[aws-sdk](https://aws.amazon.com/jp/sdk-for-ruby/)ライブラリの[```Aws::CloudSearch::Client```](https://docs.aws.amazon.com/sdkforruby/api/Aws/CloudSearch.html)の同名のメソッドに、通信が失敗した場合の再接続処理を若干付け足したものです。

```ruby
# CloudSearchの指定のドメインを削除し、作成しなおす
# （この後ドキュメントをアップロードする別のプログラムが動く）
def rebuild_domain
  _recreate_domain
  Lib::CloudSearch.define_index_fields(@domain, @index_fields)
  Lib::CloudSearch.update_service_access_policies(@domain, _access_policies)
  _wait_until_indexing # もしドキュメントのアップロードの処理が必要なら
end

private

# ドメインを一度削除し、再び作成する
def _recreate_domain
  Lib::CloudSearch.delete_domain(@domain)
  loop do
    resp = Lib::CloudSearch.create_domain(@domain)
    break unless resp.domain_status.deleted
    sleep(60)
  end
end

# インデックスが終わるまで待つ
def _wait_until_indexing
  loop do
    resp = Lib::CloudSearch.describe_domains([@domain])                                                   
    domain_status = resp.domain_status_list[0]
    break unless domain_status.processing || domain_status.doc_service.endpoint.nil?
    sleep(60)
  end
end

# アクセスポリシーのファイルを読み込んで文字列として返す
# @return [String] アクセスポリシーのJSON文字列
def _access_policies
  # 省略
end
```

# ドメインの削除完了を待つ処理

```ruby
# ドメインを一度削除し、再び作成する
# （この後）
def _recreate_domain
  Lib::CloudSearch.delete_domain(@domain)
  loop do
    resp = Lib::CloudSearch.create_domain(@domain)
    break unless resp.domain_status.deleted
    sleep(60)
  end
end
```

ちなみに、```delete_domain```や```create_domain```は、削除中や存在しない状態でもエラーを出さずに動作します。そのため、「最初に立ち上げる」場合でも問題なく処理が行われます。

最初、```describe_domains```を使ってレスポンスの中の```domain_status_list```が空になるかで判断していたのですが、

```ruby
resp = Lib::CloudSearch.describe_domains([@domain])                                                   
Lib::CloudSearch.create_domain(@domain) if resp.domain_status_list.empty?
```

空になった後でも、しばらく削除中のドメインが存在している状態で、```create_domain```がきちんと動作しませんでした。

# インデックスが終わるまで待つ処理

```ruby
# インデックスが終わるまで待つ
def _wait_until_indexing
  loop do
    resp = Lib::CloudSearch.describe_domains([@domain])                                                   
    domain_status = resp.domain_status_list[0]
    break unless domain_status.processing || domain_status.doc_service.endpoint.nil?
    sleep(60)
  end
end
```

こちらも試行錯誤しており、当初は```domain_status.processing```（ドメインが処理中かどうか）のみで判定していたのですが、それだけでは後続のドキュメントのアップロードの実装でエラーが出てしまい、不十分でした。

具体的には「[ドキュメント用のAPI](https://docs.aws.amazon.com/sdkforruby/api/Aws/CloudSearchDomain.html)のエンドポイントを調べて、そのエンドポイントのドキュメントアップロードAPIを叩く」という処理で、「ドキュメント用のエンドポイントが見つからない」ことが原因のエラーが出たため、```domain_status.doc_service.endpoint.nil?```で判定しています。

（```domain_status.doc_service```ではドメインが準備できた場合はエンドポイントの文字列が、無効な場合はnilが返されます。）

…正直、このあたりの実装は自分でも微妙に思うので、もっと良い判定方法を見落としているかもしれません…。

# まとめ

ひとまず、Cloud Searchのドキュメントを全削除するためのプログラムを書き、定期的なドメインを作り直す作業を自動化することができました。

作り直すために30分～1時間程度の時間がかかるため、要件によってはドメインを作り直さずに、[ドキュメントを削除する処理をループ](http://mhag.hatenablog.com/entry/2015/09/23/195616)させるなどの方法を取ったほうがいいかもしれません。