web-dev-qa-db-ja.com

100%ACIDに準拠する必要があるが、永続性の遅延を許容できるクエリを最適化する

前書き

私はC++で取引プログラムを書いています。このプログラムには、ユーザーバランスとオーダーブックが含まれます。オーダーブックは基本的に売買注文のセットです。注文、たとえば買い注文を送信するとき、マッチングエンジンは、その価格以下の売り注文があるかどうかを検索します。その場合、取引が発生します。取引は小さい方の注文の数量を差し引き、残りの数量は次の買い手/売り手の注文帳に追加されます。

コンテキスト

ユーザーが注文を送信すると、C++プログラムは次のようになります。

  1. データベースを読み取り、ユーザーに十分なバランスがあるかどうかを確認します。
  2. (プログラム内の)メモリの下取りを実行します
  3. データベースを変更します。1回のトランザクションで、残高を読み取り、オーダーブックを読み取り、変更して永続化します。+データベースにログエントリ(2エントリ)を書き込みます。

ご覧のとおり、トレーディングプログラムの場合、原子性と一貫性に対する許容度はゼロです。そうでなければ、二重支払いが発生します。

ただし、データベース上の1つのレイヤーのみでキャッシュを許容できます。これにより、(プログラミングを通じて)一貫性とアトミック性が確保されますが、永続性は延期されます。

プログラムにPostgreSQLを使用しています。また、データベースへのすべてのアクセスにインデックスを使用しています。インデックス作成アルゴリズムをデフォルトから変更すると、パフォーマンスが低下するだけでした。そこには正規化がほとんどありません。私が行った唯一の正規化は、balancesテーブルをusersテーブルから分離することです。他のすべては分離不可能です。

問題

とても遅いです。 Intel 4930k(8コア、16スレッド、4.5 GHzにオーバークロック)では1秒あたり100トランザクションをほとんど実行できず、少なくとも1秒あたり1000トランザクションが必要です。

問題がどこから来ているのかはわかりませんが、データベースはデータの変更があまり得意ではないと聞きました。では、ACIDコンプライアンスを危険にさらすことなく、このシステムをどのように最適化できるでしょうか。

私の解決策、それについて私は尋ねたい

データベースに直接アクセスできる唯一のレイヤーであるソフトウェアレイヤーをデータベース上に実装したいと思います。このレイヤーはデータベースをメモリにキャッシュし、永続化のためにデータベースへのすべての変更を定期的にフラッシュします。しかし、これを行うと、データベースが実行するはずのことを実行しているように感じます。

これは一般的に行われることですか?適切なソリューションを計画していますか?


コメントからの編集:

メモリ内にあるトランザクションのみを失う余裕があります。その理由は、いつでも取引が行われるべきであったか、行われなかったはずであるということです。それが特定の瞬間に起こったのか、それから1秒後に起こったのかは関係ありません。私の質問は、この方法でキャッシュすることが有効な解決策であるかどうか、そしてそれが価値があるかどうかを確認することです。それは有効な解決策のようです。それがどれだけ役立つのか、そしてなぜデータベースがそれを行わないのか(またはそれを行うように構成できないのか)はわかりませんか?

どうやら私は上記の単語の正規化を誤解しています。正規化は「テーブルを分割して意味のあるものにする」ことだと理解しているので、パフォーマンスの面では、正規化する方が悪いです(繰り返しになりますが、誤解している可能性があります)。分割される唯一のテーブルは上記のテーブルであるため、4つのテーブルに書き込む代わりに、4つのテーブルに書き込み、usersテーブルを読み取ります。そのため、そのテーブルを分割することが理由であると私はあまり確信していません。しかし、多分私はデザインを再考する必要があります。私はそこの専門家ではありません。

ODB as ORM を使用しているため、DMLを提供できるかどうかわかりません。それらを抽出できるかどうかを確認する必要があります。システムの場所がわかり次第(数時間以内に)DDLステートメントを提供します。

ORMを使用することは、それが絶望的であることを意味しますか?パフォーマンスの20〜30%を失った場合、(今のところ)それと一緒に暮らすことができます。手動でクエリを作成する場合と比較して、パフォーマンスが10倍(1000 tpsから100)低下すると予想する必要がありますか?過剰なようです。

pgbench -t 10000は、単一のスレッドで2350トランザクション/秒になります(すべてのオーダーブックが単一のスレッドを取ることができるため、私のアプリケーションは単一のスレッドを使用します。これは問題ありません)。

編集2:

私が探している第2層のソリューションを実装しているため、以下の回答を選択しました。しかし、1秒あたりのトランザクション数がまだ最適ではありません。つまり、スキーマと操作を調査する必要があります。

あなたの質問は論理的に矛盾しているようです。永続性の待ち時間を許容し、「実際には、メモリのみにあるトランザクションを失う余裕があります」は、定義上、「ACID」の「D」に違反します。

本当に「ACI」に準拠したい場合は、「synchronous_commit」をオフにすることができます。

これは、トレーダーに彼らの取引が実行されたことを報告できることを意味しますが、後でそのおっと、実際にはそうではなかったことを知ることに注意してください。貿易の両側と現金残高の両方の変化が一緒に消えたので、それはまだ原子的で一貫しています。しかし、これは依然として悪い顧客体験になる可能性があります。

「synchronous_commit」をオフにして実行できないと判断した場合は、少なくともオフにしてテストを実行する必要があります。テストの結果は、さらに最適化するためにどのように進めるべきかを示します。

2
jjanes