【技術メモ】Elasticsearch/LuceneのDocValuesについて調べる
公式サイトより
Doc Values
Inverted IndexとDoc Values
- ElasticsearchはInverted Index(転置インデックス)を採用している
- が、ソート時は転置じゃない方が効率的(カラムストアと呼ばれる)
- Elasticsearchでは、カラムストアを"doc values"として実装
- DocValuesはデフォルトで有効
- DocValuesは、fieldがインデックスされるときに生成される
Inverted Index
- 転置インデックスは、certaion operationsのとき"のみ"有利
- だからDoc Valuesが存在する
- 転置インデックスは、termを含むドキュメントを探すのは得意
- 一つのドキュメントに存在するtermを決定するのは苦手
GET /my_index/_search { "query" : { "match" : { "body" : "brown" } }, "aggs" : { "popular_terms": { "terms" : { "field" : "body" } } } }
クエリの部分
- 簡単で効率的だ
- 転置インデックスは、termsでソートされてる
- "brown"をterms listで見つけ、それを含むドキュメントを得るのは超早い
アグリゲーション部分
- 一意のtermをドキュメントから探す必要がある
- 転置インデックスでこれをやろうとするとvery expensive
- インデックスのすべてのtermをiterateして当て込む必要がある
- これは遅いしScale poorlyだ
DocValuesはこれに対して、inverting the relationshipで対処する
つまり、
Search finds documents by using the inverted index.
Aggregations collect and aggregate values from doc values.
Deep Dive on Doc Values
- DocValuesはセグメント毎ベースで生成されて、かつ、immutableだ
DocValuesはDiskをシリアライズする、これはパフォーマンスとスケーラビリティ的に重要
永続データ構造をディスクにシリアライズすることで、
- JVMのヒープ上に保持するのではなく、
OSのファイルシステムキャッシュを利用してメモリを管理することができる
データの「ワーキングセット」が利用可能なメモリよりも小さい場合、OSはドキュメントの値をメモリに常駐させる。
- これにより、ヒープに載せた時と同じパフォーマンスが得られる。
- データの「ワーキングセット」が利用可能なメモリより大きい場合、OSは必要に応じてページングを開始する。
これらを純粋にヒープに載せると、OutOfMemoryが発生するのは避けられない
DocValuesの導入によって、ヒープは小さい設定でよくなる
- 従来は、マシンのメモリの50%をJVMヒープに割り当てることが推奨されていました。
- DocValuesの導入により、過去に推奨されたfull32gbの代わりに、おそらく64GBのマシンで4〜16GBのヒープを検討すること
Heap:Sizing and Swapping
- Elasticsearchのデフォルトヒープ設定は1GB、通常は増やした方がいい
- Luceneは、インメモリのデータ構造をキャッシュするためにOSを活用する。
- Luceneセグメントは個々のファイルに格納され、セグメントは不変なので、これらのファイルは決して変更されません。
- これにより、それらは非常にキャッシュに適しています。
Luceneのパフォーマンスは、OSとのこの相互作用に依存しています。 しかし、Elasticsearchのヒープに使用可能なすべてのメモリを与えた場合、Luceneのために残されることはありません。 これはパフォーマンスに重大な影響を与えます。
基本的な推奨は、ElasticsearchのJVMに50%、Lucene用に50% もしanalyzed string dataをaggregationgしないなら、JVMを減らした方が、GCも軽くなるしLucene用のメモリが増えるのでパフォーマンスは上がる
ヒープは32GBを超えないように。 JVMは圧縮したポインタを使用することができず、GCが非常に遅くなります。
32GBの話は相当重要。もっと大きいメモリを積んだサーバだったら?1TBとか。 まず、そんな大きなサーバじゃないことをおすすめする。 既にサーバがある場合には、以下の方針。 ・主に全文検索→4-32GBをヒープに。and letting Lucene use the rest of memory via the OS filesystem cache. ・主にソート/集計で、not_analyzedデータが多い→4-32GBをヒープに。and leave the rest for the OS to cache doc values in memory. ・主にソート/集計で、analyzed stringsが多い→32GBのノードを複数たてる。fielddataのためにヒープ領域が必要、だが1つのJVMで32GB以上のヒープはだめ
DocValues解説記事
DocValues とは?
- 元々は、Lucene の機能
- これを Elasticsearch でも使えるように v1.0 で実装していたよう
- 今回のリリースでは、この DocValues の高速化が図られています
- データをヒープで管理せず、OSレベルのディスクキャッシュ用のメモリ上で管理
- 安定性が増す形
- v1.4.0 では、このメモリをより効率的に使えるようになった、ということでしょう。
大谷さんによるDocValues記事翻訳
doc values
- メモリの利用の最も大きなものの1つはfielddataです。
- aggregation、ソート、スクリプトがフィールドの値に素早くアクセスするために、フィールドの値をメモリにロードして保持します。
- ヒープは貴重なため、1ビットも無駄にしないためにメモリ内のデータは高度な圧縮と最適化を行っています。
- これは、ヒープスペース以上のデータをもつまでは、非常によく動作します。 これは、多くのノードを追加することによって常に解決できる問題です。
しかし、CPUやI/Oが限界に達してしまうずっと前に、ヒープ空間の容量に到達します。
最近のリリースは、doc valuesによるサポートがあります。
- 基本的に、doc valuesはin-memory fielddataと同じ機能を提供します。
- doc valuesの提供する利点は、それらが、非常に少量のヒープ空間しか使用しない点です。 doc valuesはメモリからではなく、ディスクから読み込まれます。
- ディスクアクセスは遅いですが、doc valuesはカーネルのファイルシステムキャッシュの利点を得られます。
- ファイルシステムキャッシュはJVMヒープとはことなり、32GBの制限による束縛がありません。
- ヒープからファイルシステムキャッシュにfielddataを移行することによって、より小さなヒープを使うことができます。
- これは、GCがより早くなり、ノードが更に安定することを意味します。
Linuxカーネルの基礎知識
ハード・ディスクと主メモリーの間にもアクセス速度の大きな差があります。 そこで,プロセッサのキャッシュと同じように,一度利用したハード・ディスクのデータをそのままメモリー内に保持しておくことで高速化が図れます。 メモリー内にデータがあれば,次回以降のアクセスの際にそれを高速に利用できます。この仕組みをディスク・キャッシュと呼びます。
ディスク・キャッシュの種類
Linuxでは,用途にあわせて複数のディスク・キャッシュを用意
- バッファ・キャッシュ
- バッファ単位でのキャッシュ
- ブロック入出力(=ファイル入出力)はすべてバッファ経由
- 処理終了後もバッファを解放せずにキャッシュとして利用
- ページ・キャッシュ
統合された2つのキャッシュ
- ブロック入出力処理は,すべてバッファ経由
- そのため,ページ・キャッシュに入るデータも必ずバッファ・キャッシュを利用
これは,キャッシュを重複使用することになる
カーネル2.2
- ファイル読込はページ・キャッシュ、書込はバッファ・キャッシュ
- 2種類のキャッシュの整合性を取るために面倒な操作が必要
- カーネル2.4
- ページ・キャッシュとバッファ・キャッシュが一部統合
- バッファ・キャッシュとページ・キャッシュの間の同期問題を劇的に改善
- カーネル2.6
- バッファ・キャッシュを完全に廃止
- ディスク・キャッシュは,すべてページ・キャッシュに統合
- これに伴って,ディスク入出力の単位はブロックではなくページに変更
キャッシュの種類
ディスクキャッシュ
- ディスクキャッシュ = ファイルキャッシュ+メタデータキャッシュ
- メタデータ = i-Node,Directory Entry
- メタデータキャッシュ = スラブキャッシュ
- スラブキャッシュ = ページサイズより小さなデータをキャッシュする仕組み
freeコマンド
- 実使用メモリー量:Totalメモリ - ( free + buffers + cached )
cached:ファイルキャッシュに使用しているメモリ量 buffers:メタデータキャッシュに使用しているメモリ量
カーネルレベルの解説込み
Linux のページキャッシュ
Linux (に限らず他の OS もそうですが) にはディスクの内容を一度読んだらそれはカーネルがキャッシュして、二度目以降はメモリから読む機構 = ページキャッシュがあります。
Linux のディスクキャッシュが「ページキャッシュ」と呼ばれるのは、キャッシュの単位がページだからです。 ページというのは Linux の仮想メモリの最小単位。 つまり何かしらのデータがメモリに存在するとき、そのメモリ領域をカーネルが扱うときの最小単位です。 ディスクの内容をキャッシュする場合、ファイルを丸ごとキャッシュしたりするのではなくiノード番号とファイルのオフセットをキーにしてページ単位でキャッシュします。
ページキャッシュの処理はどこで実装されているか、を知ろうのコーナーです。
read(2) の処理を実装しているであろう箇所、つまりファイルシステム周りのコードを見ればいいわけです が、ファイルシステム関連は幾つかのレイヤに分かれた構造になっているのでなかなか複雑です。
肝になるのは仮想ファイルシステムです。
Linux は ext3、tmpfs、reiserfs、xfs、vfat ... と多数のファイルシステムを扱うことができます。 そこでファイルシステムの実装を隠蔽するのが仮想ファイルシステムです。 またその抽象化されたファイルシステムはブロック型デバイスを抽象化したものと言えます。
すべてのファイルシステムへの命令は VFS を経由してそれぞれの実装へ到達します。 VFS はインタフェースを抽象化するだけでなく、各ファイルシステムに共通の手続きを提供したり、ページキャッシュのような性能を向上させる機能などを提供します。
ページキャッシュ(/proc/meminfo:Buffers + Cached)
ページキャッシュ経由でデータがやり取りされる。
+------------------------------------------+ | ファイルシステム、mmap、スワップ処理など | +------------------------------------------+ read↑ ↓write +------------------------------------------+ | ページキャッシュ (Buffers + Cached) | +------------------------------------------+ read↑ ↓write +------------------------------------------+ | 外部記憶装置(HDDなど) | +------------------------------------------+