cypher256's blog

Pleiades とか作った

パフォーマンス比較 Cassandra、Mongodb、SQLite、H2、MySQL、Postgres


 下記のようなシステムでパフォーマンスが良さげな SQLite を使用予定ですが、もっと速いものが無いか確認のため他のデータベースのパフォーマンスを計測してみました。SQL 利用前提ですが、NoSQL が圧倒的な性能を出す場合は検討する必要があるので KVS も確認しました。

  • データ件数は 1 億件程度、JDBC SQL 利用可能
  • INSERT、UPDATE はバッチ
  • SELECT は主キーアクセス性能を重視
  • 将来スケールアウトのための分散はありえるが、スタンドアロンで遅いのはだめ

データベースのパフォーマンス比較

計測したデータベース
データベース名 タイプ 形態 評判 計測についての備考
SQLite RDB 組み込み ※2 おもちゃ、Android標準 JDBC操作 ※1
H2 RDB 組み込み ※2 組み込み最速 JDBC操作 ※1
Derby RDB 組み込み ※2 Java標準で付属 JDBC操作 ※1
EHCache KVS 組み込み ※2 OSCacheと2大巨頭 1万件ごとにディスク書き込み設定
Redis(Jedis) KVS サーバー ※4 爆速 Jedis API 操作
Mongodb KVS サーバー ※4 高パフォーマンス Mongodb 標準 API 操作 ※3
Cassandra KVS サーバー 大手導入事例多数、廃止も多数 JDBC操作 (AutoCommit のみ可能)
MySQL(MyISAM) RDB サーバー サーバ型非トランザクションRDB最速 JDBC操作 ※1
MySQL(InnoDB) RDB サーバー サーバ型トランザクションRDB最速 JDBC操作 ※1
PostgreSQL RDB サーバー MySQLと異なりGPLじゃない JDBC操作 ※1

※1 JDBC 操作に関して INSERT は 1 万件ごとにコミット、SELECT は主キー指定で 1 件ずつ全件取得
※2 組み込みモード (ローカルファイル永続化) のみ計測、インメモリモードやサーバーモードは未計測
※3 全 INSERT 後の ensureIndex によるインデックス作成時間が計測結果に含まれる
※4 遅延書き込み (非同期書き込み) *1

計測結果

環境 CPU Core2 Duo 2GH 2GHz*2、メモリ 4GB、Windows XP 32bit*3、HDD (TOSHIBA MK8052GSX)*4、しょぼめのノートパソコン
データ 主キー:数値、値:文字列、レコード長:約 200 byte
スレッド数 1

線形的増加と指数関数的増加、臨界点

 データベースの処理数に対する処理時間は、上記結果の MySQL に見られるような線形的増加 (リニア、直線的) に増加するパターンと、Cassandra のように指数関数的 (雪ダルマ式) に増大するパターンがあります。また、線形的増加から指数関数的増加に移行する臨界点や動作不能になる臨界点がある場合が多いです。1 万件での処理時間はこうだから 1 億件の予想処理時間はその 1 万倍というような情報をたまに見かけますが、件数が増大するほど線形的増加ではないほうが多いと思います。

SQLite が予想以上に他を圧倒し高速

 SQLite  INSERTSELECT 1 ( 1.8 GB ) H2  H2  (H2)  SQLite  SQLite  SQLite 67000  45678000 (publickey) SQLite使105 (CAP-LAB ) 


 SSD 1 INSERT 6471 SELECT 383 SSD  CPU i5 2.4GH 8GBWindows 7 64bit*5SSD (TOSHIBA THNSNC128GMMJ)*6


件数が増えると EHCache が想定以上に遅い


 EHCache は 1 万件取得で最速ですが、件数が増えると組み込み RDBSQLite や H2 より遅いのは良い?にしても、クライアント・サーバー型 KVS の Mongodb や Redis より総合的に少し遅いのは予想外でした。overflowToDisk の設定にもよると思いますが、知らずに H2 や Mongodb のキャッシュとして EHCache を使ってまいそうです。すべてディスクに永続化するように設定していたのですが、100万件テストでは登録したデータを取得しようとしたときに欠落している場合があるため計測しませんでした。ところでロゴを見て気づいたのですが EHCache が回文になっているのを今初めて知りました。

Mongodb がクライアント・サーバー型としては登録性能に優れている

  SQLite 620 (InfoQ)


NoSQL、KVS の代表とも言える Apache Casandra がとんでもなく遅い

  KVS  RDB  100 SELECT  SQLite  200 2 400  SQLite  Twitter  Facebook  Cassandra 使 Cassandra  Facebook  


NoSQL、KVS が終焉


 NoSQL Google  Facebook  HBase (Hadoop)  HBase (Hadoop )*7 使Mixi  memcached  TokyoTyrant 使DeNA  MySQL  HandlerSocket  NoSQL  NoSQL  KVS  RDB 退Twitter  MySQL + memcached GAE  MySQL NoSQL  SQL Postgres  RDB  VoltDB 


  SQL  NoSQL  KVS  RDB  Google  BigTable  KVS  RDB  NoSQL  KVS MySQL 5.6  memcached  HandlerSoclet  SQL  MySQL Cluster  Oracle Exadata  KVS Postgres 32 CPU 


追加 2012/10/19

計測結果についての補足

 id:matsumoto_r 


 EHCache  SQLite  Beta  20%  10% 


計測になぜ Oracle が含まれてないの?

 Oracle OTN 



 OTN  

DB2 


(A) 使 () 
(B)  IBM  IBM  ()   
(C)  IBM  Web 
SLA - L-JWOG-6K4JSQ
計測ソース*8

 今回の計測対象のメインとなる JDBC のソースはこちらになります。INSERT のあとの SELECT なのでキャッシュ云々の話もありますが、それも含めて各データソースに対して同じ操作をしています。SQLite のみ Class.forName しているのはドライバがサービスプロバイダーフレームワークに対応していないためです。(bitbucket リポジトリの最新ソースでは 2012/09 対応)

package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

import org.junit.After;
import org.junit.Test;

public class JdbcTest {

    @Test
    public void sqlite() throws Exception {
        Class.forName("org.sqlite.JDBC");
        con = DriverManager.getConnection("jdbc:sqlite:test.sqlite3");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table if exists person");
        executeUpdate(st, "create table person (id integer primary key, name string)");
        executeQuery();
    }

    @Test
    public void h2() throws Exception {
        con = DriverManager.getConnection("jdbc:h2:testh2", "sa", "");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table if exists person");
        executeUpdate(st, "create table person (id integer primary key, name varchar)");
        executeQuery();
    }

    @Test
    public void derby() throws Exception {
        con = DriverManager.getConnection("jdbc:derby:derby;create=true");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table person");
        executeUpdate(st, "create table person (id int primary key, name varchar(200))");
        executeQuery();
    }

    @Test
    public void mysql_myisam() throws Exception {
        mysql("MyISAM");
    }
    @Test
    public void mysql_innodb() throws Exception {
        mysql("InnoDB");
    }
    private void mysql(String engine) throws Exception {
        con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table if exists person");
        executeUpdate(st, "create table person (id integer primary key, name varchar(200)) engine = " + engine);
        executeQuery(con.getMetaData().getDatabaseProductName() + "(" + engine + ")");
    }

    @Test
    public void postgres() throws Exception {
        con = DriverManager.getConnection("jdbc:postgresql:postgres", "postgres", "postgres");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table if exists person");
        executeUpdate(st, "create table person (id integer primary key, name varchar)");
        executeQuery();
    }

    @Test
    public void cassandra() throws Exception {
        con = DriverManager.getConnection("jdbc:cassandra://localhost:9160/test");
        Statement st = con.createStatement();
        executeUpdate(st, "drop table person");
        executeUpdate(st, "create table person (id int primary key, name text)");
        executeQuery();
    }

    // 共通メンバー -------------------------------------------

    private Connection con;
    private static final int COUNT = 10000 * 10;
    private static final String DATA = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";

    @After
    public void after() throws Exception {
        if (con != null) {
            con.close();
        }
    }

    private void executeUpdate(Statement st, String sql) {
        try {
            st.executeUpdate(sql);
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }

    private void executeQuery() throws Exception {
        executeQuery(con.getMetaData().getDatabaseProductName());
    }

    private void executeQuery(String databaseName) throws Exception {

        boolean isCassandra = databaseName.contains("Cassandra");
        boolean isAutoCommit = isCassandra;

        System.out.printf("%-14s", databaseName);
        if (!isAutoCommit) {
            con.setAutoCommit(false);
        }

        long insertStart = System.currentTimeMillis();
        PreparedStatement insertPs = con.prepareStatement("insert into person (id, name) values(?, '" + DATA + "')");
        for (int i = 0; i < COUNT; i++) {
            insertPs.setInt(1, i);
            insertPs.executeUpdate();
            if (!isAutoCommit && i % 10000 == 0) {
                con.commit();
            }
        }
        if (!isAutoCommit) {
            con.commit();
        }
        double insertSec = (double) (System.currentTimeMillis() - insertStart) / 1000;

        long selectStart = System.currentTimeMillis();
        PreparedStatement selectPs = con.prepareStatement("select * from person where id = ?");
        for (int i = 0; i < COUNT; i++) {
            selectPs.setInt(1, i);
            selectPs.executeQuery().next();
        }
        double selectSec = (double) (System.currentTimeMillis() - selectStart) / 1000;

        String countSql = "select count(1) from person";
        if (isCassandra) {
            countSql += " limit 100000000";
        }
        ResultSet rs = con.createStatement().executeQuery(countSql);
        rs.next();
        logProcessTime(rs.getInt(1), insertSec, selectSec);
    }

    private void logProcessTime(long count, double insertSec, double selectSec) {

        System.out.printf("%4d万件 ", count / 10000);
        System.out.printf("%7.2f秒 ", insertSec);
        System.out.printf("%7.2f秒", selectSec);
        System.out.println();
    }
}

*1:2012/10/20 1:00 @kimutansk さんの指摘により追記

*2:2012/10/14 11:57 @showyou さんの指摘により修正

*3:2012/10/14 12:15 @showyou さんの指摘によりビット数追記

*4:2012/10/15 23:51 @showyou さんの指摘により追記

*5:2012/10/14 12:15 @showyou さんの指摘によりビット数追記

*6:2012/10/14 12:15 @showyou さんの指摘により型番追記

*7:2012/10/14 11:57 @shiumachi さんの指摘により修正

*8:2012/10/19 22:50 id:ysobj さんの指摘により追記