web-dev-qa-db-ja.com

PostgreSQLはSQLiteよりもはるかに高速に書き込みを実行しますか?

簡単な整数更新のパフォーマンステストを行いました。 SQLiteは1秒あたり15回の更新のみを行いましたが、PostgreSQLは1秒あたり1500回の更新を行いました。

SQLiteの場合の数字は normal のようです。

SQLiteサイトの [〜#〜] faq [〜#〜] は、回転ディスクの基本的な制限であるかのように説明しています。

実際、SQLiteは平均的なデスクトップコンピューターで毎秒50,000以上のINSERTステートメントを簡単に実行します。しかし、1秒あたり数十のトランザクションしか実行しません。トランザクション速度は、ディスクドライブの回転速度によって制限されます。トランザクションは通常、ディスクPlatterの2つの完全なローテーションを必要とします。7200RPMディスクドライブでは、1秒あたり約60トランザクションに制限されます。 SQLiteは、トランザクションが完了する前にデータが実際に安全にディスク表面に格納されるまで(デフォルトで)SQLiteが実際に待機するため、トランザクション速度はディスクドライブの速度によって制限されます。これにより、突然電源が切れた場合やOSがクラッシュした場合でも、データは安全です。詳細については、SQLiteのアトミックコミットについてお読みください。

デフォルトでは、各INSERTステートメントは独自のトランザクションです。ただし、BEGIN ... COMMITで複数のINSERTステートメントを囲むと、すべての挿入が1つのトランザクションにグループ化されます。トランザクションをコミットするのに必要な時間は、含まれているすべての挿入ステートメントにわたって償却されるため、挿入ステートメントごとの時間が大幅に短縮されます。

もう1つのオプションは、PRAGMA同期= OFFを実行することです。このコマンドは、SQLiteがデータがディスク表面に到達するのを待たないようにするため、書き込み操作がはるかに高速に見えるようになります。ただし、トランザクションの途中で電源が切れると、データベースファイルが破損する可能性があります。

この説明は本当ですか?では、PostgreSQLはSQLiteよりもはるかに高速に実行できますか? (PostgreSQLでfsyncsynchronous_commitオプションの両方をonに設定しました)

更新:

Clojureで記述された完全なテストコードは次のとおりです。

(defproject foo "0.1.0-SNAPSHOT"
  :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/Java.jdbc "0.3.0-SNAPSHOT"]
                 [com.mchange/c3p0 "0.9.2.1"]
                 [org.xerial/sqlite-jdbc "3.7.2"]
                 [postgresql "9.1-901.jdbc4"]])
(ns foo.core
  (:require [clojure.Java.jdbc :as jdbc]
            [clojure.Java.jdbc.ddl :as ddl])
  (:import  [com.mchange.v2.c3p0 ComboPooledDataSource]))

(def sqlite
  (let [spec {:classname "org.sqlite.JDBC"
              :subprotocol "sqlite"
              :subname "test.db"}]
    {:datasource (doto (ComboPooledDataSource.)
                   (.setDriverClass (:classname spec))
                   (.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
                   (.setMaxIdleTimeExcessConnections (* 30 60))
                   (.setMaxIdleTime (* 3 60 60)))}))

(def postgres
  (let [spec {:classname "org.postgresql.Driver"
              :subprotocol "postgresql"
              :subname "//localhost:5432/testdb"
              :user "postgres"
              :password "uiop"}]
    {:datasource (doto (ComboPooledDataSource.)
                   (.setDriverClass (:classname spec))
                   (.setJdbcUrl (str "jdbc:" (:subprotocol spec) ":" (:subname spec)))
                   (.setUser (:user spec))
                   (.setPassword (:password spec))
                   (.setMaxIdleTimeExcessConnections (* 30 60))
                   (.setMaxIdleTime (* 3 60 60)))}))

(doseq [x [sqlite postgres]]
  (jdbc/db-do-commands x
    (ddl/create-table :foo [:id :int "PRIMARY KEY"] [:bar :int])))

(doseq [x [sqlite postgres]]
  (jdbc/insert! x :foo {:id 1 :bar 1}))

(defmacro bench
  [expr n]
  `(dotimes [_# 3]
     (let [start# (. System (nanoTime))]
       (dotimes [_# ~n]
         ~expr)
       (let [end#               (. System (nanoTime))
             elapsed#           (/ (double (- end# start#)) 1000000.0)
             operation-per-sec# (long (/ (double ~n) (/ (double (- end# start#)) 1000000000)))]
       (prn (str "Elapsed time: " elapsed# " ms (" (format "%,d" operation-per-sec#) " ops)"))))))

(bench (jdbc/query sqlite ["select * from foo"]) 20000)
(bench (jdbc/execute! sqlite ["update foo set bar=bar+1 where id=?" 1]) 100)

(bench (jdbc/query postgres ["select * from foo"]) 20000)
(bench (jdbc/execute! postgres ["update foo set bar=bar+1 where id=?" 1]) 5000)

そして出力は:

; Running "select * from foo" 20000 times in SQLite

"Elapsed time: 1802.426963 ms (11,096 ops)"
"Elapsed time: 1731.118831 ms (11,553 ops)"
"Elapsed time: 1749.842658 ms (11,429 ops)"

; Running "update foo set bar=bar+1 where id=1" 100 times in SQLite

"Elapsed time: 6362.829057 ms (15 ops)"
"Elapsed time: 6405.25075 ms (15 ops)"
"Elapsed time: 6352.943553 ms (15 ops)"

; Running "select * from foo" 20000 times in PostgreSQL

"Elapsed time: 2898.636079 ms (6,899 ops)"
"Elapsed time: 2824.77372 ms (7,080 ops)"
"Elapsed time: 2837.622659 ms (7,048 ops)"

; Running "update foo set bar=bar+1 where id=1" 5000 times in PostgreSQL

"Elapsed time: 3213.120219 ms (1,556 ops)"
"Elapsed time: 3564.249492 ms (1,402 ops)"
"Elapsed time: 3280.128708 ms (1,524 ops)"

pg_fsync_testの結果:

C:\temp>"C:\Program Files\PostgreSQL\9.3\bin\pg_test_fsync"
5 seconds per test
O_DIRECT supported on this platform for open_datasync and open_sync.

Compare file sync methods using one 8kB write:
(in wal_sync_method preference order, except fdatasync
is Linux's default)
        open_datasync                   81199.920 ops/sec      12 usecs/op
        fdatasync                                     n/a
        fsync                              45.337 ops/sec   22057 usecs/op
        fsync_writethrough                 46.470 ops/sec   21519 usecs/op
        open_sync                                     n/a

Compare file sync methods using two 8kB writes:
(in wal_sync_method preference order, except fdatasync
is Linux's default)
        open_datasync                   41093.981 ops/sec      24 usecs/op
        fdatasync                                     n/a
        fsync                              38.569 ops/sec   25927 usecs/op
        fsync_writethrough                 36.970 ops/sec   27049 usecs/op
        open_sync                                     n/a

Compare open_sync with different write sizes:
(This is designed to compare the cost of writing 16kB
in different write open_sync sizes.)
         1 * 16kB open_sync write                     n/a
         2 *  8kB open_sync writes                    n/a
         4 *  4kB open_sync writes                    n/a
         8 *  2kB open_sync writes                    n/a
        16 *  1kB open_sync writes                    n/a

Test if fsync on non-write file descriptor is honored:
(If the times are similar, fsync() can sync data written
on a different descriptor.)
        write, fsync, close                45.564 ops/sec   21947 usecs/op
        write, close, fsync                33.373 ops/sec   29964 usecs/op

Non-Sync'ed 8kB writes:
        write                             889.800 ops/sec    1124 usecs/op
19
alice

あなたは疑わしいのは正しいです。指定した設定のPostgreSQLは、回転メディアに対して毎秒個別の順次トランザクションで1500近くの更新を実行できません。

おそらく、あなたのIOスタックの何かが、同期の実装方法について嘘をついているかバグがあります。これは、予期しない停電やOSの障害の後にデータが深刻な破損の危険にさらされていることを意味します。

Pg_test_fsyncの結果を見ると、これは事実です。 Windowsのデフォルトであるopen_datasyncは、非現実的に高速に見えるため、安全ではないはずです。 Windows7マシンでpg_test_fsyncを実行すると、同じことがわかります。

6
jjanes

スナップショット分離の実装方法に分類されます。

SQLiteはトランザクションを分離する手段としてファイルロックを使用し、すべての読み取りが完了してから書き込みがヒットするようにします。

それとは対照的に、Postgresは複数同時実行バージョン管理(mvcc)と呼ばれるより洗練されたアプローチを使用して、複数の書き込みを複数の読み取りと並行して発生させることができます。

http://www.sqliteconcepts.org/SI_index.html

http://www.postgresql.org/docs/current/static/mvcc-intro.html

http://wiki.postgresql.org/wiki/MVCC

15

デニスの答えはあなたが必要とするすべてのリンクを持っています。あまり詳細ではありませんが、おそらく理解しやすい答えを選びます。

Sqliteは洗練されたトランザクションマネージャを使用しません。高度なマルチタスクロジックが隠されていません。実行するように指示したものをその順序で実行します。言い換えれば、あなたが言うように正確にそれを行います。 2つのプロセスから同じデータベースを使用しようとすると、問題が発生します。

一方、PostgreSQLは非常に複雑なデータベースです。複数の同時読み取りと書き込みを効率的にサポートします。それを非同期システムと考えてください-実行する作業をスケジュールするだけで、実際には詳細で制御しません-Postgresが代わりに行います。

効率をどうするか 1つのトランザクションに、数-数十-数百の更新/挿入を結合します。単純なテーブルの場合、非常に優れたパフォーマンスが得られます。

4
Dariusz

実際、回転ディスクへの書き込みは10ミリ秒程度です(通常、8ミリ秒です)。

これは、ディスクの同じ位置に書き込む場合、1秒あたり100を少し超える書き込みを意味します。これは、データベースの場合は非常に奇妙なケースです。 ACMの「ディスクについてのジャックがわからない」を参照してください。通常、ディスクは1回のローテーションで10回の読み取りまたは書き込みをスケジュールできます。

http://queue.acm.org/detail.cfm?id=864058

したがって、データベースは毎秒1,000回以上の書き込みを実行できます。 10年前、デスクトップコンピューターでアプリが1秒あたり1,500のトランザクションを実行するのを見てきました。

2
user133536

通常のハードディスク(ssdなし)を使用しているとすると、1秒あたり最大50〜100回の書き込みが予想されます。 1秒あたり15回の書き込みはわずかに少ないようですが、不可能ではありません。

したがって、Postgresが毎秒1500回の更新を実行している場合、それらは何らかのバッファ/キャッシュに書き込まれるか、単一の更新に縮小されます。実際のテストについてよく知らなければ、それが実際の理由であると言うのは難しいですが、トランザクションを開いて単一行を1500回更新し、その後コミットすると、Postgresは単一の「実際の」実行のみを実行できるほどスマートになるはずです。ディスクに書き込みます。

1
Wolph