Edited at
RealmDay 19

天下一「AndroidのORM」武道会(2015年版)

More than 3 years have passed since last update.


はじめに

この記事は「Realm Advent Calendar 2015」の19日目の記事です。

・・・なのですが、あまりRealm固有の話は出てこないです。Realmの「外側」の話に終止します。


はじめての方へ趣旨説明

本記事は、「AndroidにおけるSQLite O/Rマッパーでどれを使えばよいのか」という問題を数値比較して検討することを目的とした、以下の記事の更新版という位置づけです。

1年前に書いた以下の記事をざっくり読まれることをお勧めします。


再びORM計測を行う理由

ただのポエムなので、どこかQiitaではないところに書いておいて、あとでリンク貼ります。。。

なお私はSQLiteの知識に長けているわけではありませんので、その点をご理解ください。


エントリーリスト

基本的には私の独断と偏見で選出しております。

(初)印のあるものは、今回初計測となるORMとなります。


  • 古参ORM:比較的よく認知されているORM


    • ORMLite

    • greenDAO

    • ActiveAndroid



  • 中堅ORM:新星でもなく古参でもない微妙な位置。


    • Ollie (初)

    • SugarORM

    • DBFlow (初)

    • DBTools (初)

    • Shillelagh (初)

    • Torch (初)

    • Cupboard (初)



  • 期待の新星:今年に初版リリースされたもの。


    • Slim Repo (初)

    • RushORM (初)

    • SquiDB (初)

    • Orma (初)

    • EasyliteORM (初)

    • TriORM (初)

    • QuantumFlux (初)

    • CPOrm (初)



  • NoSQL:SQLiteを用いていないもの。Realmはここに含まれます


    • Realm

    • SnappyDB (初)

    • MapDB (初)

    • Paper (初)

    • Sabres (初)




検証方法



  • Simpleクラスを作成


    • フィールドは以下の図を参照



  • 【計測】このクラスのプロパティをORMの提供するデータベースに一万件INSERTする


    • トランザクション有効時(BulkMode:ON)および未使用時(BulkMode:OFF)の2パターン計測する



  • 【計測】INSERTしたレコードのboolean値フィールドを参照し、trueのものだけを抽出してリストにして返す


    • SQLで書くとSELECT * FROM Simple WHERE booleanValue = trueという具合に表現できる



  • 上記【計測】の計測を5回行い、平均値を算出する。この平均値をスコアとして評価する。

検証端末には前回と同じく「Xperia A SO-04E」(Android 4.2.2)を使用します。

選定理由は、昨年との比較がしやすいと考えたからです。


懸念点(および免責事項)

本記事での検証方法では、以下の点で公平さを欠くものと考えています。


  • ライブラリの都合等で、必ずしもテスト実行方法が統一されていない


    • Applicationクラスに依存するもの、そうでないもの・・・



  • ライブラリの都合等で、tearDown(テスト後の後始末処理)が統一されていない


    • 原則として本検証ではtearDownでDBファイルの名前を変更することでDBファイル削除相当の処理としている(よって次のsetUpで新しいDBファイルが生成されるものと期待している)

    • しかし一部のORMではApplicationクラスに依存するなどの理由でDBファイルそのものを変更すると動かなくなることがあった。この場合は止むを得ずテーブルのレコードを削除するのみに留めている



  • 2015年版の検証結果スプレッドシートで追加されたORMのほとんどで、機能的な比較が行えませんでした(例えば、所謂Many-to-Manyな関係に対応しているか)。

  • BulkModeと本記事で呼称しているトランザクション対応について、その対応がORMごとに異なっています。また、Listを受け付けることができるINSERT処理メソッドがある場合、それをトランザクション対応していると呼ぶべきかどうか、はっきりした態度を確定できておりません。


    • 今回の計測ではトランザクションとして扱いませんでしたが、一部ORMでは非常に有利になるケースがあるため、再考の余地があるものと考えます。



「この検証結果によって生じたいかなる問題についても筆者は一切の責任を負いかねます」。

# ただし、より良くしたいとは思っているので、優しく指摘いただけると嬉しいです。


検証用コード

検証のために使用したソースコードは以下から入手できます。

keima/MostPowerfulOrmInAndroid (tag:2015)

appFooBarプロジェクト の FooBar の部分がORM名称となっています。


検証結果一覧スプレッドシート

https://docs.google.com/spreadsheets/d/1FQMLR6wbrWhut2fjxMymEokokQqBMiLO_GP1Lx82hoc/edit?usp=sharing


補足事項


  • EasyliteORMは、テスト用にContextをモックさせるための入り口がないため、他と公平な比較ができないと判断し、比較を中止しています。


    • このORMは内部でActivityを継承したクラスを持っており、そのContextを使うらしいのだけど、ちょっと自分に技術力がなくてどうしようもなかった



  • 以下のORMは、Date型の扱いに難があったためにDate型のフィールドを使わないようにしました。これら記録は他の記録とは異なる条件となったため、参考記録として扱います。


    • DBTools

    • Torch

    • SquiDB



  • Realmは2014年版の検証とは異なるロジックでSELECTを評価しています。具体的には、RealmResultを得ただけでは中身の無いObjectのみが存在しているため、中身のある状態に戻すような処理を加えています。


    • しかしながらこのロジックで正しいのかは疑問があるため、ご指摘いただけると幸いです。


    • 問題のコードはこちらです




大会総括

まずここで一つキッパリと明言したいのは、「ActiveAndroidはもはや負債寸前である」ということです。

2014年10月を最後にコミットが途絶えていることを考えるに、将来アップデートされる希望をもつほうが難しい状態です。

また、同作者のOllieもまた、2015年3月を最後に更新が途絶えていることも気掛かりではあります。

ではActiveAndroidやOllieは更新が途絶えるほど充分枯れているかと言われると、私には疑問が残ります。

というのも、今回の検証では両ORMともにSNAPSHOT版を使用しました。これは、ActiveAndroidはSNAPSHOT版のほうが手軽にコードを記述できる機能が導入されているためで、Ollieはテーブルのレコード全件削除に不具合を抱えているものがSNAPSHOT版では改善されているためです。

SNAPSHOT版が実質の安定版であるならば、早急にリリース版を公開して欲しいのですが、そうなっていないところに深い闇を感じます。

しかしながら、ActiveAndroidはORMの定番の一つでしたし、私自身も質問を受けた時にActiveAndroidを推していた時期があります。

また、当時の私達は、まさかActiveAndroidがこのようなことになるとは思いもしていなかったはずです

(プログラマー経験の長い方なら、このライブラリは安心、このライブラリは先がない、というのが見分けられるのだろうか・・・?)。

一度導入するとアプリを作り直すまではずっとお付き合いをし続けなければならないことも、ORMライブラリ選定をより難しくしていると感じます。

とはいえカラム名を気合で揃えたりデータ移行ウィザードを頑張って作ればなんとか移行できるとは思いますが、あまりやりたくない作業なのは確かです。

なので、ORM選定においては継続的にメンテナンスされているかどうか、という要素も重要だと思います。

今回の比較用スプレッドシートでは、最後にリリースされた日などの情報をできるだけ入力してあります。

けれども、今年活発でも来年も引き続き活発かどうかは分かりません。ライブラリ選定の難しさを感じます。

ポストActiveAndroidの世界で我々は何を選べばよいのか?それはRealmが担うのか?それとも未だ見ぬORMが新時代を担うのか?

本検証記事が今後の指標となれば書いた意味があるというものですが、結局こんなものは好みと案件規模によります、という身も蓋もない話ではあります。


速さランキング

BulkMode:ON

BulkMode:OFF

順位
INSERT
SELECT
INSERT
SELECT

1
Realm
greenDAO
Paper
Realm

2
Orma
Realm
SnappyDB
Orma

3
greenDAO
Orma
MapDB
greenDAO

4
DBFlow
Shillelagh
QuantumFlux
DBFlow

5
QuantumFlux
DBFlow
CPOrm
Shillelagh


トランザクションを使用した時

トランザクションを使用した時(BulkMode:ON)のINSERTでは、上位4件が2000ミリ秒台を切るスコア。

5位のQuantumFluxが4000ミリ秒を少し超えているので、この上位4件がいかに速いか?がお分かり頂けるかと思います。

RealmやgreenDAOの速さは前回記事で書いたとおりであり、皆様おなじみだと思いますが、ここにきて今年11月に彗星のごとくリリースされたOrmaが2位。

Ormaはモデル定義などが手軽なので、単に速いだけじゃないのが素晴らしいと思います。もっと多くの人が使用してフィードバックを得ることが出来れば、更に良くなっていく気がします。

Realmは実は昨年比で200ミリ秒遅くなっているという数値上の事実がありますが、これは誤差の範囲だとも言えるし、そうではなくて何らかの速度を犠牲にした機能追加、というトレードオフがあった可能性もあります。

ちなみに、今年アップデートのなかったORMLiteは、数値だけを純粋に見れば、今回の検証で400ミリ秒速くなっています。誤差なのか、SDKによる最適化なのか・・・?

実はDBFlowは、前回の計測ではSELECTこそ速いがINSERTが激遅で微妙な感じだったのですが、そこから何度かのアップデートを経て2000ミリ秒を切るORMに進化しております。

続けて計測していると、このような気づきがあるのは面白いことだと思いました。


トランザクションを使用しない時

NoSQL無双!という感じですが、疑惑の判定もあります(いやまぁ疑惑になるようなテストケースの書き方をするな、という話であります・・・ORMに罪はないんです・・・すいません)。

今回扱ったKVS(Key-Value Store)なNoSQLでは、Stringのキーに対してObjectを格納できます。

そのObjectはListでもOKなケースがあり、それを放り込むと脅威の数値を叩き出すのです。

つまり、一万件の小さいオブジェクトをINSERTするのか、たった1つの巨大オブジェクトをINSERTするのか、という違いになります。

(そもそもディスクに書き出しているんだろうか?オンメモリDBなんじゃね?というのはありますが、それだともうちょい狂ったスコアが出るかも)

RealmはSQLiteではないけれどトランザクションを必須としているため、

トランザクションを使用しない場合の計測では一件入れたらトランザクションを閉じ・・・ということをテストコード内で行っています。なので性能がガクンと落ちています。

いづれにせよ、トランザクションを使わないケースではSQLiteがNoSQLに勝つのは非常に難しいです。

そんななかでQuantumFluxが食い込んでいるのはなかなかすごいことだと思いました。


SELECTについて

不可思議なのはSELECTの順位が変動することがトランザクションの有無で変わることがあるんだろうか?ということです。

INSERTで負荷がかかった分、端末が不安定になっているとか、あるいはトランザクションを張ったINSERTは挙動が違うのか・・・。

いづれにせよ、greenDAOの性能が5倍変わってくるのは不思議です。

まぁそれはそれとして。

(とりあえずRealmのAdvent CalendarなのでRealm有利な方(BulkMode:OFF)のデータを見ていきます)

BulkMode:OFF時のNoSQLのINSERT性能は驚異的でしたが、SELECTではSQLiteのWHERE句のような仕組みがないことが多いので、まず全件取り出してからデータをJavaレイヤーで比較する必要があります。そのため全体的に性能は良くありません。(まぁでもSnappyDBなんかはランキング上位なんだけどね)

ActiveAndroidとRealmを比較すると、SELECTの性能差10倍でRealmのほうが速いので、Realmに変えるだけでアプリが10倍速くなる、、、かもしれませんね。


実装してみて感じたこと


RxJava対応のORMが増えてきている?

個人的にはネットワーク通信の非同期制御に活用しているRxJavaですが、データベース取得も件数に寄って非同期処理にすべき箇所であり、RxJavaで管理するのは理にかなっていると思います。

RxJavaでなくとも何らかのコールバック関数をとるORMもそれなりにありました。

惜しいのは、他のORMとの比較のことがあるのと、テストケースは同期的に動いてくれたほうが助かるので、

検証でRxJavaを使うことがなかったのが残念でした。

以下のORMは、RxJavaインターフェースを返す実装を持っています


  • Ollie

  • DBTools

  • Shillelagh

  • SquiDB

  • Orma

これらのORM以外にも、「[ORM名] RxJava」で検索すると、メジャーなORMの場合はやり方を解説している記事があったり、ライブラリが見つかったりします。

もちろん、Realmも公式ブログでRxJavaへの対応方法が記されておりますよ!(なお僕は試していないし、書かれたのも2015年3月なので今でも現役で使える情報かはわからないです。。)

追記(2015/12/19 0:48):

12/17 にRxJavaを公式サポートした版がでていたようです!RxJavaガンガン使っていこう!


ContentProviderを活用したORM

これまでもContentProviderを提供するORMはありましたが、ContentProvider必須のORMが登場しました。

ContentProviderは個人的にはそれほど活用していないのですが、

ListView等との連携をさせる場合はとても強いのではないか?と思います。

QuantumFluxは自らCPOrmとSugarORMにインスパイアされたとREADMEに書いています。

性能は、QuantumFluxのほうが良い場合もあれば悪い場合もあり。

しかし使い勝手はQuantumFluxのほうが洗練されている気がします。

以下のORMは、ContentProviderに依存します


  • CPOrm

  • QuantumFlux

また、ちょっと文脈とズレますが、RealmもContentProviderに対応させることが出来るようです。


ジェネレータ系ORMは廃れる運命にあるかもしれない

greenDAOに代表される、モデル定義を実際に使用するクラス以外で記述するORMは、

この検証規格においてはケタ違いのベンチマークを出す印象があります。

実際、DBToolsも比較的良好な成績を出しているように思われます。

しかしながら、Realmのような既存ORMライクなインターフェースを備えたNoSQLや、OrmaやDBFlowのようなモデルにアノテーション記述してapt生成することでコード生成を行うORMの台頭により、徐々にその優位性が失われているように思います。

とはいえgreenDAOはジェネレータ系ORMの中では最も洗練されていると感じるORMではありますし、作者が他のORMの動向を気にかけているような雰囲気もあるので、まだまだ安泰ではないか?とも思います。

けれどもモデル生成のためにDSLを覚えるのは苦痛ですし、それがXMLベースだと最悪の体験であると言わざるを得ません。良いエディタがあれば別ですが。。。


3行でまとめ


  • 今年もRealm速かった、けれどブッチギリではない

  • SQLiteではOrma、greenDAO、DBFlowが速い

  • でも速度だけがORMの良さを決めるってものでもない(1年ぶり2度めの宣言)

Realmにおかれましては、SQLiteを使用するORMが速度追従してきていることを念頭に置いて、

弱点を克服しつつポストActiveAndroidの世界を担うデータベースライブラリになって頂きたいと願っております。

引き続き、Realm Advent Calendarをお楽しみ下さい。