[PHP] 再版

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBオリジナル
2016-06-23 14:35:311186ブラウズ

データベース シャーディングに関する非常に役立つ記事を見つけました。その内容は次のとおりです。

この記事には、データベース シャーディングに関するプレゼンテーションのスライドが付属しています。シャーディングは、Netlog で使用しているデータベースの水平スケーリングに使用される技術です。高性能、スケーラビリティ、MySQL、php、キャッシュ、パーティショニング、Sphinx、フェデレーション、または Netlog に興味がある場合は、続きを読んでください ...

このプレゼンテーションは、ブリュッセルで開催された FOSDEM 2009 の 2 日目に行われました。 FOSDEM は、約 5,000 人のハッカーが参加するオープンソース ソフトウェアに関する年次カンファレンスです。私は、Kris Buytaert と Lenz Grimmer から、MySQL Dev Room で講演するよう招待されました。この講演は、BarcampGent 2 で行った以前の講演に基づいています。

概要 私は誰ですか?ネットログとは何ですか?データベース システムのスケーリングの歴史 限界に達する シャーディングの基本 シャーディング スキームの影響 既存のソリューション 実装 問題への取り組み 最終的な考え スライド リソース

私は誰ですか?

現在、私は Netlog のリード Web 開発者として、php、MySQL、その他のフロントエンド テクノロジを使用してソーシャル ネットワークの機能を開発および改善しています。もう3年もこれをやっています。この文書では、私が DBA でもシステム管理者でもないことを言及することが重要です。そのため、私はアプリケーション/開発者の観点からデータベースのスケーリングの問題に取り組んでいます。
もちろん、このプレゼンテーションで紹介されているソリューションは次の結果です。 Netlog の開発および IT サービス部門は多大な努力を払っています。

Netlog とは何ですか?

Netlog に馴染みのない方のために、私たちが誰で何であるか、特にユーザーベースと成長の観点から私たちがどこから来たのかについて、簡単に概要を説明するのが最善です。これにより、スケーラビリティに関して物事を大局的に見ることができます。現在、アクティブなメンバーは 4,000 万人を超え、月間 5,000 万人を超えるユニーク訪問者がいます。これにより、1 か月あたり最大 50 億以上のページ ビューと、1 か月あたり 60 億のオンライン時間が加算されます。私たちは 26 の言語と 30 以上の国で活動しており、最も活動的な 5 か国はイタリア、ベルギー、トルコ、スイス、ドイツです。 (会社についての詳細情報に興味がある場合は、「概要ページ」を確認してアカウントにサインアップしてください。)

データベース統計の観点から見ると、この種の使用法では特に、保存するデータが大量になります(例: . nl.netlog.com の友人数は 1 億人以上)。このアプリケーションの性質 (対話が多い) により、非常に書き込み負荷の高いアプリになります (読み取りと書き込みの比率は約 1.4 対 1)。シャーディング前の典型的なデータベースには、ピークタイム (nl.netlog.com の現地時間で 15 時間から 22 時間) 中に 1 秒あたり平均 3000 以上のクエリがありました。
もちろん、これらの要件はすべてのアプリケーションで満たされる必要はありません。また、アプリケーションが異なれば、異なるスケーリング戦略が必要になります。それにもかかわらず、7 年前に大学生のプロジェクトとして始めたとき、私たちは今日のような状況になるとは思っていなかった(または望んでいなかった)でしょう。私たちは、スケーラビリティについてさらに洞察を深め、いくつかの貴重な提案を共有できると確信しています。
以下は、昨年の成長のグラフです。

この成長により、当然ながら、いくつかのパフォーマンスの問題が発生しました。私たちにとってボトルネックになることが多いのはデータベース層です。これは、この層が Web スタック内でステートレスではない唯一の層であるためです。リレーショナル データベース システムの相互作用と依存関係により、水平方向のスケーリングがあまり目立たなくなります。

Netlog は、php、MySQL、Apache、Debian、Memcached、Sphinx、Lighttpd、Squid などのオープン ソース ソフトウェア上で構築され、実行されます。もっと。データベースをスケーリングするための当社のソリューションも、これらのテクノロジーに基づいて構築されています。だからこそ、私たちは私たちのストーリーを文書化し、共有することで何かをお返ししたいと考えています。

データベース システムのスケーリングの歴史

あらゆる趣味のプロジェクトと同様に、Netlog (当時は asl.to、「インターネット パスポート」) も 7 年以上前に始まりました。共有ホスティング環境の (おそらくは仮想) サーバー上に単一のデータベース インスタンスが存在します。トラフィックが増大し、負荷が増加したため、別のサーバーに移行し、最終的には MySQL と php の分割セットアップ (データベース セットアップ 1) になりました。

データベース セットアップ 1: マスター (W)

次に取るべきステップは、「マスター」データベースの「スレーブ」として構成された新しいデータベースを導入することでした。単一の MySQL サーバーではアプリケーションからのすべてのリクエストに対応できないため、読み取りトラフィックと書き込みトラフィックを別々のホストに分散しました。スレーブのセットアップは、MySQL のレプリケーション機能を使用することで非常に簡単です。マスター/スレーブ構成では、すべての書き込みクエリ (INSERT/UPDATE/DELETE) がマスター データベースに送信され、すべて (またはほとんど) の読み取りクエリが 1 つ以上のスレーブ データベースに送信されます。通常、スレーブ データベースは、マスターの binlog ファイルを読み取り、すべての書き込みクエリを再実行することでマスターとの同期を維持します (データベース セットアップ 2)。
このセットアップで取り組むべき問題には、DBA チームの複雑さの増加が含まれます (複数のサーバーを監視するため)、および「レプリケーションラグ」の可能性。スレーブがマスター データベースと同期しなくなる可能性があり (読み取りクエリのロック、ダウンタイム、劣悪なハードウェアなどにより)、その結果、スレーブ データベースのクエリ時に古い結果が返されることがあります。リアルタイムの結果が必要な状況では、データの整合性を確保するためにマスター データベースへの読み取りクエリを強制的に実行する必要がある状況もあります。そうしないと、(可能性のある)競合状態による悲惨な結果が待っています。

データベース セットアップ 2: マスター (W) + スレーブ (R)

マスター/スレーブのセットアップの良いアイデアは、次のとおりです。奴隷の役割を紹介します。通常、すべての検索、バックエンド、および/または統計関連のクエリを「検索スレーブ」に割り当てます。この種のユースケースではリアルタイムの結果が必要になることはほとんどないため、レプリケーションの遅延はあまり気にしません。このシステムは、読み取り負荷の高いアプリケーションに特に適しています。サーバー負荷が 100%、読み取り/書き込み比率が 4/1 の場合、マスター サーバーは 80% の時間で SELECT クエリを実行することになります。この設定に単一のスレーブを追加すると、SELECT 容量が 2 倍になり、2 倍の量の SELECT クエリを処理できるようになります。

ただし、書き込みが多いアプリケーション、またはマスター データベースが書き込みクエリを実行している状況では、スレーブは約 90% の時間マスターとの同期で忙しいため、90% の確率で、別のスレーブを追加してもさらに 10% の容量しか獲得できません。ここでの問題は、読み取りトラフィックのみを分散し、書き込みトラフィックを分散しないことです。実際には、書き込みトラフィックを複製しています。マスター/スレーブ設定の効率には限界があるという事実を考慮すると、データの同一コピーが大量に作成されることになります。

この時点で、書き込みトラフィックの分散について考え始める必要があります。アプリケーションが書き込みトラフィックに大きく依存するほど、これに早く対処する必要があります。シンプルでわかりやすい最初のステップは、アプリケーションを機能レベルでパーティショニングし始めることです。このプロセスは垂直パーティショニングと呼ばれます。
アプリケーションでは、多かれ少なかれ別のサーバーに存在できる機能 (およびその MySQL テーブル) を識別します。無関係で JOIN を必要としないテーブルがある場合は、それらを別のサーバーに配置してみてはいかがでしょうか。 Netlog の場合、ユーザーのアイテム (写真、ブログ、ビデオ、投票など) に関する詳細を含むテーブルのほとんどを別のサーバーに配置することができました。いくつかの重要なテーブル (ユーザー ID やニックネームなどを含むテーブルなど) をすべての個別のパーティションにレプリケートすることで、必要に応じてそれらのテーブルにアクセスして結合することができます。

データベース セットアップ 3 では、次の例をご覧ください。友達やメッセージ関連のクエリのためにマスター データベースを煩わす必要はもうありません。これらの機能の書き込みおよび読み取りクエリは、その機能を担当するデータベースに直接送信されます。これらの機能固有のホストは依然として「TOP」マスター データベースのスレーブとして構成されています。そうすることで、これらの非常に重要なテーブルのいくつかを複製できるからです。

ここでの良いアイデアは、OLAP ユースケース(データ ウェアハウス)用にテーブルを分割することです。 ) OLTP のユースケース (フロントエンド、リアルタイム機能) ですが、これらはいずれにせよ、異なるアプローチが必要であり、速度と稼働時間に関して異なるニーズがあるためです。


データベース セットアップ 3: 垂直パーティショニング

セットアップ 2 で行ったこと、垂直に分割された各機能に対して簡単に繰り返すことができます。いずれかのデータベースがトラフィック要件に対応できない場合は、そのデータベースのスレーブを構成し、読み取りおよび書き込みトラフィックを分散します。このようにして、システム全体でいくつかのテーブルを複製するデータベースのツリーと、適切なクエリを適切なデータベースに分散する役割を担うデータベース クラスを作成します (データベース セットアップ 4)。

データベース セットアップ 4: 垂直パーティション化 / レプリケーション ツリー

必要に応じて、アプリケーションをさらに深く掘り下げて、分割する機能をさらに見つけることができます。残念ながら、機能を分割するたびに、必要な JOIN 機能が再び失われるため、これはますます難しくなります。また、場合によっては、単一のテーブルが大きくなりすぎて、単一のデータベース ホストで簡単に管理できる範囲を超えてしまうことがあります。この単一データベース上の単一テーブルの制限に達した最初の機能は、ユーザー間の友情を含むテーブルでした。このテーブルは急速に増大したため、テーブルにどれだけ多くのスレーブを追加しても、この機能を担当するホストのパフォーマンスと稼働時間は保証されなくなりました。もちろん、信じられないほど非常識なハードウェア設定のボックスを購入することで、いつでもスケールアウトではなくスケールアップすることを選択できますが、あると便利であることは別として、高価であり、成長を続けるとやはり制限に達してしまいます。
このスケーリングへのアプローチには制限 (データベース設定 5) があり、その制限に達した場合は、スケーリング戦略を再考する必要があります。

データベース設定 5: 制限に達する

それでは、次は何でしょうか?

今私たちに何ができるでしょうか?垂直分割は非常に役に立ちましたが、行き詰まっています。マスター間のレプリケーションは役に立ちますか?クラスターのセットアップは役に立ちますか?あまり;これらのシステムは、高可用性と高パフォーマンスを実現するように設計されています。これらはデータを複製することで機能し、書き込みトラフィックを分散するためのソリューションは提供しません。
キャッシュについてはどうですか?ああ、キャッシングのことをどうして忘れることができるでしょうか。もちろん、キャッシュはデータベース サーバーの負荷を軽減するのに非常に役立ちます。 (キャッシュを行わないと、前述の読み取り/書き込み比率はまったく異なります。) しかし、同じ問題が残ります。キャッシュはデータベースの読み取りトラフィックを軽減しますが、書き込みトラフィックに対する解決策は提供しません。データベースが「1040 接続が多すぎます」エラーのみを返すようになると、キャッシュによって遅延が発生します。しかし、キャッシュ戦略がどれほど優れていても、ある時点で訪問者の指標がおかしくなるのを防ぐことはできません。

聖杯!

テーブルを垂直方向に分割することはできませんが、水平方向に簡単に分割できますか?テーブルのシャーディングとは、そのテーブルのレコードのいくつかのグループを (物理的かどうかに関係なく) 別々の場所に配置することです。データを任意のサイズの断片/フラグメント/シャードに分割し、それらを複数のデータベース ホストに分散します。 1 億を超えるすべての友情レコードを 1 台の大型高価なマシンに置く代わりに、10 台の小型で安価なマシンにそれぞれ 1,000 万の友情レコードを置きます。

シャーディング (水平パーティショニング) は、1996 年に MMO ですでに積極的に使用されていた用語です。 (大規模マルチプレイヤー オンライン) ゲームの世界。シャーディングに関する情報を検索している場合は、これが Flickr、LiveJournal、Sun、Netlog などで使用されている技術であることがわかります。

モジュロ パーティショニング スキームを使用して 10 台のサーバー上で写真テーブルをシャーディングします

画像内上では、写真テーブルを 10 個の異なるサーバーに分割する例を示しています。データの送信先やデータにアクセスできる場所を決定するために使用されるアルゴリズムは、次のとおりです。その写真の所有者のユーザー ID に対するモジュロ関数。写真の所有者がわかっていれば、写真のその他の詳細やコメントなどを取得する場所がわかります。
別の簡単な例を見てみましょう。

使用例: 単純なブログ サイト。ブログ投稿を含むテーブルがあり、次の列があります: postid、title、message、dateadd、authorid authorid は、users テーブルへの FK (外部キー) です ブログ投稿テーブルをシャードします (著者は非常に生産的なライターであるため) ) 2 つのデータベースにわたって。偶数の authorid を持つ著者からの投稿はデータベース 1 に移動します。不均一な authorid を持つ著者からの投稿はデータベース 2 に移動します。クエリ:「ID 26 の著者からのブログ メッセージを教えてください。」

非シャード環境では、アプリケーションのどこかに次のようなコードがあります:

PLAIN TEXT

PHP:

$db = DB:: getInstance ( );  // データベース インスタンスを取得します

$db-> prepare ( "SELECT タイトル、メッセージ FROM BLOG_MESSAGES WHERE userid = {userID}" );  // クエリを準備します

$db-> assignInt ( 'userID', $userID );  // クエリ変数を割り当てます

$db->実行する ( );  // クエリを実行します

$results = $db-> getResults();  // 結果の配列を取得します

この例では、最初にデータベースに接続するデータベース クラスのインスタンスを取得します。次に、クエリを準備し、変数 (ここでは作成者の ID $userID) を割り当て、クエリを実行して結果セットを取得します。作成者の $userID に基づいてシャーディングを導入する場合、このクエリを実行する必要があるデータベースは、その $userID (偶数であるかどうかに関係なく) に依存します。これに対処するアプローチとしては、「どのユーザーがどのデータベース上にあるか」のロジックをデータベース クラスに組み込み、その $userID をそのクラスに渡すことが考えられます。最終的には次のような結果になる可能性があります。$userID を DB::getInstance() 関数に渡し、$userID % 2:

PLAIN TEXT

の結果に基づいて接続の詳細を含むオブジェクトを返します。 PHP:

$db = DB:: getInstance ( $userID );  // このユーザーに固有のデータベース インスタンスを取得します

$db-> prepare ( "SELECT タイトル、メッセージ FROM BLOG_MESSAGES WHERE userid = {userID}" );

$db-> assignInt ( 'userID', $userID );

$db->実行する ( );

$results = $db-> getResults();

$userID をパラメータとして DB クラスに渡す代わりに、クラスに提供する準備されたクエリから解析することもできますし、どの DB 接続が必要かを計算することもできます。レベルは異なりますが、重要な概念は同じです。クエリを実行する場所を知るために、データベース クラスに追加情報を渡す必要があります。これは、シャーディングを成功させるために満たさなければならない最も困難な要件の 1 つです。

データをシャーディングするにはどうすればよいですか?

データを分割したいとき、2 つの質問が頭に浮かびます。データをどこに配置するかを決定するために、データのどのプロパティ (テーブルのどの列) を使用すればよいでしょうか?そしてアルゴリズムはどうなるでしょうか?最初のキーを「シャーディング/パーティショニング キー」、2 番目のキーを「シャーディング/パーティショニング スキーム」と呼びましょう。

どのシャーディング キーを使用するかは、基本的にアプリケーションの性質や方法によって決定されます。あなたのデータにアクセスしたいでしょう。ブログの例では、作成者ごとにブログ メッセージの概要を表示する場合、作成者の $userID をシャード化することをお勧めします。サイトのナビゲーションが月ごとまたはカテゴリーごとのアーカイブであるとすると、公開日または $categoryID でシャーディングする方が賢明かもしれません。 (アプリケーションで両方のアプローチが必要な場合は、両方のキーでシャーディングを使用してデュアル システムをセットアップすることも良い考えかもしれません。)

対応するシャードを見つけるために「シャード キー」を使用してできることは、基本的に 4 つのカテゴリに分類されます。

垂直パーティショニング: フィーチャ/テーブル レベルでのデータの分割は、一種のシャーディングとみなすことができます。「シャード キー」は、たとえば、テーブル名。前述したように、このシャーディング方法は実装が非常に簡単で、アプリケーション全体への影響は比較的低いです。範囲ベースのパーティショニング: 範囲ベースのパーティショニングでは、データを複数の範囲に従って分割します。 2000 年以前とそれ以前のブログ投稿はデータベース 1 に移動し、新世紀以降のブログ投稿はもう 1 つのデータベースに移動します。このアプローチは、ログやその他の時間ベースのデータの場合に一般的です。範囲ベースのパーティショニングの他の例には、郵便番号の最初の番号に基づいてユーザーを統合することが含まれます。キーまたはハッシュ ベースのパーティショニング: 写真の例で使用されているモジュロ関数は、キーのハッシュまたはその他の数学関数に基づいてデータをパーティショニングする方法です。モジュロ関数の簡単な例では、モジュロ演算にシャードの数を使用できます。もちろん、シャードの数を変更すると、データのバランスが再調整されることになります。これは遅いプロセスである可能性があります。これを解決する方法は、より一貫性のあるハッシュ メカニズムを使用するか、シャードの元の数を正しく選択して「仮想シャード」を使用することです。ディレクトリベースのパーティショニング: 最後で最も柔軟なスキームは、特定のシャード ID にマップされたシャード キーの可能な値のそれぞれをディレクトリ検索する方法です。これにより、ディレクトリを変更することで、特定のシャード キー (特定のユーザーなど) のすべてのデータをシャード間で移動することが可能になります。一方、ディレクトリはオーバーヘッドを増大させたり、SPOF (単一障害点) になったりする可能性があります。

ブログの例で示したように、適切なデータベース サーバーでクエリを実際に実行する前に、「シャード キー」を知る必要があります。これは、クエリとアプリケーションの性質によってデータの分割方法が決まることを意味します。要求される柔軟性、予測される増加、およびデータの性質も、どのスキームを使用するかを決定する際に役立つ他の要素になります。
また、データがデータベース全体で最適にバランスされ、各データベースへの負荷が分散されるようにキーとスキームを選択することも必要です。

データをシャーディングした最終結果は、書き込みクエリを異なる独立したデータベースに分散し、それぞれが持つより多くの、しかしより安価なマシンのシステムになるはずです。データの量が少なくなるため、小さなテーブルでクエリをより速く処理できます。
成功すると、再びオンラインになります。ユーザーはこれを高く評価し、DBA も満足しています。セットアップ内の各マシンの負荷が減り、クラッシュが減り、夜通しの喧騒がなくなるからです。 (テーブルが小さいほどクエリが高速になります。これにはメンテナンスや ALTER クエリが含まれ、これも DBA と開発者の満足度を維持するのに役立ちます。)

聖杯があるなら、キラーラビットがあります

写真は The Rocketeer より。 (クリエイティブ・コモンズ・ライセンス)

もちろん、シャーディングはすべての問題を簡単に解決してくれるデータベースの水平スケーリングの特効薬ではありません。アプリケーションにシャーディングを導入するには、多大な開発コストがかかります。その影響の一部を以下に示します:

クロスシャード SQL クエリなし: 異なるシャードに存在する (可能性がある) データをフェッチしたい場合、SQL レベルの JOIN を使用してこれを行うことはできません。 $userID でシャードすると、同じユーザーからのデータとの JOIN が可能になります。ただし、シャード上の複数のユーザーから結果を取得すると、これはおそらく不完全な結果セットになります。ここで重要なのは、クロスシャード クエリが必要ないようにアプリケーションを設計することです。他の解決策としては、アプリケーション レベルでの並列クエリの導入が考えられますが、当然、データベース トラフィックを分散するという側面が失われます。ユースケースによっては、これが問題になるかどうかが決まります (たとえば、バックエンド目的の並列クエリは、思われるほどクレイジーではありません)。
その他のオプションとしては、データを非正規化し、必要な情報の一部を複数のシャードの複数のテーブルで利用できるようにすることが考えられます。コメント テーブル内のコメントの作成者のニックネームを複製すると、そのニックネームに対して追加のクエリを実行する必要がなくなります。 (コメントを取得するシャードは、作成者のニックネームが見つかるシャードとは異なる場合があります。)
シャーディングしたいゲストブック メッセージを含むテーブルがあるが、両方のリストを取得する必要がある場合メッセージ投稿者のユーザー ID と同様に、ゲストブックの所有者ユーザー ID ごとにメッセージを作成する場合は、メッセージ (または参照) を所有者と投稿者のシャードの両方に配置することで非正規化できます。データの一貫性と参照整合性: 同じ「テーブル」のデータが複数のスタンドアロン データベース サーバーに存在するため、外部キーを暗黙指定したり、グローバルに一意な auto_increment 値を指定したり、シャード間トランザクションを実行したりすることができなくなります。これは、アプリケーション レベルで整合性を強制することに対処する必要があり、最終的には開発時間のかなりの時間をチェックと修正ルーチンに費やすことになる可能性があることを意味します。 整合性の問題を軽減する 1 つの方法は、2 つのサーバーでデータベース トランザクションを開始し、両方のサーバーが稼働していることがわかった場合にのみそれぞれのトランザクションをコミットすることで、データベース間でトランザクションを偽装することです。トランザクションの 2 つのコミットの間には依然として遅延が発生します (これにより再び問題が発生する可能性があります) が、データを健全な状態に保つことにまた一歩近づいています。シャードのバランス: $userID でシャーディングを行う場合、パワー ユーザーと非アクティブ ユーザーの違いにより、シャーディング システムのバランスが崩れる可能性があります。セットアップ内のすべてのハードウェアが同じ仕様であるとは限りません。さらにシャードを追加した場合、セットアップのバランスをどのように再調整できるでしょうか? 
すべてのデータベースの負荷を均等に保つには、ある程度の努力が必要になる場合があります。この時点では、パーティション分割スキームの選択が非常に重要です。ディレクトリベースのアプローチは最も柔軟ですが、オーバーヘッドが発生し、SPOF が発生する可能性があります。ネットワークの準備はできていますか? アプリケーション サーバーは 1 回のリクエストで複数の異なるサーバーにデータを取得して保存する可能性があるため、ネットワーク トポロジと構成設定を準備する必要があります。ページ全体をレンダリングするために接続を開いたままにしますか?それとも、クエリのたびにデータベースへの接続を閉じますか?バックアップ戦略は異なります。実際のデータはさまざまなサーバー上で断片化されており、バックアップ戦略に影響を与えます。既存のソリューション?
現時点では、Alexa のランキングによると、Netlog は世界で 67 番目にアクセス数の多い Web サイトです。これは、おそらく他にも少なくとも 66 の Web サイトが私たちと同様の問題に直面していることを意味します。最も人気のある 20 の Web サイトのうち 16 は MySQL を利用しています。つまり、私たちは決して一人ではありませんね?

シャーディングとスケーリングのデータベース システムを実装している、またはそれに何らかの形で関連している既存のテクノロジーをいくつか見てみましょう。 Netlog にとって興味深いものになる可能性があります。

MySQL Cluster は、同様の問題を解決すると考えられるテクノロジーの 1 つです。実際のところ、データベース クラスターは高可用性とパフォーマンスの点で役立ちますが、書き込みを分散するように設計されていません。

MySQL パーティショニングは、MySQL のもう 1 つの比較的新しい機能であり、大きなテーブルをより小さなテーブルに水平方向に分割することができます。よりパフォーマンスの高いアイテム。ただし、これらのパーティションの物理ストレージは単一のデータベース サーバーに制限されているため、単一のテーブルが単一のデータベース サーバーの容量を超えた場合には関係ありません。

HSCALE と Spock Proxy はどちらも MySQL Proxy 上に構築されています。データのシャーディングに役立つ他の 2 つのプロジェクト。 MySQL Proxy では、プロキシに指示するための追加のプログラミング言語として LUA が導入されています(たとえば、このクエリに適切なシャードを見つけるなど)。当時、私たちはシャー​​ディングのためのソリューションを必要としていたのですが、これらのプロジェクトはいずれも、私たちが望んでいた方法でディレクトリベースのシャーディングをサポートしていないようでした。

HiveDB は Java の MySQL 用シャーディング フレームワークであり、Java 仮想マシンを必要とし、現在 php インターフェースは初期段階にあります。 Java ソリューションであるため、私たちにとってはあまり興味がありません。なぜなら、私たちは専門家であるテクノロジーを好み、アプリケーションは php で書かれているからです。

MySQL や php 関連ではない他のテクノロジーには、HyperTable (HQL)、HBase、BigTable などがあります。 、Hibernate Shards (*震え*)、SQLAlchemy (Python 用)、Oracle RAC など...MySQL 用の memcached SQL 関数またはストレージ エンジンも、ここで言及できる関連プロジェクトです。

これらのプロジェクトはどれも実際にはありません。私たちの要件に一致しているようでした。しかし、それらは正確には何ですか?

ハードウェア部門にとって柔軟です。

私たちは成長を予測しており、シャーディング システムが柔軟であることを望んでいます。トラフィックが増加することがわかっているので、シャードを迅速に追加できる必要があります。データ量の増加に伴い、ハードウェアもそれに比例して増加することが求められます。このため、ディレクトリベースのパーティション分割スキームを選択します。大規模な書き換えはありません。
まったく新しいデータベース層や互換性のない抽象化層を導入することはできません。現在と同様にデータベース クラスを使用し続け、実際にその量のスケーリングが必要な機能に対してのみシャーディングを実装したいと考えています。そのため、私たちは、既存のものに基づいて構築され、段階的な実装が可能なソリューションを選択しました。また、データを物理的にシャーディングせずにシャーディング API を使用して、開発部門と IT 部門がいつ自分の仕事を実行するかを独自に決定できるようにしたいと考えていました。複数のシャーディング キーのサポート。
データの大部分はおそらく $userID でシャーディングされますが、他のキーやシャーディング スキームも実装できるようにシステムを柔軟にしたいと考えています。わかりやすい。
開発者全員がスケーラビリティとパフォーマンスについてすべてを知っていることを期待することはできません。たとえそうであったとしても、シャード環境でデータにアクセスして保存するための API は、データを透過的にする必要があるため、パフォーマンスを気にする必要がなく、本当に楽しいこと、つまり機能の開発と改善に集中できます。
したがって、API がアプリケーションの残りの部分で簡単に使用できる php API であることが最善です。 Netlog でのシャーディングの実装
それで、何を思いついたでしょうか? 100% php で書かれた社内ソリューション。実装は主に、アプリケーション ロジックとデータベース クラスの間のミドルウェアです。完全なキャッシュ層が組み込まれています (memcached を使用)。私たちのサイトは主にプロファイルを中心に構築されているため、ほとんどのデータは $userID でシャード化されています。

このシステムでは、以下のシャード スキームを使用しています。このスキームでは、シャードは、シャーディング システム内のテーブルのプレフィックス。いくつかのシャード (テーブルのグループ) が「シャード データベース」に一緒に配置され、それらのデータベース (インスタンスではない) のいくつかが特定の「シャード データベース ホスト」上にあります。

したがって、ホストには複数のシャードがあります。これにより、シャード全体、またはデータベース全体を移動して、プール内のすべてのサーバーのバランスをとることができ、データベース内のシャードの量とサーバー上のシャードの量を調整して適切な値を見つけることができます。テーブル サイズとそのサーバーで開いているファイルのバランスを調整します。
このシステムを運用環境で使用し始めたとき、40 台のホストに 4000 個のシャードがありました。現在、プールには 80 個のホストがあります。

シャードはデータベースに存在し、データベースはホスト上に存在します

PHP 側から見ると、実装は 2 つの部分に分かれています。 1 つ目は、システムおよび検索システムへのシャード、データベース、ホストの追加、編集、削除を可能にする一連の管理および保守関連機能です。 2 番目のクラス シリーズは、データベース アクセス層とキャッシュ層で構成される API を提供します。

シャーディング管理ディレクトリ

ディレクトリまたはルックアップ システムは、実際には、シャード キーを $shardID に変換する単一の MySQL テーブルです。通常、これらは $userID と $shardID の組み合わせです。これは、レコードの量が Netlog 上のユーザーの数である単一のテーブルです。そのテーブルに ID のみが保存されているため、まだ管理可能であり、マスター間のレプリケーション、memcached、および/またはクラスターのセットアップを通じて非常に高いパフォーマンスを維持できます。

その隣には、$shardID を次のように変換する一連の構成ファイルがあります。実際のデータベース接続の詳細。これらの構成ファイルを使用すると、特定のシャードに読み取りおよび/または書き込みクエリに使用できないフラグを付けることができます。 (これは、メンテナンスの目的やホストがダウンした場合に興味深いものです。)
シャード テーブル API

注: 私たちが実装した API では、次に説明する典型的なケース以上の処理が可能になり、また、いくつかのキャッシュ モードと戦略も可能になります。アプリケーションの性質と使用法に基づきます。

シャード システム内のほとんどのレコードとデータには、$userID フィールドと $itemID フィールドの両方があります。この $itemID は、写真に関連するテーブルの $photoID、またはビデオに関連するテーブルの $videoID です。 (イメージがわかると思います...) $itemID は、auto_increment 値、または外部キー、および $userID と結合された主キーの一部である場合があります。したがって、各 $itemID は $userID ごとに一意であり、グローバルに一意ではありません。これは、分散システムで強制するのが難しいためです。

(MySQL の結合キーで auto_increment 値を使用する場合、この値は常に MAX( )+1 値であり、内部的に保存された値ではありません。そのため、新しい項目を追加し、それを再度削除して、別のレコードを挿入すると、その最後の挿入の auto_increment 値は、以前に挿入および削除されたレコードと同じになります。覚えておいてください ...)

シャーディング システムに保存されているデータにアクセスしたい場合は、通常、テーブル + $userID の組み合わせを表すオブジェクトを作成します。この API は、Web アプリで通常必要となるすべての基本的な CRUD (作成/読み取り/更新/削除) 機能を提供します。特定の作成者によるブログ メッセージを取得する最初の例に戻ると、次のシナリオになります。

クエリ: ID 26 の作成者からのブログ メッセージを教えてください。

ユーザー 26 はどこですか?
ユーザー 26 はシャード 5 上にあります。シャード 5 上にあります。ユーザー 26 のすべての $blogID ($itemID) を教えてください。
そのユーザーの $blogID は次のとおりです: array(10,12,30);シャード 5 上。ユーザー 26 の項目 array(10,12,30) に関する詳細をすべて教えてください。
それらの項目は次のとおりです: array(array('title' => "foo", 'message' => "bar"), array ('タイトル' => "牛乳", 'メッセージ' => "牛"));

このプロセスでは、ステップ 1 が別のサーバー (ディレクトリ データベース) で実行され、次にステップ 2 と 3 (シャード 5) が実行されます。ステップ 2 と 3 は 1 つのクエリに簡単に組み合わせることができますが、そうしないのには理由があります。これについては、キャッシュ戦略について説明するときに説明します。
ステップ 2 の背後にある機能により、WHERE を追加できることに注意することが重要です。 、ORDER 句と LIMIT 句を使用すると、必要なレコードのみを必要な順序でフェッチできます。

(ここで挙げた例とここでの MySQL の使用方法では、リレーショナル データベースやより単純なデータベース システムを使用することもできますが、この API をバイパスする場合には、シャード化されているかどうかにかかわらず、すべてのデータを同じ形式にすることはそれほど悪いことではありません。現在も MySQL を使用することで発生する可能性のあるオーバーヘッドがボトルネックになっているわけではありませんが、改善を検討できる点であることは確かです。)

シャード管理

シャーディング システム内のサーバーのバランスを保つために、次のようないくつかのパラメーターを監視しています。ユーザーの数、テーブルとデータベースのファイルサイズ、読み取りおよび書き込みクエリの量、CPU 負荷など。これらの統計に基づいて、シャードを新しいサーバーまたは別のサーバーに移動するか、あるいはあるシャードから別のシャードにユーザーを移動するかを決定できます。
単一ユーザーの移動操作は、そのユーザーがダウンタイムを経験することなく、完全に透過的かつオンラインで実行できます。これは、書き込みクエリを監視することによって行われます。ユーザーの移動操作を開始すると、ユーザーのデータを宛先シャードにコピーし始めます。そのユーザーに対して書き込みクエリが実行されると、移動プロセスが中止され、クリーンアップされて、後で再試行されます。したがって、ユーザー自身がその時点でアクティブでない場合、または他のユーザーがユーザーと対話していない場合 (シャーディング システムの機能の場合)、ユーザーの移動は成功します。
完全なシャードまたはデータベースの移動at a time はサーバーの負荷を分散するためのより抜本的なアプローチであり、ある程度のダウンタイムが必要ですが、シャードを読み取り専用として構成するか、切り替え中にマスター/スレーブ設定を使用するなどすることで最小限に抑えることができます

このシステム固有のものですつまり、1 つのデータベースがダウンした場合、そのデータベース上のユーザー (またはユーザーとのやり取り) のみが影響を受けます。シャードごとにクラスター、マスター/マスター設定、またはマスター/スレーブ設定を導入することで、シャードの可用性を向上させることができますが、シャード データベースにかかる負荷が比較的小さいため、シャード データベース サーバーに問題が発生する可能性はほとんどありません。

問題への取り組み

シャーディングの困難は、Memcached、並列処理、Sphinx の 3 つのテクノロジを実装することで部分的に解決されます。

Memcached

"memcached は、高性能の分散メモリ オブジェクト キャッシング システムです。本質的には汎用ですが、データベースの負荷を軽減して動的 Web アプリケーションを高速化するために使用することを目的としています。アプリケーション ロジックとシャード データベースへの SQL クエリの間にメモリ キャッシュ レイヤーを配置することで、結果をはるかに高速に取得できるようになります。このキャッシュ層により、以前は SQL レベルでは不可能だと考えられていたクロスシャード データのフェッチの一部を行うこともできます。

memcached に馴染みのない人のために、以下は Memcached の非常にシンプルで必要最低限​​の使用例です。キャッシュからキーと値のペアを取得しようとし、見つからない場合は値を計算してシステムに保存します。後続の関数呼び出しでは、データベースにストレスを与えることなく、キャッシュされた値が即座に返されます。

PLAIN TEXT

PHP:

function isObamaPresident ( )

$memcache = new Memcache ( );

$result = $memcache-> get ( 'イソバマップ社長' );  // fetch

if ( $result === false )

// データベースに重い処理を実行します

$db = DB:: getInstance ( );

$votes = $db-> prepare ( "SELECT COUNT(*) FROM VOTES WHERE vote = 'OBAMA'" )->実行する ( );

$result = ( $votes> (USA_CITIZEN_COUNT / 2 ) ) ?  「確かにそうですよ!」 : 'いいえ。';  // まあ、理想的には

$memcache-> set ( 'isobamapresident', $result, 0 );

$result を返します。

Memcached はアプリケーション コードのさまざまな方法といくつかのレベルで使用されており、シャーディングには主なものとして次のものが含まれます。

$userID から $shardID への各呼び出しがキャッシュされます。このマッピングが変更されるたびにキャッシュを新しい値で更新し、TTL (Time To Live) なしでキャッシュに保存できるため、このキャッシュのヒット率は約 100% です。シャードテーブル内の各レコードは配列としてキャッシュできます。キャッシュのキーは通常、テーブル名 + $userID + $itemID です。 「アイテム」を更新または挿入するたびに、指定された値をキャッシュ層に保存することもできるため、理論上のヒット率は再び 100% になります。シャーディング システムの「list」および「count」クエリの結果は、テーブル名 + $userID (+ WHERE/ORDER/LIMIT 句) およびリビジョン番号をキャッシュのキーとする $itemID または数値の配列としてキャッシュされます。 。

「リスト」キャッシュと「カウント」キャッシュのリビジョン番号自体は、テーブル名と $userID の組み合わせごとに一意のキャッシュされた番号です。これらの数値は、「list」キャッシュと「count」キャッシュのキーで使用され、そのテーブル名と $userID の組み合わせに対する書き込みクエリが実行されるたびにバンプされます。実際、リビジョン番号は、更新されたとき、またはキャッシュで見つからなかったときに「time()」に設定されるタイムスタンプです。このようにして、最新の更新以降、キャッシュからフェッチされたすべてのデータが常に正しい結果になることを保証できます。
これを念頭に置いて、もう一度ブログの例に戻ると、次のシナリオが得られます。

クエリ: Give me the ID 26 の著者からのブログメッセージ

ユーザー 26 はどこですか?
このクエリの結果は、ほとんど常に memcached で利用できます。シャード 5 上。ユーザー 26 のすべての $blogID ($itemID) を教えてください。
このクエリの結果は、ユーザー 26 の BLOGS テーブルの更新が最後に行われてから以前にリクエストされていた場合、キャッシュ内に見つかります。シャード 5 上。ユーザー 26 の項目 array(10,12,30) に関する詳細をすべて教えてください。
このタイプのキャッシュのヒット率が高いため、このクエリの結果はほとんどの場合キャッシュ内で見つかります。複数のアイテムを取得するときは、Memcached との間のトラフィックを最適化するために必ずマルチ取得リクエストを実行します。

このキャッシュ戦略のおかげで、最初は愚かなアイデアに思えた 2 つの別々のクエリ (リスト クエリ + 詳細クエリ) のパフォーマンスが向上しました。これを 2 つのクエリに分割せず、すべての詳細 (メッセージ + タイトル + ...) を含むアイテムのリストを Memcached にキャッシュしていなかったら、レコードのプロパティのコピーをさらに多く保存することになります。

「リスト」キャッシュに追加した興味深いパフォーマンスの調整です。コメントの最初のページ (1 ~ 20) をリクエストするとします。実際に最初の 100 項目をクエリし、その 100 のリストをキャッシュに保存して、その結果のリクエストされたスライスのみを返します。 2 番目のページ (21 ~ 40) へのその後の呼び出しは、常にキャッシュからフェッチされる可能性があります。したがって、データベースから要求するウィンドウは、アプリによって要求されるウィンドウとは異なります。

キャッシュの競合状態がデータの一貫性にとって問題になる可能性がある機能、または各レコードを個別にキャッシュすることがオーバーヘッドとなるユースケース (例: レコードは 1 種類のクエリにのみ挿入および選択されて使用されるため)、またはJOIN やより高度な SQL クエリを実行する場合、異なるキャッシュ モードや異なる API 呼び出しを使用します。

この API 全体にはかなりの PHP 処理が必要ですが、以前はすべて処理されていましたが、現在はアプリケーション レベルで実行しています。 MySQL サーバー自体によって最適化されます。ただし、php レベルのスケールでのメモリ使用量と処理時間はデータベースよりもはるかに優れているため、これはそれほど問題ではありません。

並列処理

ほとんどのデータはおそらくメモリ。友達の友達リストを取得する場合、その方法の 1 つは、自分の友達をループしてその友達を取得し、その結果を処理して、友達は知っているが、あなたはまだ知らない人のリストを取得することです。 。 
これに必要な実際のデータベース クエリの量は少量ですが、それでもクエリはシンプルで超高速です。それぞれ数百人の友人を持つユーザーに対してこれを処理すると、問題が発生し始めます。このため、特定の大きなタスクを、並列処理できるいくつかの小さなタスクに分割するシステムを実装しました。
この php での並列処理は、php サーバー ファームに対していくつかの Web リクエストを実行することによって行われ、各リクエストは、それぞれのタスクのごく一部を処理します。タスク。実際には、すべてを一度に実行するよりも、10 個の小さなタスクを同時に処理する方が高速です。タスクを分割して結果を結合するために必要な追加の Web リクエストのオーバーヘッドと CPU サイクルは、利益に比べれば無関係です。

Sphinx の使用

シャード データでは不可能になるその他の一般的なクエリは、概要クエリです。すべてのユーザーがアップロードした最新の写真をすべてまとめたページが必要だとします。ユーザーの写真が 100 のデータベースに分散されている場合は、それぞれのデータベースにクエリを実行して、それらの結果をすべて処理する必要があります。いくつかの機能に対してこれを行うことは正当化されないため、ほとんどの「探索」ページ (コミュニティーのコンテンツを参照して発見するページ) は別のシステムから提供されています。
Sphinx は無料のオープンソースの SQL 全文検索です。エンジン。私たちは、平均的な入力フィールド + 検索ボタン以上の検索エンジンとして使用しています。実際、その日に最も視聴されたビデオのリストは、Sphinx からのクエリ結果である場合もあります。これらの概要ページのデータのほとんどは、データがリアルタイムでなくても問題ありません。したがって、各シャードのデータから定期的に構築され、結合されたインデックスからこれらの結果を取得することが可能です。

Sphinx の使用方法 (およびそこに到達する方法) の完全な概要については、以下を参照することをお勧めします。私の同僚の Jayme Rotsaert のプレゼンテーション「Netlog での検索のスケーリングと最適化」。彼は Sphinx の使用に多大な努力を払ってきました。

最後の感想

シャーディングについて言えることが 2 つだけあるとしたら、それは次の 2 つの引用でしょう。

「必要がないならやらないでください!」 (37signals.com) 「シャードは早めに、そして頻繁に!」 (startuplessonslearned.blogspot.com)

2 つの相反することを言っているように聞こえますか?まあ、はい、いいえです。

シャーディングはサーバー ファームのセットアップとメンテナンスを確実に複雑にするため、アーキテクチャにシャーディングを導入したくないでしょう。監視すべきことはさらに多く、問題が発生する可能性もあります。 
現在、あらゆるセットアップ、テクノロジー、ユースケースに適した、すぐに使えるソリューションはありません。既存のツールのサポートは貧弱で、それを可能にするためにかなりのカスタム コードを構築する必要がありました。
データを分割するため、リレーショナル データベースで気に入ってきた機能の一部が失われます。
よりシンプルなソリューション (より良いハードウェア、より多くのハードウェア、サーバーの微調整とチューニング、垂直パーティショニング、SQL クエリの最適化など) が開発コストを削減する必要があるのに、なぜシャーディングに多大な労力を投資する必要があるのでしょうか?

一方で、訪問者の統計が本当に屋根から風が吹き始めます。それは良い方向です。結局のところ、それはうまくいきました。
シャーディングの実装で最も難しい部分は、データ層へのアクセスごとに関連する「シャード キー」がわかるようにアプリケーションを (再) 構造化し、(再) 設計することでした。ブログ メッセージの詳細をクエリし、ブログ メッセージが作成者のユーザー ID でシャーディングされている場合、ブログのタイトルにアクセス/編集するには、そのユーザー ID を知っておく必要があります。
これを念頭に置いてアプリケーションを設計します (「使用可能なキーは何ですか?」シャーディングに使用できるスキームは?」) は、シャーディングが必要になったときに、より簡単かつ段階的にシャーディングを実装するのに間違いなく役立ちます。

現在の設定では、すべてがシャード化されているわけではありません。しかし、それは問題ではありません。私たちは、このスケーリング戦略を必要とする機能に重点を置き、時期尚早の最適化には時間を費やしません。
現在では、高価なマシンに費やすお金は減り、利用可能で処理できるシステムが整っています。

プレゼンテーション

Jurriaan Persyn のプレゼンテーションをもっと見る。
(タグ: fosdem2009 fosdem)

Netlog www.netlog.com/go/developer www.37signals.com/svn/posts/1509-mr-moore-gets-to-punt- の優れた開発および IT サービス チームにリソースを提供します。オンシャーディング www.addsimplicity.com/adding_simplicity_an_engi/2008/08/shard-lessons.html www.scribd.com/doc/2592098/DVPmysqlucFederation-at-Flickr-Doing-Billions-of-Queries-Per-Day startlessonslearned.blogspot .com/2009/01/sharding-for-startups.html www.codefutures.com/weblog/database-sharding www.25hoursaday.com/weblog/2009/01/16/BuildingScalableDatabasesProsAndConsOfVariousDatabaseShardingSchemes.aspx highscalability.com dev.mysql.com /doc/refman/5.1/en/partitioning.html www.hibernate.org/414.html en.wikipedia.org/wiki/SQLAlchemy spockproxy.sourceforge.net www.scribd.com/doc/3865300/Scaling-Web-Sites -by-Sharding-and-Replication oracle2mysql.wordpress.com/2007/08/23/scale-out-notes-on-sharding-unique-keys-foreign-keys www.flickr.com/photos/kt 高性能 MySQL、第 2 版 (O'Reilly)

さらにご質問やご意見がございましたら、お気軽に jurriaan@netlog.com までご連絡ください。私のブログ (www.jurriaanpersyn.com) と Netlog 開発者ブログ (www.netlog.com/go/) を購読してください。開発者/ブログ。 

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。