三津石智巳

👦🏻👦🏻👧🏻 Father of 3 | 🗺️ Service Reliability Engineering Manager at Rakuten Travel | 📚 Avid Reader | 👍 Wagashi | 👍 Caffe Latte | 👍 Owarai

【感想】Elasticsearch: The Definitive Guide


ようやくElasticsearchの内部を学べる日が来た…。

"Instead of rewriting the whole inverted index, add new supplementary indices to reflect more-recent changes. Each inverted index can be queried in turn—starting with the oldest—and the results combined."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

転置インデックスは更新の都合で分割されている。全く知らなかった。

"Lucene, the Java libraries on which Elasticsearch is based, introduced the concept of per-segment search. A segment is an inverted index in its own right, but now the word index in Lucene came to mean a collection of segments plus a commit point—a file that lists all known segments, as depicted in Figure 11-1."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

  • Luceneにおける分割の単位はsegmentと呼ばれる
  • segmentの集合がLuceneにおけるindexと呼ばれる。これは、Elasticsearchにおけるindexとは異なる。
  • LuceneにおけるindexはElasticsearchのshardである。
  • Elasticsearchのshardの集合がElasticsearchにおけるindexである。

Elasticsearchが検索するとき、2段階の集約が行われている。

  • Elasticsearchのshardの集約
  • Luceneのsegmentの集約

OracleやMongoDBと比較して大きく異なる点はin-memory bufferのrefreshが行われるまで、そのsegmentはsearchができない点だと考えられる。私の理解が正しければMVCCを採用しているストレージエンジンではディスクへのfsyncが起こる前から、メモリ上で読み込みができる認識。

"Document updates work in a similar way: when a document is updated, the old version of the document is marked as deleted, and the new version of the document is indexed in a new segment. Perhaps both versions of the document will match a query, but the older deleted version is removed before the query results are returned."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

delete/updateの場合、古いsegment内のdocumentは単にマーキングされる。クエリにマッチした場合でもレスポンスを返す前にフィルタリングされる。この方式ではsearchのコストは多少高いようだ。

ここまで読んできて、MongoDB/WiredTigerの関係から類推すると、Lucene上でsegmentのfull commitが専用のスレッドだけでは間に合わなくなった場合にElasticsearchのスレッドでクエリ・コマンドの受付が停止するのではないか。

"In Elasticsearch, this lightweight process of writing and opening a new segment is called a refresh. By default, every shard is refreshed automatically once every second. This is why we say that Elasticsearch has near real-time search: document changes are not visible to search immediately, but will become visible within 1 second."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

つまりfull commitとは別に、デフォルト1秒毎にrefreshが行われる。私の理解では、in-memory bufferからfilesystem cache(メモリ)上にsegmentの書き込みが行われ、filesystem cacheからsegmentのsearchが可能になる。

CQRSのようなアーキテクチャパターンを採用している場合、index.refresh_intervalはデータ同期のSLOに合わせて良いように思われる。

ただ、in-memory bufferにdocumentがより多くとどまることになるが、頻繁なrefreshと多量のデータに対するrefreshの間のトレードオフはまだ分からない。

"The translog is also used to provide real-time CRUD."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

ひぇー!!!😲なんで?🤔
WiredTigerでjournalからデータを読むことはないはず。secondary indexを別のタイミングでつくるというのは今まであまり考えたことがなかったので要学習。

"The action of performing a commit and truncating the translog is known in Elasticsearch as a flush. Shards are flushed automatically every 30 minutes, or when the translog becomes too big."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

一度用語の整理を

  • refresh → delete in-memory buffer
  • full commit=flush → delete translog

なお、上記引用のデフォルト値は本書執筆当時の1.4のもの。


1.4では、(おそらく)translogのfsyncはrequest毎、flushは30mもしくは200mb毎がデフォルト値となっている。


7.3では、デフォルトのindex.translog.durability: requestの場合request毎だが、asyncの場合インターバルを指定できる。5s毎のfsyncがデフォルト。flushは512mb毎。


MongoDBと比較してみる。

If a write operation includes or implies a write concern of j: true.

まず、write concernとして指定できる点は違う。j: trueでない場合、100ms毎のfsyncがデフォルト。

index.translog.durability: requestの挙動が文献によってややブレがある

  • translogのfsyncをrequest毎に行う
  • translogのflushをrequest毎に行う

普通に考えると、後者のflush(full commit)がコストだからrefreshやtranslogが存在するわけで、前者が正しい挙動だと思われるが…

個人的にはindex.translog.durabilityは7.3デフォルトのrequestのままで良いように思われる。そもそもディスクI/Oがボトルネックになり、アプリケーションの性能に影響が出るのはtranslogのfsyncではなく、flush(full commit)なのではないかという仮説を持っている。
flushがボトルネックの場合、デフォルトの512mbのindexing毎にパフォーマンスのスパイクが観測できると考えられる。なお、flushがボトルネックである場合、indexing rateが変えられないのであれば、スケールアップもしくはnode/shardの追加ぐらいしか本質的にはできることがなさそう。

他のシチュエーションとしては、全てのindexingがclient側で待ちが発生している場合、translogのfsyncを待っている可能性がある。async/100msを試す価値はあると思われるが、単にin-memory buffer/translogにボトルネックが移る。他にTOO_MANY_REQUESTS、optimistic concurrency controlのモニタリングも必要になる。


直接関係ないがこういう回答ができる人になりたい。


これも分かりやすいが、refresh/flushの用語が混ざっている。


なお、2018年にtranslog依存であった種々の操作をLuceneのsoft delete依存に移植したようだ。

ここまで読んできて、reindexini/initial loadの場合には短時間ですべてメモリ上で完結するに越したことはないが、具体的なパラメーターは非常に状況依存なのと、near-realtime処理をするときとの切り替えも気を使うので、注意深い設計とテストが必要という当たり前の結論に至る。

"The merging of big segments can use a lot of I/O and CPU, which can hurt search performance if left unchecked. By default, Elasticsearch throttles the merge process so that search still has enough resources available to perform well."

via Check out this quote from Elasticsearch: The Definitive Guide - https://learning.oreilly.com/library/view/elasticsearch-the-definitive/9781449358532/part01ch11.html

他にもsegment mergingの際にもコストがかかる。MongoDB/WiredTigerではcompactに相当すると思うがあまり意識できてないな。