🧹

RailsログのElasticsearch送信時に起こるmapping conflict対策

に公開

はじめに

スマサテでWebアプリケーション開発を担当しているサカモトです。
弊社では、 Vectorを使ったログ収集基盤 を構築しています。
この記事では、RailsアプリケーションのログをElasticsearchに送信する際に発生した
mapping conflict(型衝突) 問題とその対策について解説します。

背景

Lograge gemによって成形されたRailsログには params フィールドが含まれており、そのリクエストのパラメータ情報がJSON形式で格納されています。
これを Vector 経由で Elasticsearch にログを送信する際、異なるエンドポイントのリクエストで、paramsのkeyが同じでも型が異なるケースがありました。
例えば以下のようなケースです。

{
  "params": {
    "address": "東京都千代田区"
  }
}
{
  "params": {
    "address": {
      "prefecture": "東京都",
      "city": "千代田区",
      "street": "1-1"
    }
  }
}

あるエンドポイントでは address が文字列型(keyword/text)であり、別のリクエストではオブジェクト型として送信されているような場合、
Elasticsearch では、同じフィールドに複数の型が混在すると mapping conflict(型衝突) となり、該当ドキュメントがインデックスできなくなる問題が発生します。

つまり、先に文字列型で address がmappingされると、後からオブジェクト型で送信されたドキュメントは「address フィールドの型が異なる」というエラーで弾かれてしまいます。

paramsフィールドは非常に柔軟であり、さまざまな型のデータが混在する可能性が高いことや、検索の汚染を防ぐために、
params フィールド全体を Elasticsearch の mapping に登録しない(インデックスしない) ことが望ましいと判断しました。

mappingに登録しない方法

Elasticsearch の index template(データストリームに適用されるテンプレート) の設定によって特定のpropertyをmappingへ登録しないようにすることが可能です。
またElasticsearch では、component template を組み合わせて index template を構築するのが推奨 [1] されています。
params フィールドを mapping に登録せず、_source のみに保持する ために
paramsenabled:false にした専用の component template を作成し、Rails 用の index template に組み込む方式で対応しました。

Step 1: component template を作成する

まず params を mapping しないための component template を作成します。

PUT _component_template/ct-rails-params-disabled // ct-rails-params-disabled は任意のテンプレート名
{
  "template": {
    "mappings": {
      "properties": {
        "params": {
          "type": "object",
          "enabled": false
        }
      }
    }
  }
}

上記のように、 params に対して enabled: false することで params 以下のフィールドをすべて mapping しないような設定ができます。
params 自体は _source にそのまま保存されるため、調査時には参照可能です。

Step 2: Rails 用の index template を作成し、component を読み込む

次に、logs-rails* の Data Stream に適用される index template を作成し、
先ほどの component template を composed_of に追加します。
logs-rails* は Rails アプリケーションのログ用 Data Stream 名に合わせて適宜変更してください。

PUT _index_template/it-logs-rails // it-logs-rails は任意のテンプレート名
{
  "index_patterns": ["logs-rails*"],
  "priority": 300,
  "data_stream": {},
  "composed_of": [
    "ct-rails-params-disabled"
  ]
}

index template は複数が index pattern にマッチする場合、すべての component template がマージされますが、priority が最も高いテンプレートの設定やマッピングが優先されます(conflict した場合は最優先のものが使われます)。
もし、すでに logs-rails* 用の index template が存在する場合は、既存の template の composed_of に追加する形で対応します。
またもし、 logs-* のような汎用的な template が存在する場合は、今回作成した index template の priority を高く設定する必要があります。
そして、必要に応じて logs-* に適用している他の component template も composed_of に追加してください。

Step 3: 既存 Data Stream への反映

Elasticsearch の仕様上、テンプレートは 既存の backing index[2] には反映されません
そのため、この変更を反映させるには ロールオーバーが必要です。

手動ロールオーバー

POST logs-rails/_rollover

これにより、新しく作成される write-index[3]ct-rails-params-disabled の設定が適用され、
params フィールドが mapping されなくなります。

まとめ

mapping conflict によってログがインデックスされない問題は、ログの完全性に大きく影響してしまいます。
些細なことではありましたが、信頼性の高いログ基盤を維持するために重要で細かい設定も取りこぼさずに対応していくことが大切だと改めて感じました。

採用情報
弊社ではWebフルスタックエンジニア、AIエンジニアなどを積極的に採用しています。
ご興味のある方はぜひWantedlyをご覧ください。

https://www.wantedly.com/projects/2099580


脚注
  1. https://www.elastic.co/docs/manage-data/data-store/templates ↩︎

  2. backing index: データストリーム内の実際のインデックス(logs-rails-000001 などロールオーバーによって生成される) ↩︎

  3. write-index: データストリームに新規書き込みされるインデックス ↩︎

スマサテ Tech Blog

Discussion