15

本当の DBA の意見が必要です。Postgres 8.3 は、私の Macbook Pro でこのクエリを実行するのに 200 ミリ秒かかりますが、Java と Python は 20 ミリ秒 (350,000 行) 未満で同じ計算を実行します。

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

SQL データベースを使用する場合、これは正常な動作ですか?

スキーマ (テーブルには調査への回答が保持されます):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

コンテキストのために Java と Python でいくつかのテストを作成しましたが、それらは SQL をクラッシュさせます (純粋な python を除く):

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

sqlite3 でさえ、すべての列が文字列であると仮定しているにもかかわらず、Postgres と競合します (対照的に、Postgres で整数の代わりに数値列に切り替えるだけでも、10 倍の速度低下が発生します)。

成功せずに試したチューニングには次のものが含まれます(盲目的にいくつかのWebアドバイスに従います):

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

私の質問は、ここでの私の経験は正常ですか?これは、SQL データベースを使用するときに期待できることですか? ACID にコストがかかることは理解できますが、これはちょっとおかしいと思います。リアルタイムのゲーム速度を求めているわけではありませんが、Java は 20 ミリ秒未満で数百万の double を処理できるため、少しうらやましく思います。

シンプルな OLAP を低価格で (お金とサーバーの複雑さの両方の点で) 実行するより良い方法はありますか? 私は Mondrian と Pig + Hadoop を調べましたが、さらに別のサーバー アプリケーションを維持することにそれほど興奮しておらず、それらが役立つかどうかもわかりません。


いわば、Python コードと Java コードは社内ですべての作業を行っています。それぞれ 350,000 個のランダムな値を持つ 4 つの配列を生成し、平均を取ります。タイミングには世代を含めず、平均化ステップのみを含めます。Java スレッドのタイミングは 4 つのスレッド (配列あたり平均 1 つ) を使用し、やり過ぎですが、間違いなく最速です。

sqlite3 のタイミングは Python プログラムによって駆動され、ディスクから実行されます (:memory: ではありません)。

Postgres が舞台裏でさらに多くのことを行っていることは認識していますが、これは読み取り専用データであるため、その作業のほとんどは私にとって重要ではありません。

Postgres クエリは、その後の実行でタイミングを変更しません。

Python テストを再実行して、ディスクからスプールすることを含めました。タイミングは大幅に遅くなり、4 秒近くになります。しかし、Python のファイル処理コードはほとんど C で書かれていると思います (csv lib ではないかもしれませんが)。これは、Postgres がディスクからストリーミングしていないことを示しています (または、あなたが正しいので、お辞儀をする必要があります)。誰がストレージ層を書く前に!)

4

10 に答える 10

15

あなたのテストスキームはあまり役に立たないと思います。db クエリを実行するために、db サーバーはいくつかの手順を実行します。

  1. SQL をパースする
  2. クエリプランを作成します。つまり、使用するインデックス (存在する場合) を決定し、最適化します。
  3. インデックスが使用されている場合は、実際のデータへのポインターを検索してから、データ内の適切な場所に移動するか、
  4. インデックスが使用されていない場合は、テーブル全体をスキャンして必要な行を判断します
  5. ディスクから一時的な場所にデータをロードします (できればメモリ)。
  6. count() および avg() 計算を実行する

したがって、Python で配列を作成して平均を取得すると、基本的にこれらすべてのステップがスキップされ、最後のステップが保存されます。ディスク I/O は、プログラムが実行しなければならない最もコストのかかる操作の 1 つであるため、これはテストの重大な欠陥です (以前ここで尋ねたこの質問への回答も参照してください)。他のテストでディスクからデータを読み取った場合でも、プロセスはまったく異なり、結果がどの程度関連しているかを判断するのは困難です。

Postgres が時間を費やしている場所に関する詳細情報を取得するには、次のテストをお勧めします。

  • クエリの実行時間を集計関数なしの SELECT と比較します (つまり、ステップ 5 をカットします)。
  • 集計が大幅な速度低下につながることがわかった場合は、比較から単純な SELECT を介して生データを取得し、Python の方が高速かどうかを試してください。

クエリを高速化するには、まずディスク アクセスを減らします。時間がかかるのは集約であるとは思えません。

それにはいくつかの方法があります:

  • db エンジン自体の機能または memcached などのツールを使用して、後続のアクセスのためにデータを (メモリ内に!) キャッシュします。
  • 保存するデータのサイズを減らす
  • インデックスの使用を最適化します。場合によっては、これはインデックスの使用を完全にスキップすることを意味する場合があります (結局のところ、これはディスク アクセスでもあります)。MySQL の場合、クエリがテーブル内の全データの 10% 以上をフェッチすると想定する場合は、インデックスをスキップすることをお勧めします。
  • クエリがインデックスをうまく利用している場合、MySQL データベースの場合、インデックスとデータを別々の物理ディスクに配置すると役立つことがわかります。ただし、それが Postgres に当てはまるかどうかはわかりません。
  • また、何らかの理由で結果セットをメモリ内で完全に処理できない場合に行をディスクにスワップするなど、より高度な問題が発生する可能性もあります。しかし、そのような調査は、別の方法で修正できない深刻なパフォーマンスの問題に遭遇するまでは、そのままにしておきます。これには、プロセスの内部の詳細に関する知識が必要になるためです。

アップデート:

上記のクエリのインデックスを使用していないようで、おそらく何も使用していないことに気付いたので、インデックスに関する私のアドバイスはおそらく役に立たなかったでしょう。ごめん。それでも、アグリゲーションは問題ではなく、ディスク アクセスが問題だと思います。とにかく、インデックスのものは残しておきますが、まだ使用できる可能性があります。

于 2008-09-09T12:31:26.437 に答える
11

Postgres は見た目以上に多くのことを行っています (最初はデータの一貫性を維持しています!)

値が 100% 正確である必要がない場合、またはテーブルがめったに更新されないが、この計算を頻繁に実行している場合は、マテリアライズド ビューを調べて高速化することをお勧めします。

(Postgres でマテリアライズド ビューを使用していないことに注意してください。少しハッキーに見えますが、状況に適している可能性があります)。

マテリアライズド ビュー

また、実際にサーバーに接続する際のオーバーヘッドと、要求をサーバーに送信して戻すために必要なラウンドトリップも考慮してください。

このようなものには200ミリ秒がかなり良いと考えています.Oracleサーバーでの簡単なテストでは、約50万行でインデックスのない同じテーブル構造で、約1〜1.5秒かかります。これは、ほとんどすべてOracleがデータを吸うだけですオフディスク。

本当の問題は、200 ミリ秒で十分に速いかどうかです。

- - - - - - - もっと - - - - - - - - - -

実際に遊んだことがないので、具体化されたビューを使用してこれを解決することに興味がありました。これはオラクルにあります。

まず、毎分更新される MV を作成しました。

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

更新中、行は返されません

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

更新すると、生のクエリを実行するよりもはるかに高速です

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

ベース テーブルに挿入した場合、結果はすぐには表示されず、MV が表示されます。

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

しかし、1 分ほど待つと、MV が舞台裏で更新され、結果が必要なだけ速く返されます。

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

これは理想的ではありません。まず、リアルタイムではないため、挿入/更新はすぐには表示されません。また、必要かどうかに関係なく、MV を更新するためにクエリを実行しています (これは、任意の時間枠またはオンデマンドで調整できます)。しかし、これは、秒単位の正確さではない値を扱うことができる場合、MV がエンドユーザーにどれだけ速く見えるかを示しています。

于 2008-09-09T14:26:28.297 に答える
5

ENGINE = MEMORY を指定して MySQL を再テストしましたが、何も変わりません (まだ 200 ミリ秒)。インメモリ データベースを使用する Sqlite3 も同様のタイミング (250 ミリ秒) を示します。

ここの数学は正しいように見えます(少なくともサイズは、sqlite dbの大きさです:-)

テーブルがメモリ内にあるというすべての兆候があるため、ディスクが原因で遅いという議論を買っているわけではありません(postgresの人たちは、OSがプログラマーよりもうまくやると誓うので、テーブルをメモリにピン留めするのが難しいことに対して警告します)

タイミングを明確にするために、Javaコードはディスクから読み取っていないため、Postgresがディスクから読み取って複雑なクエリを計算している場合、完全に不公平な比較になりますが、それは本当に重要なことです.DBは小さなテーブルをメモリに格納し、ストアド プロシージャ IMHO をプリコンパイルします。

更新 (以下の最初のコメントへの応答として):

すべての行を選択すると、すべてのシリアル化とフォーマットに膨大な時間がかかるため、公平な方法で集計関数を使用せずにクエリをテストする方法がわかりません。遅さの原因が集約関数にあると言っているわけではありません。同時実行性、整合性、およびフレンドによるオーバーヘッドに過ぎない可能性があります。集計を唯一の独立変数として分離する方法がわかりません。

于 2008-09-10T02:29:45.767 に答える
3

私自身はMS-SQLの担当者であり、 DBCC PINTABLEを使用してテーブルをキャッシュし、SETSTATISTICSIOを使用してディスクではなくキャッシュから読み取っていることを確認します。

私はPostgresでPINTABLEを模倣するものを見つけることができませんが、pg_buffercacheはキャッシュにあるものの詳細を提供しているようです-それをチェックして、テーブルが実際にキャッシュされているかどうかを確認することをお勧めします。

エンベロープ計算を簡単に戻すと、ディスクからページングしているのではないかと思われます。Postgresが4バイトの整数を使用すると仮定すると、行ごとに(6 * 4)バイトがあるため、テーブルは最小(24 * 350,000)バイト〜8.4MBになります。HDDで40MB/ sの持続スループットを想定すると、データを読み取るために約200msを見ていることになります(指摘されているように、ほとんどすべての時間が費やされているはずです)。

私がどこかで数学を台無しにしない限り、Javaアプリに8MBを読み込んで、表示されている時間に処理できる可能性があるかどうかはわかりません-そのファイルがドライブまたはOS。

于 2008-09-10T01:47:32.590 に答える
3

これらは非常に詳細な回答ですが、ほとんどの場合、データがメモリに簡単に収まり、同時読み取りが必要で書き込みがなく、同じクエリで何度もクエリされることを考えると、Postgres を離れずにこれらの利点を得るにはどうすればよいかという疑問が生じます。

クエリと最適化プランをプリコンパイルすることはできますか? ストアドプロシージャがこれを行うと思っていたでしょうが、実際には役に立ちません。

ディスク アクセスを回避するには、テーブル全体をメモリにキャッシュする必要があります。Postgres に強制的にキャッシュさせることはできますか? ただし、クエリは繰り返し実行された後、わずか 200 ミリ秒で実行されるため、既にこれを行っていると思います。

ロック コードを最適化できるように、テーブルが読み取り専用であることを Postgre に伝えることはできますか?

空のテーブルでクエリの構築コストを見積もることは可能だと思います (タイミングの範囲は 20 ~ 60 ミリ秒です)。

Java/Python テストが無効な理由はまだわかりません。Postgres はそれほど多くの作業を行っていません (ただし、同時実行の側面についてはまだ対処していません。キャッシュとクエリの構築だけです)。

更新: ドライバーを介して 350,000 をプルし、Python にシリアライゼーションの手順を実行して集計を実行することによって示唆されているように、SELECTS を比較することは公平ではないと思います。タイミング。両方のエンジンがメモリ内データで動作している場合、リンゴとリンゴの比較になるはずですが、それがすでに起こっていることを保証する方法はわかりません。

コメントを追加する方法がわかりません。評判が足りないのでしょうか?

于 2008-09-09T13:50:18.477 に答える
1

あなたの結果はそれほど驚くべきものではないと思います。どちらかといえば、Postgres が非常に高速であるということです。

データをキャッシュする機会があれば、Postgres クエリは 2 回目に高速に実行されますか? もう少し公平にするために、Java と Python のテストでは、最初にデータを取得するコストをカバーする必要があります (理想的には、ディスクからデータをロードします)。

このパフォーマンス レベルが実際のアプリケーションにとって問題であるが、他の理由で RDBMS が必要な場合は、memcachedを調べることができます。これにより、生データへのキャッシュ アクセスが高速になり、コードで計算を行うことができます。

于 2008-09-09T11:43:10.967 に答える
1

TCP を使用して Postgres にアクセスしていますか? その場合、ネーグルはあなたのタイミングを台無しにしています。

于 2008-09-10T09:45:20.150 に答える
1

RDBMS が一般的に行うもう 1 つのことは、別のプロセスによる同時アクセスからユーザーを保護することにより、並行性を提供することです。これはロックを配置することによって行われ、それによるオーバーヘッドが発生します。

変更されることのない完全に静的なデータを扱っている場合、特に基本的に「シングル ユーザー」のシナリオにいる場合は、リレーショナル データベースを使用しても必ずしも多くのメリットが得られるとは限りません。

于 2008-09-09T13:04:15.077 に答える
0

オラクルのタイミングをありがとう、それは私が探しているものです(残念ですが:-)

ほとんどのユーザーにとって、このクエリの最も興味深い形式を事前計算できると思うので、マテリアライズド ビューはおそらく検討する価値があります。

Postgres を実行しているのと同じマシンでクエリを実行しているため、クエリの往復時間が非常に長くなることはないと思います。

また、キャッシュ サイズも確認しましたが、Postgres はキャッシュの処理を OS に依存しているようです。このための理想的な OS として BSD が具体的に言及されているため、Mac OS はテーブルをメモリー。誰かがより具体的なパラメーターを念頭に置いていない限り、より具体的なキャッシングは私の手に負えないと思います。

最終的には、おそらく 200 ミリ秒の応答時間に耐えることができますが、7 ミリ秒が目標の可能性があることを知っていると、不満を感じます。多くのキャッシングと事前計算されたハック。

MySQL 5 を使用してタイミングを確認したところ、Postgres よりわずかに悪いです。したがって、いくつかの主要なキャッシングのブレークスルーを除けば、これがリレーショナル データベース ルートに期待できることだと思います。

あなたの回答のいくつかに賛成票を投じることができればいいのですが、まだ十分なポイントがありません.

于 2008-09-09T15:34:19.943 に答える
0

プログラムでメモリ内で実行するのと同等のパフォーマンスを期待するには、ワーキング セット全体がメモリに収まるポイントまで postgres のキャッシュを増やす必要があります。

于 2008-09-09T14:10:06.667 に答える