私の名前はアンドレイ・フロロフです。 私はAllodsチームの主任プログラマーであり、サーバーチームとして働いています。 私の開発経験はほぼ10年ですが、2009年10月にしかゲームに参加しませんでした。2010年3月から3年以上チームに所属しています。 Skyforgeサーバーとデータベースに何らかの形で接続されているすべてのものを扱っています。 この記事では、AllodsとSkyforgeの例を使用して、オンラインゲームのデータベースについて説明します。
読みたくない場合は、記事を最後までスクロールして、Game Developers Conferenceのレポートのビデオをご覧ください。 ポストに残っている人にはボーナスがあります-NoSQL-JSONハイブリッドとリレーショナルデータモデルに関するストーリーの形式でレポートに重要な追加。
進化
ゲームベースは典型的なOLTPシステムです(多くの小規模で短いトランザクション)。 ただし、ゲームでのデータベースの使用は、Web、銀行、その他の企業での使用とは多少異なります。 まず、これはゲームのデータモデルが銀行よりもはるかに複雑であるという事実によるものです。 第二に、ゲーム開発者のほとんどのプログラマーは、C ++の厳しい世界から出てきて、彼らにひげとバイナリパッケージングを愛していました。 キャラクターをディスクに保存する必要がある場合、絶対にそれらのすべて、彼らはそれをファイルにシリアル化したい最初のもの。 それが、Allods Onlineでのすべての始まりです。 プログラマーはファイルストレージを作成しましたが、すぐにそれをよく考え、MySQLの下ですべてを書き直しました。 プロジェクトは正常に開始され、人々はプレイし、経験が蓄積されました。
Allodsにあったもの:
- Java、MySQL
- 破片。 そして、それらのそれぞれは、オンラインである特定の限られた数のプレーヤーのために設計されました
- この数のプレーヤーは、1秒あたり約200のトランザクションを生成
- データベースで動作するサービスはモノトレードされました。 それは非常に多くのトランザクションに十分でした
数年後、Skyforgeが始まりました。 Skyforgeにはまったく異なる要件があったため、データベースを操作するアプローチを再考する必要がありました。
これらは要件です:
- シャードはもうありません。 ひとつの大きな統一世界があります
- 私たちはサーバーをオンラインの100,000人のプレイヤーと数えています。
- 推定によると、これらのプレーヤーは1秒あたり7000を超えるトランザクションを発行する必要があります
- まだJavaで記述していますが、MySQLでPostgreSQLに切り替えました
さて、舞台裏を見て、技術思想の進化的発展の過程で私たちがどうなったかを見てみましょう。
建築
まず、サーバーが配布されます。 ベースも配布されます。 第二に、サーバーのアーキテクチャはサービス指向です。 これは、すべてがメッセージを交換するサービスの形式で提示されることを意味します。 ゲームには多数のサービスがありますが、データベースに直接アクセスできるのはトランザクション実行サービスのみです。 一般的に言えば、私の芸術的能力を最大限に活用すると、次のようになります。
写真に描かれているすべての要素が複数のコピーに存在することを考慮するだけで十分です。
- ゲームメカニクスサーバーにはアバターがあります。 アバターは、プレーヤーを表すJavaオブジェクトです。 データベースサーバーの数倍のゲームメカニクスサーバーがあります。
- すべてのサーバーは、特別なインターフェースを介してデータベースと通信します。 このインターフェイスには何百ものメソッドが含まれ、ゲームメカニックスプログラマーからベースの分散された本質を隠し、1つのメソッド-1つのトランザクションというわかりやすいコントラクトを提供します。 これは、何百ものメソッドを持つ1つのクラスではなく、10個のメソッドを持つ1つのクラスであり、それぞれが10個のメソッドを持つ小さな「サブインターフェース」を提供することを理解する必要があります。 操作の一種の「パック」。
- データベースサービス(データベース)は、到着した操作を実行し、結果をデータベースに書き込みます。 データベースサービスとデータベース自体は、ネットワーク上で余分な時間を無駄にしないために、同じ物理サーバーに配置されます。
キャッシュとしてのアバター
この単純なスキームには1つの重要なポイントがあります。 私たちのアバターはゲームの仕組みのパフォーマンスに必要ですが、副作用として、実際にはデータベース上のキャッシュです。 「このプレーヤーのアイテムを見せて」や「ヴァシリーのアバターはどこにいるの?」などのすべてのリクエストは、このアバターによって処理されます。 プレイヤーがゲームに参加するとき、私たちは彼のアバターをアップロードし、プレイヤーがオンラインである限り彼は生きます。 この簡単なトリックにより、ほとんどの読み取り要求、さらには書き込み要求の一部をデータベースから削除できます。
すべてのプレーヤーデータを2つのカテゴリに分類します。
- 重要ではないデータ、その損失はプレイヤーが生き残ることができます。 これらには、マップ上の位置、健康レベルなどが含まれます。 アバターからそのようなデータを「蓄積」し、定期的に、またゲームを離れるとデータベースにドロップします。
- 重要なデータ。その損失はプレーヤーにとって苦痛です。 これらには、アイテム、お金、クエストなどが含まれます。 このデータでは、すべてがはるかに複雑です。 プレーヤーがこのデータを失わないように努めています。 彼は多くの時間と労力を費やしました。 したがって、これらは同期的にデータベースに保存する必要があります。 重要なデータの保存がデータベースの主な負担となります。
それでは、データベース内の重要なデータの状態と、別のサーバー上にあるアバターをどのように同期させるのでしょうか? 実際にはすべてが非常に簡単です。 件名を取得するスキームを検討してください。
- ゲームメカニクスサーバーは、データベースサービス「take item XXX」にリクエストを送信します。
- データベースサーバーは、必要なチェックを実行します(バッグに十分なスペースがあるか、このアイテムを「グレージング」する必要があるかなど)。 その後、アバターバッグの更新された状態をベースに保存します。
- 保存が成功した場合にのみ、アバターにバッグのステータスの更新が送信されます。 アバターは、ゲームクライアントに更新を送信します。 その結果、プレーヤーは、アイテムがベースに安全に保管されている場合にのみ、アイテムを持っていることがわかります。
PostgreSQL
Skyforgeでは、以下の理由を組み合わせてMySQLを廃止しました。
- MySQLでは、すべての機能がさまざまなストレージエンジンに分散されています。 InnoDBにあるもの、MyISAMにあるもの、MEMORYエンジンにあるもの。 この非常に複雑な生活。
- MySQLでは、分散トランザクションメカニズムが壊れており、実際に使用したいと考えていました。 MySQL開発者は、6番目のバージョンにのみ修正することを約束しましたが、これはまだ計画に含まれていません。
- MySQLでは、グループコミットのメカニズムが壊れていました。 バージョン5.5で修復されたため、この項目はもはや関係ありません。
- MySQLには実際にはかなりの数のバグ、奇妙に機能する機能、非常に限られたクエリオプティマイザーがあります。
PostgreSQLはこれらすべての問題を解決し、代わりに自動バキュームの問題のみを提供しました。 NoSQLデータベースを使用しないことにしました。 データの一貫性に対する要件は非常に高く、世界中の単一のNoSQLデータベースが、あるアバターから別のアバターにアイテムを一貫してトランザクションで転送することはできません。 この場合の最終的な一貫性は、私たちにはあまり適していませんでした。 それはゲーム体験を大いに台無しにします。
ハイブリッドデータスキーマ
PostgreSQLを使用しているという事実は、データをリレーショナル形式で保存する必要があるという意味ではありません。 リレーショナルデータベースはキーと値のストレージとして使用できます
完全にリレーショナルなモデルは私たちには適していない、なぜなら いくつかのパフォーマンスのボトルネックが含まれています。 たとえば、プレーヤーがいて、彼にはクエストがあります。 プレイヤーは何百ものクエストを完了することができ、ゲームに入るとすべてを表示する必要があります。 リレーショナルモデルを使用する場合、データベースから数百の行を返すように要求する必要があり、これには時間がかかります。 一方、非リレーショナルモデルには多くの欠点があります。定数の欠如、データを部分的に更新できないなどです。
さまざまな実験の後、いくつかのフィールドに非リレーショナルデータが含まれる一連のリレーショナルモデルに満足していることに同意しました。 AllodsとSkyforgeで最近まで、データバイナリの一部をシリアル化し、テーブルのフィールドとして保存しました。 しかし、わずか3週間前にようやくすべてを理解し、JSON挿入を使用してリレーショナルスキーマにデータを保存しました。
次のようになります。
# select * from avatar limit 1;
id | 144115188075857124
position |
{"point":{"x":7402.2793,"y":6080.2197,"z":51.42402},"yaw":0.0,"map":"id:132646944","isLocal":false,"isValid":true}
death_descriptor | {"deathTime":-1,"respawnTime":-1,"sparkReturnDelay":-1,"recentDeathTimesArray":[]}
health | 1250
mana_descriptor | {"mana":{"8":300}}
avatar_client_info | \x
character_race_class_res_id | 26209282
character_sex_res_id | 550995
last_online_time | 1371814800726
JSON. , PostgreSQL 9.3 JSON. , — PostgreSQL MongoDB PostgreSQL.
Virtual shards
, . ID , , .
ID : — , — ID .
long id = <shard_id> <account_id>
. . . -, virtual shards . , , 15 . , 5 . 215 310. , .
SSD
SSD. RAID-. , , , fsync.
. , 200 , SSD. , SSD ó . SSD WAL PostgreSQL, , . SSD !
, .
- PostgreSQL — .
- SSD .
- -.
- , . .
, .
, , . . , master—slave , , .
,
.
C .
- .