Kafka のバイナリ検索アルゴリズムの改善

Kafka のバイナリ検索アルゴリズムの改善

[[356205]]

私は最近、Kafak のソース コードをいくつか研究し、Kafak の改良されたバイナリ検索アルゴリズムを皆さんと共有したいと思います。バイナリ検索はすべてのプログラマーが習得すべき基本的なアルゴリズムであり、Kafka がバイナリ検索をどのように改善して独自のシナリオに適用するかを学ぶことは価値があります。

Kafka はインデックス検索シナリオにバイナリ検索を適用するため、この記事ではまず Kafka のログ構造とインデックスについて簡単に紹介します。 Kafak では、メッセージはログの形式で保存されます。各ログは、実際には複数のログ セグメントを含むフォルダーです。ログ セグメントとは、次の図に示すように、メッセージ ログ ファイルと、同じファイル名 (開始オフセット) を持つ 4 つのインデックス ファイルを指します。

メッセージはメッセージ ログ ファイルに追加形式で保存され、各メッセージには一意のオフセットがあります。メッセージを検索する場合、インデックス ファイルを使用して検索します。クエリがオフセットに基づいている場合、メッセージの検索にはオフセット インデックス ファイルが使用されます。インデックスクエリの議論を容易にするために、以下の議論は変位インデックスの背景に基づいて行われます。変位インデックスの本質は、オフセットと対応するディスクの物理的な位置を格納するバイト配列です。ここで、オフセットとディスクの物理的な位置は 4 バイトに固定されており、次の図に示すように、8 バイトごとにキーと値のペアとして考えることができます。

インデックス構造を明確に理解できたので、この記事のテーマである「バイナリ検索」に正式に進むことができます。インデックス項目の配列とターゲット オフセットを指定すると、次のコードを記述できます。

  1. private def IndexSlotRangeFor(idx: ByteBuffer, target: Long, searchEntity: IndexSearchEntity): ( Int , Int ) = {
  2. // _entries はインデックスエントリの数を示します
  3. // 1. 現在のインデックスが空の場合は、見つからないことを示すために (-1,-1) を直接返します
  4. _entries == 0 の場合
  5. (-1, -1)を返す
  6.  
  7. // 2. 検索するオフセットが現在の最小オフセットより小さくないことを確認する
  8. (compareIndexEntry(parseEntry(idx, 0), target, searchEntity) > 0)の場合
  9. 戻り値(-1, 0)
  10.    
  11. // 3. バイナリ検索アルゴリズムを実行してターゲットを見つける
  12. var lo = 0
  13. var hi = _entries - 1
  14. (低<高)の間{
  15. val mid = ceil(hi / 2.0 + lo / 2.0).toInt
  16. val found = parseEntry(idx, mid)
  17. val compareResult = compareIndexEntry(見つかったもの、ターゲット、検索エンティティ)
  18. 比較結果 > 0 の場合
  19. 高 = 中 - 1
  20. そうでない場合 (比較結果 < 0)
  21. lo = 中
  22. それ以外 
  23. リターン(ミッド、ミッド)
  24. }
  25.    
  26. (lo、if (lo == _entries - 1) -1 else lo + 1)
  27. }

上記のコードでは、通常のバイナリ検索を使用しています。これがどのような問題を引き起こす可能性があるか見てみましょう。各インデックス項目のサイズは 4B ですが、オペレーティング システムがメモリにアクセスする最小単位はページです。ページは通常 4KB (4096B) で、512 個のインデックス項目が含まれます。インデックス内の指定されたオフセットを見つけることは、オペレーティング システムがメモリにアクセスしたときに指定されたオフセットが配置されているページを見つけることになります。次の図に示すように、インデックス サイズが 13 ページであると仮定します。

Kafka は通常、メッセージを読み取るときに最新のオフセットを読み取るため、クエリされるページは末尾、つまりページ 12 に集中します。次に、上記のコードを組み合わせて、最新のオフセットをクエリするときにどのページにアクセスされるかを確認します。バイナリ検索によれば、ページ 6、9、11、12 が順番に訪問されます。

Kafka が受信するメッセージ数が増えると、インデックス ファイルも 13 ページまで増加します。このとき、バイナリ検索に従って、7、10、12、13 ページが順番にアクセスされます。

訪問したページが前のページとは全く異なっていることがわかります。以前は、ページが 12 しかなかったため、Kafka はインデックスを読み取るときにページ 6、9、11、12 に頻繁にアクセスしていました。ただし、Kafka は速度を上げるために mmap を使用するため、すべての読み取りおよび書き込み操作はオペレーティング システムのページ キャッシュを経由するため、ページ 6、9、11、および 12 はディスクの読み込みを回避するためにページ キャッシュにキャッシュされます。ただし、ページ 13 を増やすと、ページ 7、10、12、13 にアクセスする必要があります。ページ 7 と 10 は長い間アクセスされていないため (最近のオペレーティング システムではページ キャッシュの管理に LRU またはその派生形式が使用されています)、ページ キャッシュ外になっている可能性があり、ページ フォールト割り込みが発生します (スレッドは、ページ キャッシュにキャッシュされていないディスクからのデータの読み込みを待機してブロックされます)。 Kafka の公式テストでは、この状況により数ミリ秒から 1 秒の範囲の遅延が発生します。

上記の状況を考慮して、Kafka はバイナリ検索を改良しました。通常、データはインデックスの末尾から読み取られます。次に、インデックスの最後の 8192B (8KB) を「ホット ゾーン」に、残りを「コールド ゾーン」に分割し、それぞれに対してバイナリ検索を実行します。コードは次のように実装されます。

  1. private def indexSlotRangeFor(idx: ByteBuffer, target: Long, searchEntity: IndexSearchType): ( Int , Int ) = {
  2. // 1. 現在のインデックスが空の場合は、見つからないことを示すために (-1,-1) を直接返します
  3. _entries == 0の場合
  4. (-1, -1)を返す
  5.  
  6. // メソッドとしてカプセル化されたバイナリ検索
  7. def binarySearch(開始: Int 終了: Int ):( Int Int ) = {
  8. var lo =開始 
  9. var hi =終了 
  10. 低<高の間{
  11. 中間値 = (下限 + 上限 + 1) >>> 1
  12. val found = parseEntry(idx, mid)
  13. val compareResult = compareIndexEntry(見つかったもの、ターゲット、検索エンティティ)
  14. 比較結果が 0 より大きい場合
  15. 高 = 中 - 1
  16. そうでない場合(比較結果 < 0)
  17. lo = 中
  18. それ以外 
  19. リターン(ミッド、ミッド)
  20. }
  21. (lo、if (lo == _entries - 1) -1 else lo + 1)
  22. }
  23.  
  24. /**
  25. * 2. ホットゾーンの最初のインデックス項目を確認します。 _warmEntries はいわゆる境界線であり、現在は 8192 バイトに固定されています。
  26. * OffsetIndexの場合、_warmEntries = 8192 / 8 = 1024、つまり1024番目のインデックスエントリ
  27. * ほとんどのクエリはインデックス項目の末尾に集中しているため、末尾の8192バイトがホットゾーンとして設定されます。
  28. * クエリ対象がホットゾーンインデックス範囲内にある場合は、ページの中断を避けるためにホットゾーンを直接クエリします。
  29. */
  30. val firstHotEntry = Math.最大(0, _entries - 1 - _warmEntries)
  31. // 3. ターゲットオフセット値がホットゾーンかコールドゾーンかを判断する
  32. if(compareIndexEntry(parseEntry(idx, firstHotEntry), target, searchEntity) < 0) {
  33. // ホットゾーンの場合はホットゾーンを検索
  34. binarySearch(firstHotEntry, _entries - 1)を返します
  35. }
  36.  
  37. // 4. 求める変位値が現在の最小変位値より小さくないことを確認する
  38. if(compareIndexEntry(parseEntry(idx, 0), target, searchEntity) > 0)
  39. 戻り値(-1, 0)
  40.  
  41. // 5. 寒冷地帯の場合は寒冷地帯を検索する
  42. バイナリ検索(0, 最初のホットエントリ)
  43. }

これの利点は、テールが頻繁にクエリされる場合、テール ページは基本的にページ キャッシュ内にあるため、ページ フォールトによる中断を回避できることです。

前の例を使って見てみましょう。各ページには最大 512 個のインデックス項目が含まれるため、最後の 1024 個のインデックス項目を含むページはホット領域と見なされます。次に、ページ 12 がいっぱいでない場合は、ページ 10、11、および 12 がホット エリアであると判断されます。ページ 12 がちょうどいっぱいになると、ページ 11 と 12 がホット エリアであると判断されます。ページ 13 が増加していっぱいでない場合は、ページ 11、12、および 13 がホット領域であると判断されます。最新のメッセージを読んでいると仮定すると、ホットゾーンでのバイナリ検索は次のようになります。

ページ 12 がいっぱいでない場合は、ページ 11 と 12 が順番にアクセスされます。ページ 12 がいっぱいになると、アクセスしたページにも同じ状況が適用されます。ページ 13 が表示されると、ページ 12 とページ 13 が順番にアクセスされ、長時間アクセスされていないページにはアクセスされなくなるため、ページ フォールトによる中断を効果的に回避できます。

ホットゾーンのサイズが 8192 バイトに設定されている理由については、公式の説明では、これが適切な値であるということになります。

ホット ゾーン内のページ数が 3 以下になるように十分小さい場合、バイナリ検索で使用されるページはページ キャッシュ内にある可能性が高くなります。つまり、設定値が大きすぎると、ホットゾーンのページがページ キャッシュに含まれなくなる可能性があります。

これは、4MB のメッセージ データをカバーするのに十分な大きさ (8192 バイト、または変位インデックスの場合は 1024 個のインデックス エントリ) であり、ほとんどの同期ノードがホット ゾーンでクエリを実行するのに十分です。

最後に一言でまとめると、Kafka インデックスで通常のバイナリ検索を使用すると、ページ欠落の中断が発生し、遅延が発生します。また、ほとんどのクエリが末尾に集中するため、インデックス領域をホット領域とコールド領域に分割し、別々に検索することで、ホット領域のページが可能な限りページキャッシュ内にあるようにすることができ、ページ欠落による中断を回避することができます。

<<:  Scality: コンテナ化とクラウドネイティブアプリケーションが2021年のデータストレージ環境を定義する

>>:  組織はクラウドに潜んでいる人物を把握する必要がある

推薦する

Baiduがオリジナルコンテンツと転載コンテンツを区別する方法を分析

SEO にとって、ソフト記事は非常に効果的なプロモーション方法であるだけでなく、外部リンクを増やすた...

微博マーケティングはクローズドループ時代へ加速している

新華網、北京、4月2日(周文林記者)Weiboでのワンストップショッピングはもはや遠い夢ではない。オ...

痕跡を残さずにオリジナル記事のキーワードを最適化する方法

ウェブサイトが成功したいのであれば、当然、充実したコンテンツが欠かせません。eコマースプロジェクトを...

ニュース記事が含まれない理由についての簡単な説明

ニュース記事が含まれない理由についての簡単な説明社内の最適化担当者として、最近多くの編集者から、なぜ...

潜在顧客を獲得し、取引を成立させるための3つのシンプルなオンラインマーケティングプラン

本日、Pujiang は、Win インターネット マーケティング オペレーティング システムにおける...

百度のスナップショットの誤解、完全な分析はランキングの重みに影響を与えない

多くのウェブマスターは、Baiduスナップショットについて非常に懸念しています。多くの友人が、ウェブ...

seopassword SEO初心者を解放してください

過去 1 ~ 2 か月で、SEO 業界で非常に人気が高まった SEO トレーニング - seopas...

なぜテンセントは検索をうまくできないのか?テンセント・ソソのインターネットでの消滅の謎を解明

テンセントといえば、誰もが知っているように、同社は中国最大のインターネット企業であり、時価総額は30...

クラウドエコノミーの変革の可能性を解き放つ: 課題を克服し、価値を最大化する

進化するテクノロジー環境において、企業はクラウドベースの経済モデルがもたらす大きなチャンスをますます...

ウェブサイト運営者はどのようにしてウェブサイトを素早く診断できるのでしょうか?

ウェブサイトの運営で遭遇する問題は、誰にとっても最大の頭痛の種ですが、特に、ウェブサイトを最適化する...

ウェブサイトのコンテンツをうまく作るには、10のポイントから始める必要があります

ウェブサイトの重さはウェブサイトのコンテンツに直接左右されることを認めなければなりません。ウェブサイ...

エッジコンピューティングが未来に革命を起こす

近年、接続デバイスの数が急増し、膨大な量のデータが生成されています。この成長に伴い、従来の集中型コン...

NetEase Cloudは業界のニーズに応えて高品質の顧客にサービスを提供し、金融業界の「インターネットサーフィン」を支援します。

金融とテクノロジーの組み合わせにより、金融サービスの境界が大幅に拡大し、包括的な金融が多数の小規模お...

医学論文が収録されない理由を考える

最近、筆者は外部リンク記事を投稿する際に非常に興味深い現象を発見しました。私の日々の仕事は主に機密情...