Ruby用のデータ処理ツールを提供するプロジェクトRed Data Toolsをやっている須藤です。
数年前からRuby on RailsアプリケーションでADBCを使えるとよさそうだなぁと思っていた(証拠1、証拠2)のですが、ついにそれが動くようになったのでactiverecord-adbc-adapterとしてリリースしました。
ADBCとはArrow Database Connectivityの略で、Apache Arrowを使ってDBと高速に大量データをやりとりするための仕組みです。Active RecordでADBCを使えるようになると、Ruby on Railsアプリケーションでの大量データのやりとりが速くなります。また、Active Record経由でDuckDBも使えるようになります。DuckDBもADBCをサポートしているからです。
Active Recordアダプター
Active RecordはRuby on Railsのデータベースアクセス処理を抽象化したライブラリーです。(雑だけどだいたいあっていそうな説明。)Active Recordにはアダプターという仕組みがあり、バックエンドのデータベース固有の処理を吸収しています。このアダプターという仕組みがあるおかげで、ユーザー(Ruby on Railsアプリケーション開発者)はデータベースの詳細を(あまり)気にせずにデータベースを使うことができます。
→ PostgreSQL
ユーザー → Active Record → MySQL
→ SQLite
アダプターにはPostgreSQL用アダプターやSQLite用アダプターがありますが、今回はADBC用アダプターactiverecord-adbc-adapterを作りました。
ADBCアダプター
ADBCアダプターを使うことによりADBCをサポートしている各種データベースにアクセスできるようになります。通常、ActiveRecordのアダプターは特定のデータベース1つと紐づいていますが、ADBC自体が各種データベースとの接続APIを抽象化しているので、1つのADBCアダプターで複数のデータベースをサポートするようになっています。
→ PostgreSQL
ユーザー → Active Record → ADBC → DuckDB
→ SQLite
Active Recordにビルトインのアダプターを使う場合は次のようにadapter:でどのデータベースに接続するかを指定します。
production:
adapter: postgresql
一方、ADBCアダプターを使う場合はadapter:はつねにadbcでdriver:でどのデータベースに接続するかを指定します。(ADBCではActive Recordの「アダプター」に相当するものを「ドライバー」と呼んでいるのです。)
production:
adapter: adbc
driver: adbc_driver_postgresql
もし、これの使い勝手がよくないという話があったら、データベースごとにadbc_postgresqlのようなアダプターを用意するようにするかもしれません。
ADBCアダプターの使いどころ
ADBCは大量データのやりとりでメリットがでてくるもので、1件ずつデータを参照・更新するようなケースには向いていません。1件ずつ扱う場合はActive Recordのビルトインのアダプターを使ってください。ADBCアダプターを使うと無用な複雑性を導入するだけです。
ということで、普通は1件ずつ扱う用の接続情報と大量データを扱う用の接続情報が必要になるはずです。Active Recordは複数のデータベースに対応しているので、その機能を使うことになります。たとえば、次のようにconfig/database.ymlを書くことになります。
default: &default
primary:
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
url: <%= ENV.fetch("DATABASE_URL") { "postgresql:///my_app_#{Rails.env}" } %>
adbc:
adapter: adbc
driver: adbc_driver_postgresql
# "url"じゃなくて"uri"だよ!!!
uri: <%= ENV.fetch("DATABASE_URL") { "postgresql:///my_app_#{Rails.env}" } %>
# マイグレーションを無効にする。
database_tasks: false
development:
<<: *default
test:
<<: *default
production:
<<: *default
primaryがデフォルトで使われる接続情報で、adapter: postgresqlになっているのでビルトインのアダプターを使います。adbcはADBC用の接続情報で、adapter: adbcとなっています。ADBCではマイグレーションをしないので、database_tasks: falseにしています。
モデルも分けます。Active Recordはクラス単位で接続を管理しているからです。(あってる?ActiveLdapのために調べたときはそうだったと思うけど、今もそう?)
まず、ADBC用の抽象クラスを作ります。このクラスを継承するとADBC用の接続情報を使います。
# app/models/adbc_application_record.rb
class AdbcApplicationRecord < ActiveRecord::Base
include ActiveRecordADBCAdapter::Ingest
self.abstract_class = true
connects_to database: { writing: :adbc, reading: :adbc }
end
ADBCでアクセスしたいクラスはこのクラスを継承します。既存のテーブルを参照するためにself.table_name=で明示的にテーブル名を指定しないといけないでしょう。(これとは別にいつもどおりアクセスする用のclass Event < ApplicationRecordがある想定。)
# app/models/adbc_event.rb
class AdbcEvent < AdbcApplicationRecord
self.table_name = :events
end
あとはいつもどおり使えます。
たとえば、こうすると全データをApache Arrowデータとして取得できます。Apache Arrowデータなので速いです。(Apache Arrowに関してはこのブログ内にもいろいろ情報があるのでここでは説明しません。)
AdbcEvent.all.to_arrow
ingestを使うとApache Arrowデータをロードできます。Apache Arrowデータなので速いです。
AdbcEvent.ingest(arrow_data)
たとえば、最近1ヶ月のイベントデータをDuckDBで分析するためにPostgreSQLからデータを取り込む、というのはこんな感じで書けます。で、これが速いです。
DuckDBAdbcEvent.ingest(PgAdbcEvent.where(created_at: ..(1.month.ago)).to_arrow)
こんな感じで大量データを扱うのが高速になります。
ADBCアダプターの速さ
これまで速い速いと言ってきましたが、本当に速いのでしょうか?簡単なケースですが確認してあります。
100個の整数の列を持つ10000行のデータをPostgreSQLでロード・ダンプして確認した結果が以下です。大量データとは言えない量ですが、それでも、データのロードはActive Recordより37.5倍、生のSQL(INSERT)より4.6倍より速いです。データのダンプはActive Recordより4.1倍、生のSQL(SELECT)より1.8倍速いです。データが増えるとさらに速度差が出てきます。
ロード:
| 手法 | 実行時間(秒) | ADBCがどのくらい速いか |
|---|---|---|
| ADBC | 0.075787 | |
| Active Record | 2.844029 | 37.5倍 |
| SQL | 0.350767 | 4.6倍 |
ダンプ:
| 手法 | 実行時間(秒) | ADBCがどのくらい速いか |
|---|---|---|
| ADBC | 0.067801 | |
| Active Record | 0.283621 | 4.1倍 |
| SQL | 0.123201 | 1.8倍 |
詳細はリポジトリー内のベンチマークスクリプトを参照してください。
使いたくなってきましたよね?バッチ処理で大量データを扱わないとかいけないんだけどそこが遅いんだよねー、とか、データ分析用の管理画面を作りたい、とか、Ruby on RailsアプリケーションとしてBIツールを作りたい!とかいうときに使ってみてください!
DuckDBも使える
みなさん、DuckDBを知っていますか?ざっくり言うと、データ分析用のSQLiteみたいなやつです。ローカルで高速にデータ分析用のSQLを実行できます。データ分析用のSQLとは、カラムでの絞り込みや、集計や、ソートなどをたくさん使うようなSQLです。
まだ知らない人はDuckDB実践入門を読むとよいでしょう。DuckDBの解説本の翻訳です。原著は少し古いDuckDBをベースにしていますが、訳注として最新のDuckDB用の補足も入っています。これを読めばDuckDBのことが大体わかります。(レビューアーとしてちょっとお手伝いしました。)
Ruby用のDuckDBバインディングはありますが、Active Record用のアダプターはまだありません。(Red Data Toolsで作ろうとはしています。)Ruby on Railsアプリケーションから使うならActive Record経由で使いたいですよね。
Active Record経由でDuckDBを使うためにこのADBCアダプターを使えるのです。DuckDBはADBCをサポートしているからです。いくつか足りない機能がありましたが作っておきました。
RubyでもADBCでDuckDBを使ってデータ処理ですよ!
まとめ
数年前からあるといいだろうなぁと思っていたActive RecordのADBCアダプターがなんとなく動くようになったのでリリースしました。Rubyでデータ処理をするためのツールがまた一つ整備されたはずです!DuckDBも使えるしね!
まだまだやらないといけないことはいろいろあるので、興味がある人はRed Data Toolsのチャットにきてください。一緒に開発しましょう。たとえば、対応しているActive Recordの操作を増やすとか、対応しているバックエンドを増やす(テストを書く)とかがあります。また、このADBCアダプターを使ったソフトウェアも開発したいです。たとえば、BIツールです。Ruby on RailsアプリケーションとしてこういうBIツールを作れたらかっこよくないですか!?お仕事としてそういうものを開発できそうな話があったらお問い合わせフォームよりお気楽にお問い合わせください。
そうそう、インストール方法は省略したので、本当に使いたくなった人はドキュメントを参照してください。
ところで、このADBCアダプターをRuby on Rails界隈の人たちにも自慢したいんですが、いい機会を知っている人がいたら教えてください。