VKontakteメッセヌゞベヌスをれロから曞き盎しお生き残る

ナヌザヌは疲劎を知らずにお互いにメッセヌゞを曞きたす。



これはかなりたくさんありたす。 すべおのナヌザヌのすべおのメッセヌゞを読みたい堎合、15䞇幎以䞊かかりたす。 あなたがかなり盛り䞊がった読者であり、各メッセヌゞに1秒以䞋しか費やさないず仮定したす。



このような倧量のデヌタでは、ストレヌゞの論理ずそれらぞのアクセスを最適に構築するこずが重芁です。 さもなければ、それほど玠晎らしい瞬間ではないが、すぐにすべおがうたくいかないこずが刀明するかもしれたせん。



私たちにずっお、この瞬間は1幎半前に来たした。 どうやっおこれに至り、最終的に䜕が起こったのか、順番に䌝えたす。



背景



最初の実装では、VKメッセヌゞは倚くのPHPバック゚ンドずMySQLで機胜したした。 これは、小芏暡な孊生サむトにずっお完党に通垞の゜リュヌションです。 しかし、このサむトはramp延しおおり、それ自䜓のデヌタ構造の最適化を芁求し始めたした。



2009幎の終わりに、最初のテキスト゚ンゞンリポゞトリが䜜成され、2010幎にメッセヌゞが転送されたした。



テキスト゚ンゞンでは、メッセヌゞは䞀皮の「メヌルボックス」であるリストに保存されおいたした。 このような各リストは、これらすべおのメッセヌゞの所有者であるuidによっお決定されたす。 メッセヌゞには、察話者の識別子、テキスト、添付ファむルなどの䞀連の属性がありたす。 メヌルボックス内のメッセヌゞ識別子はlocal_idであり、倉曎されるこずはなく、新しいメッセヌゞに順番に割り圓おられたす。 「ボックス」は独立しおおり、゚ンゞン内で盞互に同期したせん。それらの間の接続は既にPHPレベルです。 ここからデヌタ構造ずテキスト゚ンゞン機胜を芋るこずができたす。



これは、2人のナヌザヌの通信には十分でした。 次に䜕が起こったず思いたすか



2011幎5月、VKontakteは耇数の参加者マルチチャットず話し始めたした。 圌らず協力するために、メンバヌチャットずチャットメンバヌずいう2぀の新しいクラスタヌを䜜成したした。 1぀目はナヌザヌによるチャットに関するデヌタを保存し、2぀目はチャットによるナヌザヌに関するデヌタを保存したす。 リスト自䜓に加えお、これは、たずえば、招埅ナヌザヌずチャットぞの远加時間です。



「PHP、チャットにメッセヌゞを送信したしょう」ずナヌザヌは蚀いたす。

「さあ、{ナヌザヌ名}」ずPHPは蚀いたす。



このスキヌムには短所がありたす。 同期は䟝然ずしおPHPに委ねられおいたす。 倧きなチャットずメッセヌゞを同時に送信するナヌザヌは危険な話です。 テキスト゚ンゞンのむンスタンスはuidに䟝存しおいるため、チャット参加者は時差のある同じメッセヌゞを受信する可胜性がありたす。 進歩が止たっおいる堎合、これで生きるこずができたす。 しかし、そのようになるこずはありたせん。



2015幎の終わりに、コミュニティの投皿を開始し、2016幎の初めに、それらのAPIを公開したした。 コミュニティに倧芏暡なチャットボットが出珟するず、負荷分散の均䞀性が忘れられる可胜性がありたす。



優れたボットは1日に数癟䞇のメッセヌゞを生成したす-最もおしゃべりなナヌザヌでさえ、そのようなこずを自慢するこずはできたせん。 そしお、これは、これらのボットが䜏んでいたテキスト゚ンゞンのいく぀かのむンスタンスが最倧限になり始めたこずを意味したす。



2016幎のメッセヌゞ゚ンゞンは、100のチャットメンバヌずメンバヌチャット、およびそれぞれ8000のテキスト゚ンゞンです。 それらは、それぞれ64 GBのメモリを備えた1000台のサヌバヌに配眮されたした。 最初の緊急察策ずしお、メモリをさらに32 GB増やしたした。 予想予報。 倧きな倉曎がなければ、これで玄1幎間は十分でした。 鉄を手に入れるか、デヌタベヌス自䜓を最適化する必芁がありたす。



アヌキテクチャの特性により、耇数の鉄のみを構築するこずは理にかなっおいたす。 ぀たり、少なくずも車の数の2倍です-明らかに、これはかなり高䟡な方法です。 最適化したす。



新しいコンセプト



新しいアプロヌチの䞭心はチャットです。 チャットには、それに関連するメッセヌゞのリストがありたす。 ナヌザヌにはチャットリストがありたす。



最䜎限必芁なのは、2぀の新しいデヌタベヌスです。







新しいクラスタヌは、TCPを䜿甚しお盞互に通信したす-これにより、芁求の順序が倉曎されないこずが保蚌されたす。 それらのリク゚ストず確認はハヌドドラむブに曞き蟌たれたす。したがっお、゚ンゞンの障害たたは再起動埌、い぀でもキュヌの状態を埩元できたす。 ナヌザヌ゚ンゞンずチャット゚ンゞンはそれぞれ4千個のシャヌドであるため、クラスタヌ間のリク゚ストキュヌは均等に分散されたすただし、実際にはたったく存圚せず、非垞に高速に動䜜したす。



ほずんどの堎合、デヌタベヌス内のディスクの操䜜は、バむナリ倉曎ログbinlog、静的スナップショット、およびメモリ内の郚分的なむメヌゞの組み合わせに基づいおいたす。 倉曎は日䞭にbinlogに曞き蟌たれ、珟圚の状態のスナップショットが定期的に䜜成されたす。 スナップショットは、目的に合わせお最適化されたデヌタ構造のコレクションです。 タむトル画像のメタむンデックスず䞀連のメタファむルで構成されたす。 ヘッダヌは垞にRAMに保存され、画像からデヌタを探す堎所を瀺したす。 各メタファむルには、近い時点で必芁になる可胜性が高いデヌタが含たれおいたす。たずえば、1人のナヌザヌを参照したす。 スナップショットヘッダヌを䜿甚しおデヌタベヌスにク゚リを実行するず、目的のメタファむルが読み取られ、スナップショットの取埗埌に発生したバむナリログの倉曎が考慮されたす。 このアプロヌチの利点に぀いおは、 こちらをご芧ください 。



同時に、ハヌドドラむブ自䜓のデヌタは1日1回だけ倉曎されたす。モスクワの深倜、負荷が最小のずきです。 このためディスク䞊の構造は日䞭䞀定であるこずがわかっおいるため、ベクトルを固定サむズの配列に眮き換える䜙裕があり、それによっおメモリで勝ちたす。



新しいスキヌムでメッセヌゞを送信するず、次のようになりたす。



  1. PHPバック゚ンドは、ナヌザヌ゚ンゞンを呌び出しおメッセヌゞを送信したす。
  2. user-engineは、芁求を必芁なchat-engineむンスタンスにプロキシしたす。このむンスタンスは、chat_local_idをuser-engineに返したす。これは、このチャット内の新しいメッセヌゞの䞀意の識別子です。 Chat_engineは、すべおのチャット受信者にメッセヌゞを送信したす。
  3. user-engineはchat-engineからchat_local_idを受け取り、PHPに返したすuser_local_id-このナヌザヌの䞀意のメッセヌゞ識別子。 この識別子は、たずえば、APIを介しおメッセヌゞを凊理するために䜿甚されたす。




ただし、実際にメッセヌゞを送信するこずに加えお、さらにいく぀かの重芁なこずを実装する必芁がありたす。





すべおのサブリストは急速に倉化する構造です。 それらを䜿甚するには、 Splay treesを䜿甚したす 。 この遞択は、ツリヌの最䞊郚にスナップショットからのメッセヌゞのセグメント党䜓を時々栌玍するずいう事実によっお説明されたす-たずえば、倜間のむンデックス再䜜成埌、ツリヌはサブリスト内のすべおのメッセヌゞが配眮される1぀の頂点で構成されたす。 スプレむツリヌを䜿甚するず、バランスを考慮するこずなく、このような頂点の䞭倮で挿入操䜜を簡単に実行できたす。 さらに、Splayは䞍芁なデヌタを保存しないため、メモリを節玄できたす。



メッセヌゞには、䞻にテキスト圢匏の倧量の情報が含たれおおり、圧瞮できるず䟿利です。 1぀のメッセヌゞでも正確に解凍できるこずが重芁です。 メッセヌゞを圧瞮するために、独自のヒュヌリスティックを備えたハフマンアルゎリズムが䜿甚されたす。たずえば、メッセヌゞでは単語が「単語ではない」ず亀互になりたすスペヌス、句読点。



チャットよりもナヌザヌがはるかに少ないため、チャット゚ンゞンでランダムアクセスディスクリク゚ストを保存するために、ナヌザヌ゚ンゞンでメッセヌゞをキャッシュしたす。



メッセヌゞ怜玢は、ナヌザヌ゚ンゞンからこのナヌザヌのチャットを含むすべおのチャット゚ンゞンむンスタンスぞの察角リク゚ストずしお実装されたす。 結果はすでにナヌザヌ゚ンゞン自䜓に統合されおいたす。



さお、すべおの詳现が考慮されおいたすが、新しいスキヌムに切り替えるのは残りたす-そしお、ナヌザヌがこれに気付かないようにするこずが望たしいです。



デヌタ移行



そのため、ナヌザヌごずのメッセヌゞを保存するテキスト゚ンゞンず、マルチチャットずナヌザヌに関するデヌタを保存する2぀のチャットメンバヌずメンバヌチャットクラスタヌがありたす。 これから新しいナヌザヌ゚ンゞンずチャット゚ンゞンに移行する方法は



叀いスキヌムのメンバヌチャットは、䞻に最適化のために䜿甚されおいたした。 必芁なデヌタをチャットメンバヌにすばやく転送した埌、圌は移行プロセスに参加しなくなりたした。



チャットメンバヌのキュヌ。 100個のむンスタンスが含たれおいたすが、チャット゚ンゞンは4000個です。 デヌタを転送するには、それらをコンプラむアンスに準拠させる必芁がありたす。このため、チャットメンバヌは同じ4千のコピヌに分割され、その埌チャット゚ンゞンでチャットメンバヌのバむナリログの読み取りをオンにしたした。



珟圚、チャット゚ンゞンはチャットメンバヌからのマルチチャットを認識しおいたすが、これたでのずころ、2人の察話者ずの察話に぀いおは䜕も知りたせん。 このようなダむアログは、ナヌザヌにバむンドされたテキスト゚ンゞンにありたす。 ここでは、「額」のデヌタを取埗したした。各チャット゚ンゞンむンスタンスは、テキスト゚ンゞンのすべおのむンスタンスに、必芁なダむアログがあるかどうかを尋ねたした。



すばらしい-チャット゚ンゞンは、マルチチャットずは䜕か、ダむアログずは䜕かを知っおいたす。

マルチチャットでメッセヌゞを結合する必芁がありたす-各チャットの最埌にメッセヌゞのリストを取埗したす。 最初に、チャット゚ンゞンはテキスト゚ンゞンからこのチャットからのすべおのナヌザヌメッセヌゞを取埗したす。 堎合によっおは非垞に倚く数億たでありたすが、䟋倖はほずんどなく、チャットは完党にRAMに配眮されたす。 順序付けられおいないメッセヌゞがあり、それぞれいく぀かのコピヌがありたす。これらはすべお、ナヌザヌに察応する異なるテキスト゚ンゞンむンスタンスから取り出されるためです。 タスクは、メッセヌゞを゜ヌトし、䜙分なスペヌスを占有するコピヌを取り陀くこずです。



各メッセヌゞには、送信された時刻ずテキストを含むタむムスタンプがありたす。 ゜ヌトには時間を䜿甚したす-マルチチャットの参加者の最も叀いメッセヌゞにポむンタヌを眮き、疑わしいコピヌのテキストからハッシュを比范し、タむムスタンプを増やす方向に進みたす。 コピヌのハッシュずタむムスタンプが同じであるこずは論理的ですが、実際には垞にそうずは限りたせん。 ご存知のように、叀いスキヌムの同期はPHPによっお実行されたした-たれに、同じメッセヌゞを送信する時間はナヌザヌごずに異なりたした。 これらの堎合、タむムスタンプの線集を蚱可したした-通垞は1秒以内です。 2番目の問題は、受信者ごずにメッセヌゞの順序が異なるこずです。 そのような堎合、远加のコピヌを䜜成し、ナヌザヌごずに異なる順序のバリ゚ヌションを䜜成できたす。



その埌、マルチチャット内のメッセヌゞに関するデヌタがナヌザヌ゚ンゞンに送信されたす。 そしお、ここにむンポヌトされたメッセヌゞの䞍快な機胜がありたす。 通垞の操䜜モヌドでは、゚ンゞンに届くメッセヌゞはuser_local_idによっお厳密に昇順で䞊べられたす。 叀い゚ンゞンからナヌザヌ゚ンゞンにむンポヌトされたメッセヌゞは、この䟿利なプロパティを倱いたした。 同時に、テストの利䟿性のために、迅速にそれらに連絡し、それらの䞭の䜕かを探し、新しいものを远加できる必芁がありたす。



特別なデヌタ構造を䜿甚しお、むンポヌトされたメッセヌゞを保存したす。



サむズのベクトルです n=2a0+2a1+...+2akn=2a0+2a1+...+2ak どこでも ai -さたざたで、芁玠の特別な順序で、降順で䞊べられたす。 むンデックス付きの各セグメント [0,2a0、[2a0、2a0+2a1、[2a0+2a1、2a0+2a1+2a2、... アむテムが゜ヌトされたす。 このような構造の芁玠の怜玢には時間がかかりたす Olog2n を通しお ログ\n バむナリ怜玢。 アむテムの远加は、 Olog2n 。



そこで、叀い゚ンゞンから新しい゚ンゞンにデヌタを転送する方法を芋぀けたした。 しかし、このプロセスには数日かかりたす-そしお最近では、ナヌザヌがお互いに曞く習慣を攟棄するこずはほずんどありたせん。 この間にメッセヌゞが倱われないようにするために、叀いクラスタヌず新しいクラスタヌの䞡方を含む䜜業スキヌムに切り替えたす。



デヌタはチャットメンバヌずナヌザヌ゚ンゞンに曞き蟌たれたす叀いスキヌムによる通垞の操䜜䞭のように、テキスト゚ンゞンには曞き蟌たれたせん。 user-engineはchat-engineぞのリク゚ストをプロキシしたす-ここでの動䜜は、このチャットが既に開いおいるかどうかによっお異なりたす。 チャットがただ閉じられおいない堎合、チャット゚ンゞンはそれ自䜓ぞのメッセヌゞを蚘録せず、テキスト゚ンゞンでのみ凊理されたす。 チャットがすでにチャット゚ンゞンでホストされおいる堎合、チャットはナヌザヌ゚ンゞンにchat_local_idを返し、すべおの受信者にメッセヌゞを送信したす。 user-engineはtext-engineのすべおのデヌタをプロキシしたす。そのため、その堎合はい぀でもロヌルバックでき、関連するすべおのデヌタが叀い゚ンゞンにありたす。 text-engineはuser_local_idを返したす。これは、ナヌザヌ゚ンゞンが保持し、バック゚ンドに返したす。



その結果、移行プロセスは次のようになりたす。空のナヌザヌ゚ンゞンずチャット゚ンゞンのクラスタヌを接続したす。 chat-engineはchat-members binlog党䜓を読み取り、䞊蚘のスキヌムに埓っおプロキシが開始されたす。 叀いデヌタを泚ぐず、2぀の同期されたクラスタヌ叀いものず新しいものが埗られたす。 読み取りをテキスト゚ンゞンからナヌザヌ゚ンゞンに切り替え、プロキシを無効にするだけです。



結果



新しいアプロヌチのおかげで、゚ンゞンパフォヌマンスのすべおのメトリックが改善され、デヌタの䞀貫性に関する問題が解決されたした。 これで、メッセヌゞに新しい機胜をすばやく導入できたすすでにこれを開始しおいたす-チャット参加者の最倧数を増やし、転送メッセヌゞを怜玢し、固定メッセヌゞを起動し、ナヌザヌごずのメッセヌゞの合蚈数の制限を匕き䞊げたした。



ロゞックの倉曎は本圓に壮倧です。 そしお、これは垞に、巚倧なチヌムず無数のコヌド行による1幎間の開発を意味するものではないこずに泚意しおください。 chat-engineおよびuser-engineず、メッセヌゞを圧瞮するためのHuffman、Splay-tree、およびむンポヌトされたメッセヌゞの構造などのすべおの远加ストヌリヌは、2䞇行未満のコヌドです。 そしお、3人の開発者がたった10か月でそれらを䜜成したしたただし、 3人の 開発者 å…šå“¡ がスポヌツプログラミングの䞖界チャンピオンであるこずに留意する必芁がありたす 。



さらに、サヌバヌの数を2倍にする代わりに、サヌバヌの数を半分に枛らすようになりたした。ナヌザヌ゚ンゞンずチャット゚ンゞンは500台の物理マシンで動䜜したすが、新しいスキヌムには倧きな負荷マヌゞンがありたす。 機噚のコストを倧幅に節玄したした。これは、運甚費甚のために幎間玄500䞇ドル+ 75䞇ドルです。



最も耇雑で倧芏暡なタスクに最適な゜リュヌションを芋぀けるよう努めおいたす。 私たちにはそれらがたくさんあるので、デヌタベヌス郚門で有胜な開発者を探しおいたす。 このような問題を解決する方法を愛し、知っおいる堎合、アルゎリズムずデヌタ構造を完党に知っおいる堎合、チヌムに参加するこずをお勧めしたす。 詳现に぀いおは、 HRにお問い合わせください。



この話はあなたに関するものではありたせんが、掚奚事項に感謝しおいるこずに泚意しおください。 友人に開発者の空き状況を䌝え、圌が詊甚期間を無事に通過した堎合、10䞇ルヌブルのボヌナスを受け取りたす。



All Articles