Groonga deltaというMySQL/MariaDBのデータをリアルタイムでGroongaに同期するツールを開発した須藤です。どのような使い方・設計・実装になっているかを説明します。
機能の説明
Groonga deltaは次の機能を提供します。
- MySQL/MariaDB内にあるデータを一括でGroongaに取り込む機能
- MySQL/MariaDBへの
INSERT
/UPDATE
/DELETE
の結果をリアルタイムでGroongaに取り込む機能 - データ取り込み時にデータを加工する機能
これらの機能により次のようなユースケースを実現できます。
- マスターデータはMySQL/MariaDBで管理し、全文検索はGroongaで実現する
- アプリケーションは検索の仕方以外Groongaのことを知らずに済みたい
- 検索性能をスケールアウトしたい
GroongaをMySQL/MariaDBに組み込むMroongaでも同様のことは実現できるのですが、MroongaはクラッシュセーフではないためInnoDBでデータを管理している場合に比べて運用コストが上がる点がネックです。Groonga deltaのアプローチではInnoDBでマスターデータを管理できます。Groongaが持っているデータが壊れたとしてもInnoDBのマスターデータを再度取り込んで復旧できるためMroongaがマスターデータを持っている場合に比べて運用コストが上がりにくいです。ただし、GroongaサーバーなどMySQL以外にも管理対象が増えるという点で運用コストは上がります。
実現方法の概要
Groonga deltaがやっていることは端的に言うとレプリケーションです。MySQL/MariaDBがソースでGroongaがレプリカのレプリケーションです。
実際、「MySQL/MariaDBへのINSERT
/UPDATE
/DELETE
の結果をリアルタイムでGroongaに取り込む機能」はMySQL/MariaDBのレプリケーション機能で実現しています。MySQL/MariaDBにレプリケーションクライアントとして接続し「差分情報(binlog)を取得してGroongaに取り込む」ということをし続けています。ただし、取得した差分情報をそのままGroongaに取り込むわけではありません。一旦、取得した差分情報をストレージに書き出し、別途書き出された差分情報をGroongaに取り込みます。
違う:
|MySQL|ーbinlog→|Groonga delta|ー差分情報→|Groonga|
Groonga deltaのアプローチ:
|MySQL|ーbinlog→|Groonga delta|ー差分情報→|ストレージ|
|ストレージ|ー差分情報→|Groonga delta|→|Groonga|
直接Groongaに取り込むアプローチでは「Groongaが落ちるとデータを取りこぼすかもしれない」という問題があります。これは次のときに発生します。
- Groongaが落ちている間はbinlogを処理できない
- MySQLは古いbinlogを削除するかもしれない
- 未処理のbinlogが削除されるとデータを取りこぼす
一方、Groonga deltaのアプローチではGroongaに直接取り込むのではなくストレージに保存するのでストレージが生きていればbinlogの取り込みは続けられます。Groongaよりストレージの方が安定しているはずなのでGroongaに直接アプローチよりもデータの取りこぼしリスクが下がります。
またGroonga deltaのアプローチだとMySQLから取得した差分情報を何度でも取り込めるというメリットがあります。これは次のときに嬉しいです。
- GroongaのDBが壊れてデータを再度取り込まないといけなくなったとき、MySQLに接続しなくても取り込める
- 複数のGroongaインスタンスに取り込める
- 複数のGroongaで検索を処理する検索システムを構築できる(スケールアウトできる)
デメリットはストレージを消費することです。
Groonga deltaは次の2つのサービスからなります。
groonga-delta-import
groonga-delta-apply
groonga-delta-import
がMySQLのbinlogを取得して差分情報をストレージに書き出すサービスです。データの加工もgroonga-delta-import
の仕事です。
groonga-delta-apply
がストレージに書き出された差分情報をGroongaに取り込むサービスです。
これらの実現方法の詳細を説明する前に使い方を説明します。
使い方
groonga-delta-import
とgroonga-delta-apply
はローカルにインストールして使うこともできますがGroonga deltaが提供しているDockerイメージを使うことを推奨します。具体的にはghcr.io/groonga/groonga-delta
というイメージを使ってください。最新バージョンを使う場合はghcr.io/groonga/groonga-delta:latest
で特定のバージョンを使う場合(たとえば1.0.0)はghcr.io/groonga/groonga-delta:1.0.0
です。
このイメージの中にgroonga-delta-import
もgroonga-delta-apply
も入っているので次のように使います。
docker run --rm \
ghcr.io/groonga/groonga-delta:latest \
groonga-delta-import --server ...
docker run --rm \
ghcr.io/groonga/groonga-delta:latest \
groonga-delta-apply --server ...
ただし、設定ファイルや各種データを書き込むための場所を用意する必要があるので、実際はホストのパスをボリュームとしてマウントして使うことになります。詳細はこの後に説明するので、ここではghcr.io/groonga/groonga-delta
イメージを使えばすぐにgroonga-delta-import
コマンドとgroonga-delta-apply
コマンドを使えるということがわかれば十分です。
それではそれぞれのコマンドのより細かい使い方を説明します。
groonga-delta-import
の使い方
まずgroonga-delta-import
用のディレクトリーを用意します。ここではimport/
ディレクトリーを用意します。
mkdir -p import
このディレクトリーの直下にconfig.yaml
を用意します。設定ファイルになります。
editor import/config.yaml
次のような内容になります。delta_dir
とlog_dir
のディレクトリーを../
とimport/
ディレクトリーと同じレベルに置いていることがポイントです。log_dir
は別によいのですが、delta_dir
は後で設定するgroonga-delta-apply
と共有する(groonga-delta-import
がdelta_dir
に出力した差分情報をgroonga-delta-apply
がGroongaに取り込む)のでimport/
以下ではなくimport/
と同じレベルに置いてある方が便利なのです。
delta_dir: ../delta # 差分を保存するディレクトリー
log_dir: ../log # ログを保存するディレクトリー
local: # 静的に用意するGroongaコマンドの設定
# スキーマや初期データやインデックス定義に使う
dir: local # Groongaコマンドが保存されているディレクトリー
initial_max_number: 99 # MySQLから初期データを取り込む前に
# どこまでのGroongaコマンドを使うか
# (詳細は後述)
mysql: # データを持っているMySQLサーバーの設定
# binlog_formatはROWでないといけない!!!
host: 192.168.0.100 # IPアドレス
replication_slave:
# レプリケーション用のユーザー
# REPLIACATION SLAVE権限だけあればよい
user: "replicator"
replication_client:
# レプリケーション関連の初期化用のユーザー
# SHOW MASTER STATUSなどを実行する
# REPLIACATION CLIENT権限だけあればよい
user: "c-replicator"
select:
# 初期データ取り込み・テーブルのメタデータ取得用のユーザー
# SELECT権限だけあればよい
user: "selector"
mapping:
# MySQLのデータをどのようにGroongaに取り込むかの設定(後述)
...:
config.yaml
とは別にsecret.yaml
という同じ構造の設定ファイルも使うことができます。secret.yaml
はMySQL接続用のパスワードなど秘密の情報を入れておき、必要なユーザーだけが読めるようにします。(config.yaml
の中にパスワードも書いてsecret.yaml
を使わないという使い方もできます。)
touch import/secret.yaml
chmod go-rwx import/secret.yaml
editor import/secret.yaml
たとえば、次のようにMySQL接続用のパスワードを設定します。
mysql:
replication_slave:
# レプリケーション用のユーザーのパスワード
password: "replicator-password"
replication_client:
# レプリケーション関連の初期化用のユーザー
password: "c-replicator-password"
select:
# 初期データ取り込み・テーブルのメタデータ取得用のユーザー
password: "selector-password"
「MySQLのデータをどのようにGroongaに取り込むかの設定」(mapping
)について説明する前にlocal
について説明します。
MySQLからGroongaにデータを取り込むにはGroonga側にデータを取り込む場所がないといけません。つまり、データを取り込む先のテーブルやカラムが必要です。また、高速に検索するためにはインデックス定義も必要です。そのようなMySQL上の各レコードと直接対応しないGroonga上の操作のためにlocal
は存在します。ちなみ、local
はローカルにあるファイルからデータ(Groongaコマンド)を取り込むという意味です。
今回はimport/local/
以下にGroongaコマンドを配置するという設定にした(local.dir
をlocal
にした)ので、import/local/
以下にどのようにファイルを配置するかを説明します。
import/local/
以下のファイルは数字から始める必要があります。たとえば、000_logs.grn
とか001_users.grn
といった感じです。groogna-delta-import
はその数字を10進数の数値として解釈して昇順にソートします。ソートしたら順番に実行します。
数値は連続していなくても構いません。たとえば、000_logs.grn
と002_groups.grn
だけしかなくても構いません。そのため、0
から99
までは初期スキーマ定義、100
から199
までは初期インデックス定義、200
以降は導入後のスキーマ変更というように範囲で使い方を決めて運用にすることになります。このとき重要なのがMySQLから初期データを取り込む(binlogからの差分取り込みではなくすでにMySQLに入っているデータを取り込む)前に実行したいか後に実行したいかの境目です。境目を判断するときの基準は「テーブル定義・プラグイン読み込み」は前で「インデックス定義」は後です。なぜかというとインデックスを作ってからデータを入れるよりデータを入れてからインデックスを作る方が高速だからです。データを入れてからインデックスを作ると静的なインデックス構築を使えます。
たとえば、次のようなスキーマを使いたいとします。
# ログ
table_create logs TABLE_HASH_KEY ShortText
column_create logs message COLUMN_SCALAR ShortText
# ユーザー
table_create users TABLE_HASH_KEY ShortText
column_create users age COLUMN_SCALAR UInt8
# logs.messageを全文検索するためのインデックス
table_create terms lexicon TABLE_PAT_KEY ShortText \
--default_tokenizer TokenBigram \
--normalizer NormalizerNFKC130
column_create terms logs_message \
COLUMN_INDEX|WITH_POSITION \
logs message
この場合、logs
テーブルとusers
テーブルの定義はMySQLから初期データを取り込む前に実行して、terms
テーブルは後に実行するとよいということです。
これを制御するための設定が後で説明するとしていたlocal.initial_max_number
です。↑の設定では99
にしていました。この数値の設定まではMySQLから初期データを取り込む前に実行します。ということで、次のようなファイル構成にします。
# import/local/000_logs.grn
# ログ
table_create logs TABLE_HASH_KEY ShortText
column_create logs message COLUMN_SCALAR ShortText
# import/local/001_users.grn
# ユーザー
table_create users TABLE_HASH_KEY ShortText
column_create users name COLUMN_SCALAR ShortText
column_create users birthday COLUMN_SCALAR Time
# import/local/100_logs_index.grn # ←99より大きい!
# logs.messageを全文検索するためのインデックス
table_create terms lexicon TABLE_PAT_KEY ShortText \
--default_tokenizer TokenBigram \
--normalizer NormalizerNFKC130
column_create terms logs_message \
COLUMN_INDEX|WITH_POSITION \
logs message
これでテーブル定義はMySQLから初期データを取り込む前に実行し、インデックス定義は後に実行します。
運用後にスキーマを変更する場合はimport/local/
以下にファイルを追加していくことになりますが既存のファイルより大きい数値にすることを忘れないでください。そうしないと新しく追加したファイルが処理されません。たとえば、↑の例だと099_groups.grn
を追加しても処理ません。101_groups.grn
など100_logs_index.grn
の100
よりも大きな数値にします。
それでは後で説明するとしていた「MySQLのデータをどのようにGroongaに取り込むかの設定」(mapping
)について説明します。
これは非常に重要な設定でいろいろな機能があります。少し長くなりますが一通り説明します。
mapping
は次のようにGroonga側のテーブル名をキー、そのGroonga側のテーブルにどのようにデータを入れるかを指定するのが値のマッピング(このマッピングはYAML用語のマッピング)です。
mapping:
Groongaのテーブル名1:
... # どうやって↑にデータを入れるか
Groongaのテーブル名2:
... # どうやって↑にデータを入れるか
...
MySQL側の複数のテーブルのレコードを1つのGroongaのテーブルに集約することもできます。これはどうしてかというと、Groongaのスキーマを設計するときは同時に検索したいものを1つのテーブルに集約することがすごく重要だからです。詳細は階層構造データ用のGroongaのスキーマ設計方法を参照してください。ということで、次のように複数の取り込み元のMySQLのテーブルを書けます。データを取り込むときはGroongaの_key
が必須です。_key
がないとMySQL側のレコードとGroonga側のレコードを同定できず、DELETE
/UPDATE
を同期できません。
mapping:
users: # 取り込み先のGroongaのテーブル名
sources:
- database: app1 # 取り込み元のMySQLのデータベース名
table: admins # 取り込み元のMySQLのテーブル名
columns:
# キーは取り込み先のGroongaのカラム名
# 値は取り込まれる値
# _keyは必須!
# 値の中の%{id}は取り込み元のMySQLのidカラムの値に展開される
_key: "app1-admins-%{id}"
# 値の中の%{name}は取り込み元のMySQLのnameカラムの値に展開される
name: "%{name}"
- database: app2 # 取り込み元のMySQLのデータベース名
table: operators # 取り込み元のMySQLのテーブル名
columns:
# キーは取り込み先のGroongaのカラム名
# 値は取り込まれる値
# _keyは必須!
# 値の中の%{id}は取り込み元のMySQLのidカラムの値に展開される
_key: "app2-operators-%{id}"
# 値の中の%{full_name}は取り込み元のMySQLのfull_nameカラムの値に展開される
name: "%{full_name}"
↑の例ではcolumns
の中には単純にGroongaのカラムとMySQLのカラムの値からGroongaの値を作るためのテンプレートを指定していました。Groongaのカラムの型が文字列(ShortText
とか)の場合はこれでよいのですが、数値(UInt8
とか)や時刻(Time
)の場合は明示的に型を指定しないといけません。指定しなくてもGroongaのキャスト機能で動くこともあるのですが、指定した方が安全です。適切な型を指定するには次のようにtype
を指定します。
mapping:
users: # 取り込み先のGroongaのテーブル名
sources:
- database: app1 # 取り込み元のMySQLのデータベース名
table: admins # 取り込み元のMySQLのテーブル名
columns:
_key: "app1-admins-%{id}"
birthday:
# 値はtemplateに書く
template: "%{birth_year}-%{birth_month}-%{birth_day}"
# 型はtypeに書く
type: Time
単純な文字列の連結でGroongaのカラムの値を作れない場合は次のようにexpression
を使ってRubyの式を書くことができます。expression
を使う場合、MySQLのどのカラムを使うかをsource_column_names
で指定しないといけないことに注意してください。groonga-delta-import
は必要なカラムのみMySQLから取得することで無駄なデータの転送による性能低下を抑えているからです。source_column_names
から漏れているとMySQLから該当カラムを取得していないのでエラーが発生します。
mapping:
logs: # 取り込み先のGroongaのテーブル名
sources:
- database: app1 # 取り込み元のMySQLのデータベース名
table: logs # 取り込み元のMySQLのテーブル名
columns:
_key: "app1-logs-%{id}"
message:
# 式はexpressionに書く
# %{message}ではなくmessageだけでMySQLのカラム名を参照できる
# stripはRubyが提供している先頭と最後の空白すべてを削除するメソッド
expression: |
category + ": " + detail.strip
# expressionで使っているMySQLのカラム名をすべて列挙する
source_column_names:
- category
- detail
restriction
を使うことで取り込むレコードを制限することもできます。今のところ時刻の値が特定の範囲内のデータにあるかだけで制限できます。これはGroonga deltaを開発した案件のユースケースではこの機能だけで十分だったからです。どういうときに使うかというと、MySQLにGroongaでは扱えない範囲の時刻(たとえば0000-01-01
とか)が入っていた場合にそのレコードを無視するために使います。
mapping:
logs: # 取り込み先のGroongaのテーブル名
restriction:
# 時刻型の値が1970-01-01から2100-01-01の範囲外にあるカラムが
# 1つでもあるレコードは処理対象外とする
time:
min: "1970-01-01T00:00:00Z"
max: "2100-01-01T00:00:00Z"
mapping
の書き方は以上です。
これでgroonga-delta-import
用のディレクトリーimport/
を使う準備ができました。import/
ではなくimport/
の親ディレクトリーをボリュームとしてマウントします。これは、import/config.yaml
の中で../delta/
など親ディレクトリーの直下にあるディレクトリーを参照しているからです。マウントしたらgroonga-delta-import --dir
でマウントしたパスの下にあるimport/
を指定して使います。
docker run --rm \
--volume $PWD:/var/lib/groonga-delta \
ghcr.io/groonga/groonga-delta:latest \
groonga-delta-import \
--server \
--dir /var/lib/groonga-delta/import
groonga-delta-apply
の使い方
続いてgroonga-delta-apply
の使い方を説明します。groonga-delta-apply
も専用のディレクトリーを用意します。ここではapply/
ディレクトリーを用意します。
mkdir -p apply
このディレクトリーの直下にconfig.yaml
を用意します。設定ファイルになります。
editor apply/config.yaml
次のような内容になります。
log_dir: ../log # ログを保存するディレクトリー
local: # ローカルにある差分情報を取り込む
# groonga-delta-importのdelta_dirで指定したディレクトリーと同じ場所
delta_dir: ../delta
groonga: # 取り込み先のGroongaの設定
# GroongaサーバーのURL
url: "http://192.168.0.200:10041"
# Groongaサーバーからのレスポンスがあるまでのタイムアウト。
# 初期データを取り込むときに1つのリクエストで
# 100万件のレコードを取り込むことがあるので長めに設定。
read_timeout: 1800
groonga-delta-apply
にはgroonga-delta-import
にあったようなGroongaデータとMySQLデータのマッピングとかが必要ないのでそれほど設定することはありません。
これでgroonga-delta-apply
用のディレクトリーapply/
を使う準備ができました。apply/
ではなくapply/
の親ディレクトリーをボリュームとしてマウントします。これは、apply/config.yaml
の中で../delta/
など親ディレクトリーの直下にあるディレクトリーを参照しているからです。マウントしたらgroonga-delta-apply --dir
でマウントしたパスの下にあるapply/
を指定して使います。
docker run --rm \
--volume $PWD:/var/lib/groonga-delta \
ghcr.io/groonga/groonga-delta:latest \
groonga-delta-import \
--server \
--dir /var/lib/groonga-delta/apply
運用時の設定
動作検証する分には手動でdocker run
してもよいのですが実運用で手動起動はありえません。ということで実運用時はサービス化します。サービス化の実現例としてDocker deltaを使った案件でのやり方を紹介します。
AlmaLinux 8を使ったのでDockerではなくPodmanを使いました。Podmanにはsystemdの設定を生成する機能があるのでそれを使ってサービス化しました。
まず、Groonga delta用の専用ユーザーgroonga-delta
を用意します。設定ファイルは/home/groonga-delta/
以下に置きます。実行ユーザはgroonga-delta
にします。
systemdの設定は次のようなスクリプトをgroonga-delta
ユーザーで実行すると用意できます。sudo
の部分は別のスクリプトにして別のユーザーで実行した方がよいかもしれません。実際はAnsibleで設定したのでこのスクリプトは使っていません。
for service in import apply; do
container_name=groonga-delta-${service}
# コンテナーを作る
podman create \
--env TZ=Asia/Tokyo \
--name ${container_name} \
--replace \
--user $(id -u groonga-delta):$(id -g groonga-delta) \
--volume /home/groonga-delta:/home/groonga-delta:z \
ghcr.io/groonga/groonga-delta:latest \
groonga-delta-${service} \
--server \
--dir /home/groonga-delta/${service}
# systemdの設定を生成する
podman generate systemd \
--files \
--name \
--new \
--no-header \
${container_name}
# podmanのログをjounalctlで見やすいのでpodmanのデーモン化をやめる
sed -i -e 's/ -d / /g' container-${container_name}.service
# インストール
sudo -H cp container-${container_name}.service /etc/systemd/system/
sudo -H systemctl --daemon-reload
sudo -H systemctl enable --now container-${container_name}
done
実現方法の詳細
使い方を説明したので実現方法の詳細を説明します。使うだけの人はここは飛ばしてもよいです。
binlog取得機能の実装
MySQLからbinlogを取得する機能はmariadb-connector-cを使っています。これはMariaDBが提供するMySQL/MariaDBのクライアントライブラリーです。MySQLが提供するクライアントライブラリーはMySQLにレプリケーションクライアントとして接続する機能が入っていないのですがmariadb-connector-cには入っているのでmariadb-connector-cを使っています。
なお、MySQL/MariaDBが提供するmysqlbinlog
コマンドを使ってbinlogを取得する実装も入っているのですがあまり安定した実装にできなかったので非推奨です。
ただ、開発時にはmariadb-connector-cのレプリケーションクライアント機能に問題があったので改良してパッチを送ってあります。次のように必要な変更は取り込まれているので最新のmariadb-connector-cでは期待通り動きます。
- Add support for ROWS_EVENT V2 by kou · Pull Request #188 · mariadb-corporation/mariadb-connector-c
- Use mariadb_free_rpl_event() for freeing MARIADB_RPL_EVENT by kou · Pull Request #189 · mariadb-corporation/mariadb-connector-c
ただ、↑の改良が入ったmariadb-connector-cのパッケージがない環境もまだ多いので前述のDockerイメージを使うことを推奨しています。あのDockerイメージにはまだ取り込まれていないパッチも含んだmariadb-connector-cが入っています。
Groonga deltaはRubyで実装されているのでmariadb-connector-cにレプリケーションクライアント機能があってもRubyバインディングがなければ使えません。Rubyにはmysql2というMySQL提供のクライアントライブラリーにもmariadb-connector-cにも対応したバインディングがあるのですが、mariadb-connector-cにだけあるレプリケーションクライアント機能のバインディングは含まれていません。
ということでmysql2にレプリケーションクライアント機能のバインディングを追加するバインディングを開発しました。それがmysql2-replicationです。
mariadb-connector-cのレプリケーションクライアント機能は受信したbinlogをパースする機能を提供していません。Rubyでbinlogをパースする機能を実装したmysql_binlogがあるのでそれを使ってパースしてもよいのですが、今回はbinlogの流量がかなり多い環境でも使いたく、速度が遅いとイヤだったのでCで実装しているmysql2-replication内でbinlogをパースしています。速度は比較していないのですがmysql_binlogより速いはずです。すごく速いといいな。
なお、mysqlbinlog
コマンドを使った実装のときはmysql_binlogでbinlogをパースしています。
差分情報の保存方法
差分情報は次のようにストレージに保存します。各ファイルにタイムスタンプがついていることがポイントです。
. -- schema -- 2021-04-05 -- 2021-04-05-15-26-06-436686418.grn
| | +- 2021-04-05-15-26-07-436686418.grn
| | +- 2021-04-05-15-26-08-436686418.grn
| +- packed -- 2021-04-05-15-26-06-436686418 -- 2021-04-05-15-26-06-436686418.grn
| +- 2021-04-05-15-26-08-436686418 -- 2021-04-05-15-26-07-436686418.grn
| +- 2021-04-05-15-26-08-436686418.grn
+- data -- ${TABLE1} -- YYYY-MM-DD -- YYYY-MM-DD-hh-mm-ss-NNNNNNNNN-ACTION.parquet
| | +- ...
| +- ...
| +- packed -- ...
+- ${TABLE2} -- ...
.
.
.
groonga-delta-apply
は自分がどのタイムスタンプまで差分を適用したかという情報を持っていて、適用済みのタイムスタンプより後のタイムスタンプの差分情報があったらタイムスタンプが古い順に適用します。こうすることでMySQLのデータと同期できます。なお、↑の設計を考えた後で知ったのですがDelta Lakeも同じようなアプローチでデータを同期していました。
packed
というディレクトリーは最適化のためのディレクトリーです。小さな差分情報がたまり過ぎると1から差分を取り込み直すとき(システムの検索性能を上げるために新しいGroongaインスタンスを作るときとか)にオーバーヘッドが大きいです。そのため、ある程度差分情報がたまったら差分情報を大きな塊にしてしまいます。これがpacked
というディレクトリーに置く情報です。groonga-delta-apply
は最初の取り込み時にだけまずpacked
があるかを探し、あればそれを適用し対応する未packed
の差分情報は使いません。これで1から差分を取り込むときの速度を上げます。なお、packed
を取り込む機能は実装されていますが作る機能は現時点で実装されていないので実質使えません。
スキーマの差分情報はGroongaコマンド形式で保存しますが、データの差分情報は可能な限りApache Parquet形式で保存します。これはデータをより速く処理するためです。GroongaにデータをロードするにはJSON形式かApache Arrow形式を使う必要がありますが、高速なデータ交換のために設計されたApache Arrowの方が高速です。Apache Parquet形式で保存されたデータは高速にApache Arrow形式に変換できるのでApache Parquet形式で保存しておくとGroongaへの取り込みが速くなるのです。
(schema/
とdata/
を分ける必要あるの?とかファイル名のACTION
ってなに?とか気になる人がいたとして、気にしているだけじゃなく実際に私に聞いてきたら説明します。)
今後の改良案
実装の詳細を一通り説明したので今後の改良案について説明します。現時点では今の機能で十分なのですが、今後の案件次第で次のような改良ができたらいいなぁと思っています。
- PostgreSQL対応
- Groongaのレプリケーションに応用
- Amazon S3やGoogle Cloud Storageなどオンラインストレージ対応
- Groongaクラスターに対する検索機能の追加
PostgreSQLのロジカルレプリケーション機能を使うとMySQLのbinlogを使ったレプリケーションのようなことができます。そのため、PostgreSQLからリアルタイムでデータを取得する仕組みも同じように実現できるはずです。
Groongaにはレプリケーション機能がなく、Fluentdとfluent-plugin-groongaを使ったレプリケーションなどで実現する必要があります。groonga-delta-import
がGroongaのように振る舞うことでGroongaクライントから差分情報を取得して保存することができるはずです。つまり、groonga-delta-import
ベースでGroongaのレプリケーションを実現できるはずです。fluent-plugin-groongaを使ったアプローチはpush型のレプリケーション(ソースがレプリカに差分情報をpush)なので、レプリカの追加が大変(レプリカはどうやって初期データを用意する?)だったりソースに負荷が集まりやすかったりします。一方、Groonga deltaのアプローチはpull型のレプリケーション(レプリカが差分情報をpull)なので、レプリカの追加が容易(ストレージから差分情報を取得するだけ)だったりソースに負荷が集中したりしません。ということで、Groongaのレプリケーションの実現に使えるとよさそうな気がしませんか?
ローカルのストレージだと複数のマシン上にあるGroongaサーバーで差分情報を共有することが難しいです。オンラインストレージに差分情報を保存したりオンラインストレージから差分情報を取り込めたりできるとGroongaクラスターの構築が簡単になります。差分情報の読み書き処理はすでにモジュール化してあるのでキレイに実現できるはずです。
今のGroonga deltaの仕組みでも(NFS上に差分情報を保存すれば)Groongaクラスターを作ることはできますが、大きなデータを分割してクラスター内の各Groongaサーバーが処理できるわけではありません。各Groongaサーバーはすべて同じデータを持っていて各サーバーで処理を完結させるだけです。そのため、クラスターを組んでも組まなくても扱えるデータ量は同じです。Groonga deltaを使って各サーバーで持つデータを分散し、検索時に分散したデータをそれぞれのサーバーで検索しその結果をマージすれば、クラスターを組むことでより大きなデータを扱うことができます。
という感じでいろいろ広がりがありそうだと思っています。
まとめ
Groonga deltaというMySQL/MariaDBのデータをGroongaに同期するツールの使い方・設計・実装を紹介しました。Mroongaとは違ったアプローチでMySQL/MariaDB内のデータを全文検索したい人は試してみてください。
PostgreSQLのデータでも同じようなことがしたい!という人はGroongaのサポートサービスを検討してください。
2022年4月から毎週火曜日の12:15-12:45にこのような技術的な話をGroonga開発者に直接聞ける「Groonga開発者に聞け!(グルカイ!)」というYouTube Liveを始めています!connpassのGroongaグループまたはYouTubeのGroongaチャンネルに登録しておけば通知が届くので活用してください。