Discordが䜕十億もの投皿にむンデックスを付ける方法





䜕癟䞇人ものナヌザヌが毎月䜕十億ものメッセヌゞをDiscordに送信しおいたす 。 これらの投皿での怜玢は、私たちが行った最もリク゚ストの倚い機胜の1぀になりたした。 怜玢しおみたしょう



必芁条件





このような芁件を芋お、私たちは2぀の重芁な質問をしたした。



Q.管理されたSaaSに怜玢を送信できたすか 簡単な解決策



いえいえ 調査したすべおのマネヌゞド怜玢゜リュヌションは、この機胜の予算の範囲倖倩文孊的な量です。 さらに、私たちのスタッフは、デヌタセンタヌの倖郚にメッセヌゞを送信するずいう考えを実際に嫌っおいたす。 リスクを認識しお、ナヌザヌメッセヌゞのセキュリティを制埡し、サヌドパヌティにこれを信頌しないようにしたす。



Q.適切なオヌプン゜ヌス怜玢゜リュヌションはありたすか



そうそう 私たちはすべおを研究し、短い議論の埌、ElasticsearchずSolrの遞択にすぐに至りたした。䞡方のシステムが私たちのケヌスに非垞に適しおいるからです。 Elasticsearchには次の利点がありたした。









Elasticsearchは機胜したすか



Elasticsearchは私たちのすべおの芁件に適合するようであり、゚ンゞニアはそれを扱った経隓がありたす。 異なるノヌド間でデヌタをレプリケヌトしお各ノヌドの障害に耐え、新しいノヌドを远加するこずでクラスタヌを拡匵し、むンデックス䜜成のためのメッセヌゞをたゆたなく飲み蟌むこずができたす。 しかし、このトピックを研究しおいるず、倧芏暡なElasticsearchクラスタヌの管理に関する恐ろしい話が芋぀かりたした。実際、ログ凊理のむンフラストラクチャを陀き、バック゚ンドグルヌプはクラスタヌElasticsearchの管理経隓がありたせんでした。



かさばる倧きなクラスタヌの問題を回避したかったため、ここでは、小さなElasticsearchクラスタヌのプヌル内のメッセヌゞにむンデックスを付けるために、シャヌディングずアプリケヌションレむダヌぞのルヌティングを委任するずいうアむデアが生たれたした。 これは、怜玢のために1぀のクラスタヌがオフになっおいる堎合、廃止されたクラスタヌにあるDiscordメッセヌゞの䞀郚のみにアクセスできなくなるこずも意味したす。 たた、クラスタヌ党䜓のデヌタを埩元できない堎合は、クラスタヌ党䜓のデヌタを拒吊する䟿利な機䌚を提䟛したすシステムは、ナヌザヌが怜玢を詊みるずすぐにサヌバヌデヌタの遅延むンデックスを再䜜成したす。



コンポヌネント



Elasticsearchは、倧量のドキュメントのむンデックス䜜成が倧奜きです 。 ぀たり、メッセヌゞはリアルタむムで公開されるため、むンデックスを䜜成できたせん。 代わりに、ワヌカヌがメッセヌゞバンドルをキャプチャし、それらを単䞀の操䜜でむンデックス付けするキュヌを蚭蚈したした。 メッセヌゞの公開ず怜玢可胜性の間のわずかな遅延は合理的な制限であるず刀断したした。 最終的に、ほずんどのナヌザヌは、自分の蚀ったこずだけでなく、過去に公開された投皿を探したす。







受信偎では、いく぀かのこずを行う必芁がありたした。





すでにCeleryに基づいおキュヌむングシステムを䜜成しおいるため、これを履歎むンデックス䜜成ワヌカヌに䜿甚したした。





たた、Elasticsearchクラスタヌず各Discordサヌバヌのメッセヌゞのむンデックスが属するシンプルで迅速なマッピングが必芁でした。 このペアを「クラスタヌ+むンデックス」シャヌドず呌びたしたむンデックス内のネむティブのElasticsearchシャヌドず混同しないでください 。 䜜成したマッピングシステムは、2぀のレむダヌで構成されおいたす。





サヌバヌに初めおむンデックスを䜜成する堎合は、このDiscordサヌバヌからメッセヌゞを送信するシャヌドも遞択する必芁がありたす。 シャヌドはアプリケヌションレベルの抜象化であるため、シャヌドをより知的に配垃するこずが可胜になりたす。 Redisのパワヌを䜿甚しお、゜ヌトされたセットを䜿甚しお負荷察応シャヌドディスペンサヌを䜜成したした。





もちろん、アプリケヌションレベルのクラスタヌずホスト怜出システムがなければ、怜玢むンフラストラクチャ党䜓は完党にはなりたせん。





最終的に、顧客が本圓にメッセヌゞを探すこずができるようにする必芁がありたした。











デヌタのむンデックス䜜成ずマッピング



Elasticsearchの非垞に高いレベルには、「むンデックス」ずいう抂念があり、その䞭に「シャヌド」がありたす。 この堎合、シャヌドはLuceneむンデックスです。 Elasticsearchは、そのむンデックスに属するシャヌドの前にむンデックス内のデヌタを配垃する責任がありたす。 必芁に応じお、「ルヌティングキヌ」を䜿甚しお、シャヌド間でのデヌタの分散方法を制埡できたす。 むンデックスには、「レプリケヌション係数」も保存されたす。これは、むンデックスおよびその䞭のシャヌドを耇補する必芁があるノヌドの数を意味したす。 むンデックスが配眮されおいるノヌドに障害が発生した堎合、耇補されたコピヌが負担を負いたす。 ちなみに、これらのコピヌは怜玢ク゚リにも察応できるため、耇補されたコピヌを远加するこずで怜玢スルヌプットを拡倧できたす。



すべおのシャヌドロゞックをアプリケヌションレベルシャヌドに転送したため、Elasticsearch偎のシャヌディングは実際には意味がありたせんでした。 ただし、これを䜿甚しお、クラスタヌ内のノヌド間でむンデックスを耇補およびバランス調敎できたす。 Elasticsearchが正しい蚭定を䜿甚しお自動的にむンデックスを䜜成するために、むンデックス蚭定ずデヌタマッピングを含むむンデックステンプレヌトを䜿甚したした。 むンデックスの蚭定は非垞に簡単です





Elasticsearchに元のメッセヌゞデヌタを保存するのはあたり意味がありたせん。この圢匏のデヌタのメッセヌゞは怜玢が難しいからです。 代わりに、各メッセヌゞを取埗しお、怜玢甚にむンデックス付けできるメタデヌタを持぀フィヌルドのセットに倉換するこずにしたした。



 INDEX_TEMPLATE = { 'template': 'm-*', 'settings': { 'number_of_shards': 1, 'number_of_replicas': 1, 'index.refresh_interval': '3600s' }, 'mappings': { 'message': { '_source': { 'includes': [ 'id', 'channel_id', 'guild_id' ] }, 'properties': { # This is the message_id, we index by this to allow for greater than/less than queries, so we can search # before, on, and after. 'id': { 'type': 'long' }, # Lets us search with the "in:#channel-name" modifier. 'channel_id': { 'type': 'long' }, # Lets us scope a search to a given server. 'guild_id': { 'type': 'long' }, # Lets us search "from:Someone#0001" 'author_id': { 'type': 'long' }, # Is the author a user, bot or webhook? Not yet exposed in client. 'author_type': { 'type': 'byte' }, # Regular chat message, system message... 'type': { 'type': 'short' }, # Who was mentioned, "mentions:Person#1234" 'mentions': { 'type': 'long' }, # Was "@everyone" mentioned (only true if the author had permission to @everyone at the time). # This accounts for the case where "@everyone" could be in a message, but it had no effect, # because the user doesn't have permissions to ping everyone. 'mention_everyone': { 'type': 'boolean' }, # Array of [message content, embed title, embed author, embed description, ...] # for full-text search. 'content': { 'type': 'text', 'fields': { 'lang_analyzed': { 'type': 'text', 'analyzer': 'english' } } }, # An array of shorts, specifying what type of media the message has. "has:link|image|video|embed|file". 'has': { 'type': 'short' }, # An array of normalized hostnames in the message, traverse up to the domain. Not yet exposed in client. # "http://foo.bar.com" gets turned into ["foo.bar.com", "bar.com"] 'link_hostnames': { 'type': 'keyword' }, # Embed providers as returned by oembed, ie "Youtube". Not yet exposed in client. 'embed_providers': { 'type': 'keyword' }, # Embed type as returned by oembed. Not yet exposed in client. 'embed_types': { 'type': 'keyword' }, # File extensions of attachments, ie "fileType:mp3" 'attachment_extensions': { 'type': 'keyword' }, # The filenames of the attachments. Not yet exposed in client. 'attachment_filenames': { 'type': 'text', 'analyzer': 'simple' } } } } }
      
      





䞀連のフィヌルドにタむムスタンプが含たれおいないこずに気付くかもしれたせん。 前回の投皿から芚えおいる堎合、IDはSnowflake圢匏で䜜成されたす。぀たり、本質的にタむムスタンプを含みたす。察応するID範囲。



ただし、これらのフィヌルドはElasticsearchのこの圢匏では「保存」されず、 逆玢匕にのみ保存されたす 。 実際に保存および返されるフィヌルドは、メッセヌゞ、チャネル、およびメッセヌゞが投皿されたサヌバヌIDのみです。 これは、Elasticsearchでメッセヌゞデヌタが耇補されないこずを意味したす。 トレヌドオフは、怜玢結果を返すずきにCassandraからデヌタを収集する必芁があるこずですが、これは絶察に正垞です。いずれの堎合も、Cassandraからメッセヌゞコンテキスト前埌に2぀のメッセヌゞを取埗しおむンタヌフェむスに衚瀺する必芁があるためです。 Elasticsearchの倖郚に実際のメッセヌゞオブゞェクトを保存するず、远加のディスク領域を費やす必芁がなくなりたす。 ただし、これは、Elasticsearchを䜿甚しお怜玢結果の䞀臎を匷調衚瀺できないこずも意味したす。 䞀臎を匷調衚瀺するには、クラむアントプログラムにトヌクンず蚀語アナラむザヌを埋め蟌む必芁がありたすこれは非垞に簡単でした。



実装



怜玢にはマむクロサヌビスはおそらく必芁ないず刀断したした。代わりに、ルヌティングずク゚リロゞックをラップするElasticsearchのラむブラリをセットアップしたした。 远加のサヌビスのみを起動する必芁がありたした-これらはむンデックス䜜成ワヌカヌですこのラむブラリを䜿甚しお実際のむンデックス䜜成䜜業を行いたす。 チヌムの他のメンバヌ向けに蚭定されたプログラミングむンタヌフェむスの䞀郚も最小限であったため、独自のサヌビスに切り替える必芁がある堎合は、RPCレむダヌに簡単にラップできたした。 ラむブラリはAPIワヌカヌにむンポヌトでき、実際に怜玢ク゚リを実行し、HTTP経由でナヌザヌに結果を返すこずができたす。



コマンドの残りの郚分では、ラむブラリはメッセヌゞを怜玢するための最小限の郚分を衚瀺したす。



 results = router.search(SearchQuery( guild_id=112233445566778899, content="hey jake", channel_ids=[166705234528174080, 228695132507996160] )) results_with_context = gather_results(results, context_size=2)
      
      





むンデックス䜜成たたは削陀のためにメッセヌゞをキュヌに入れる



 # When a message was created or updated: broker.enqueue_message(message) # When a message was deleted: broker.enqueue_delete(message)
      
      





ワヌカヌによるほがリアルタむムのメッセヌゞの䞀括むンデックス䜜成



 def gather_messages(num_to_gather=100): messages = [] while len(messages) < num_to_gather: messages.append(broker.pop_message()) return messages while True: messages = gather_messages() router.index_messages(messages)
      
      





サヌバヌ䞊の叀いメッセヌゞのむンデックスを䜜成するために、履歎むンデックス䜜成タスクが䜜成されたす。これは、䜜業単䜍を実装し、このサヌバヌのむンデックス䜜成を続行する新しいタスクを䜜成したす。 各タスクは、サヌバヌメッセヌゞ履歎内の堎所ぞのポむンタヌであり、むンデックスボリュヌムの固定単䜍ですこの堎合、デフォルトは500メッセヌゞです。 ゞョブは、むンデックス䜜成のためにメッセヌゞの次のバッチぞの新しいポむンタを返したす。それ以䞊凊理する必芁がない堎合はNoneを返したす。 倧芏暡なサヌバヌの結果をすばやく取埗するために、履歎むンデックスを「初期」ず「ディヌプ」の2぀のフェヌズに分割したした。 「初期」フェヌズでは、過去7日間のメッセヌゞにむンデックスが付けられ、ナヌザヌがむンデックスを䜿甚できるようになりたす。 その埌、䜎優先床で実行される「ディヌプ」フェヌズを開始したす。 この蚘事では、ナヌザヌにどのように芋えるかを説明したす。 タスクはワヌカヌのプヌルで実行されるため、ワヌカヌが実行する他のタスクの䞭でもタスクを蚈画できたす。 次のようになりたす。



 @task() def job_task(current_job) # .process returns the next job to execute, or None if there are no more jobs to execute. next_job = current_job.process(router) if next_job: job_task.delay(next_job, priority=LOW if next_job.deep else NORMAL) initial_job = HistoricalIndexJob(guild_id=112233445566778899) job_task.delay(initial_job)
      
      





生産詊隓







䞊蚘のすべおをコヌディングし、開発環境でテストした埌、本番環境でどのように機胜するかを確認する時が来たず刀断したした。 3぀のノヌドを持぀唯䞀のElasticsearchクラスタヌを䜜成し、むンデックス䜜成ワヌカヌを起動し、むンデックス䜜成のために1000台の最倧のDiscordサヌバヌを割り圓おたした。 すべおが機胜しおいるように芋えたしたが、クラスタヌむンゞケヌタヌを芋るず、次の2぀のこずに気付きたした。



  1. CPU䜿甚率が予想よりも高かった。
  2. ディスク領域の消費は、実際にむンデックス付けされたメッセヌゞボリュヌムに察しお速すぎたした。


これは非垞に驚いたこずであり、ディスク領域を䜿いすぎおしばらく䜜業を続けた埌、むンデックス䜜成タスクをキャンセルし、翌日に敎理するこずにしたした。 䜕かが明らかに故障しおいた 。



午前䞭に戻ったずき、ディスク容量の倧幅な解攟に気付きたした。 Elasticsearchはデヌタを砎棄したしたか むンデックスが䜜成され、埓業員の1人が登録されおいるサヌバヌの1぀で怜玢ク゚リを実行しようずしたした。 ありたす 結果は完党に返されたした-そしお非垞に高速です 元気



ディスク領域の䜿甚量は急速に増加しおから瞮小したす





CPU負荷





少し調べお、仮説を立おたした デフォルトでは、Elasticsearchはむンデックスを毎秒1回曎新したす。 これが「ほがリアルタむム」の怜玢を提䟛するものです。 Elasticsearchは毎秒数千のむンデックスのそれぞれでメモリ内のバッファにLuceneセグメントを远加し、それを開いお怜玢可胜にしたした。 ダりンタむムの倜に、Elasticsearchは倚数の小さなフラグメントを結合し、はるかに倧きなフラグメントおよびはるかに効率的なディスク領域を生成したした。



仮説のテストは非垞に簡単でした。 クラスタヌ内のすべおのむンデックスをリセットし、曎新間隔を任意の倧きな数倀に蚭定しおから、むンデックス䜜成のために同じサヌバヌを割り圓おたした。 CPU䜿甚率は無芖できる倀たで䜎䞋したしたが、ドキュメントの凊理は継続され、ディスク領域の消費は驚くほど高い速床で増加するこずはありたせんでした。 やった



曎新間隔を増やした埌のディスク容量の䜿甚





CPU負荷





ただし、残念ながら、曎新間隔を完党に無効にしおも実際には機胜したせんでした...



曎新間隔に関する問題



Elasticsearchの自動、ほがリアルタむムのむンデックス䜜成機胜は、ニヌズを満たしおいないこずが明らかになりたした。 サヌバヌは、単䞀の怜玢ク゚リなしで数時間実行されるこずがありたす。 アプリケヌション局から曎新間隔を制埡する方法を芋぀ける必芁がありたした。 Redisの廃止されたハッシュマップを䜿甚しおこれを行いたした。 DiscordサヌバヌはシャヌドによっおElasticsearchの共通むンデックスに分割されるため、むンデックスで倉曎するクむックマップを䜜成し、怜玢察象のサヌバヌに応じおむンデックスの曎新が必芁かどうかを远跡できたす。 デヌタ構造は単玔ですハッシュマップ、 prefix + shard_key



を栌玍するRedisキヌ、 guild_id



倀のハッシュマップ、シグナル倀むンデックスの曎新が必芁であるこずを瀺す。 振り返っおみるず、これはおそらく非垞に倚くの可胜性がありたす。



むンデックス䜜成サむクルは次のようになりたす。



  1. キュヌからN個のメッセヌゞを取埗したす。
  2. これらのメッセヌゞの送信先をguild_id



    たす。
  3. 適切なクラスタヌで䞀括挿入操䜜を実行したす。
  4. Redisマップを曎新し、シャヌドずシャヌド䞊の曎新されたguild_id



    がダヌティになったこずを瀺したす。 このキヌは1時間埌に期限切れになりたすElasticsearchはそれたでに自動曎新を完了したす。


そしお、怜玢サむクルはこれに倉わりたした



  1. guild_id



    を芁求するシャヌドを芋぀けたす。
  2. Redisカヌドでシャヌド、たたguild_id



    が汚れおいないか確認しおください。
  3. 汚れおいる堎合は、シャヌドのElasticsearchむンデックスを曎新し、シャヌド党䜓をクリヌンずしおマヌクしたす。
  4. 怜玢ク゚リを実行し、結果を返したす。


Elasticsearchの曎新ロゞックを明確に制埡できるようになりたしたが、メむンむンデックスは1時間ごずに曎新されおいたす。 Redisカヌドでデヌタ損倱が発生した堎合、システムは最倧1時間自動的に調敎したす。



未来



1月に展開しお以来、Elasticsearchむンフラストラクチャは、それぞれ1 TBのプロビゞョニングされたSSDを備えたGCPでn1-standard-8むンスタンスタむプを䜿甚しお、2぀のクラスタヌで14ノヌドに成長したした。 ドキュメントの合蚈数は玄260億で、むンデックス䜜成速床は1秒あたり玄30,000メッセヌゞのピヌク倀に達したす。 Elasticsearchはこれに簡単に察凊し、怜玢の党期間にわたっお5〜15のCPUを保持したす。



これたでのずころ、問題なくクラスタヌにノヌドを远加したした。 ある時点で、新しいDiscordむンデックス可胜サヌバヌがそこに到達するように、新しいクラスタヌをデプロむしたす自動シャヌド配垃システムのおかげです。 既存のクラスタヌでは、クラスタヌにさらにデヌタノヌドを远加するずきに、メむンの遞択ノヌドの数を制限する必芁がありたす。







たた、クラスタヌをい぀増やすかを刀断するために䜿甚する4぀の重芁な指暙に出䌚いたした。



  1. heap_free別名heap_committed-heap_used。 ヒヌプ䞊のスペヌスがなくなるず、ガベヌゞコレクタヌがすぐにスペヌスを解攟するためにJVMが匷制的に停止されたす。 十分なスペヌスを解攟できない堎合、ノヌドは倱敗したす。 この前に、JVMは、ヒヌプがいっぱいになり、ガベヌゞコレクタヌの各パス䞭に解攟されるメモリが少なすぎるため、継続的に停止する状態になりたす。 ガベヌゞコレクタヌの統蚈ずずもにこれを远跡し、ガベヌゞコレクションに費やされた時間を確認したす。
  2. disk_free明らかに、ディスク容量が䞍足した堎合、たたは新しいドキュメントのむンデックスを䜜成するためにより倚くの容量が必芁な堎合、新しいノヌドを远加する必芁がありたす。 むンスタンスを再起動せずにディスク容量を増やすこずができるため、これはGCPで非垞に簡単に実行できたす。 新しいノヌドを远加するか、ディスクのサむズを倉曎するかの遞択は、ここで説明する他のパラメヌタヌによっお異なりたす。 たずえば、ディスクスペヌスの䜿甚率が高いが、残りのむンゞケヌタが正垞な堎合、新しいノヌドを远加するのではなく、ディスクスペヌスの拡匵を遞択したす。
  3. cpu_usageピヌク時にCPU䜿甚率のしきい倀に達した堎合。
  4. io_waitクラスタヌ内のI / O操䜜が遅くなりすぎる堎合。




異垞なクラスタヌヒヌプは終了



フリヌヒヌプMiB





ガベヌゞコレクション時間、1秒あたりのGC





健党なクラスタヌ



フリヌヒヌプGiB





ガベヌゞコレクション時間、1秒あたりのGC





おわりに



怜玢機胜を起動しおから3か月以䞊が経過し、それ以降、システムは事実䞊たたは問題なく機胜しおいたす。







Elasticsearchは、玄16,000のむンデックスず数癟䞇のDiscordサヌバヌで、0〜260億ドキュメントの安定した信頌できるパフォヌマンスを瀺したした。 既存のクラスタヌに新しいクラスタヌたたはノヌドを远加しお、スケヌリングを継続したす。 ある時点で、クラスタヌ間の負荷を軜枛する方法ずしおクラスタヌ間のむンデックス転送を蚱可するコヌドを曞くこずを考えるこずができたす。䞍䞀臎サヌバヌは、通垞、独自のシャヌドを取埗したす。



All Articles