RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【NoSQL】RethinkDB 使い方

f:id:tech-rakus:20220204104832p:plain

勤怠開発課のy_konnoです。
暇があるとNoSQLを漁りだす癖があるんですが、今回はRethinkDBが気になったので試してみたので書いてみようと思います。

NoSQLとは

まず先にNoSQLについて軽く触れておきましょう。
NoSQLとはデータを関係テーブルとは異なる何らかの方法で保存するタイプのデータベースです。
キーバリュー、ドキュメント、グラフなどがNoSQLでよく用いられます。

NoSQLは特定のデータモデルに特化した設計がなされています。
このためNoSQLを用いるとより柔軟なデータ設計ができたり、パフォーマンスを高めることが容易になったり、簡単にスケールアウトする構成をとれたりします。

代表的なNoSQLプロダクトは以下のとおりです。

  • NoSQLプロダクト例
    • キーバリュー:Redis
    • ドキュメント:MongoDB
    • グラフ:Neo4j

RethinkDBとは

RethinkDBはNoSQLの一つでオープンソースのドキュメント指向データベースです。
当初RethinkDB社が開発やサポートを行っていたようですが、紆余曲折を経て現在はLinux Foundationに寄贈され、開発が継続されているようです。

データの操作はRethinkDB query language(ReQL)によって行います。
複数のReQLをチェーンしたり、遅延実行することが可能です。

大きな特徴の一つとして、更新結果をリアルタイムでPUSHする機構があります。
これにより変更をポーリングすること無く連続的に取得する事が可能となっています。

その他にもクラスタリングやMap Reduceなどの大規模向けの機能や、テーブルのJOIN、複数の2次インデックスのサポートなど非常に多岐にわたる機能が提供されています。

公式のクライアントはJavaScriptPythonRubyJavaの4つが用意されています。
他の処理系であってもコミュティによる開発が行われています。

セットアップ

サーバーはWindowsMacLinuxいずれの環境にも対応していますが、公式のDockerイメージが用意されているのでそちらを利用します。
本稿執筆時点でのバージョンは2.4.1を利用しています。

https://hub.docker.com/_/rethinkdb

docker pull rethinkdb

Pullが完了したら起動します。
RethinkDBにおける通信のポートは28015なので割当てます。
また、Webの管理コンソールも同梱されているのでついでに利用できるようにしておきます。
そちらのポートは8080です。

docker run -d -p 8080:8080 -p 28015:28015 --name rethink1 rethinkdb

RethinkDBのWeb管理コンソールはブラウザからlocalhost:8080でアクセスできます。
テーブルの確認・作成・削除やドキュメントの確認などの操作を行えます。

ひとまず、これでRethinkDBの利用準備が整いました。

基本的な使い方

まずは、RethinkDBの基本的な使い方を見てみます。
ここでは一通りCRUDをしてみようと思います。

クライアントはJavaです(JDKは11想定)依存関係の追加方法はそれぞれ以下になります。

・Gradle

dependencies {
    implementation 'com.rethinkdb:rethinkdb-driver:2.4.4'
}

Maven

<dependencies>
  <dependency>
    <groupId>com.rethinkdb</groupId>
    <artifactId>rethinkdb-driver</artifactId>
    <version>2.4.4</version>
  </dependency>
</dependencies>

接続

まずはRethinkDBへのコネクションを生成します。

RethinkDB r = RethinkDB.r;
Connection connection = r.connection()
    .hostname("localhost")
    .port(28015)
    .connect();

これだけです。
RethinkDBのインスタンスの生成時の名称(r)に若干ぞわぞわしますが、それ以外は至ってわかりやすいかと思います。
なお、コネクションの取得時にタイムアウトや認証系の情報も設定できます。

以降は、このコネクションを使ってINSERTやSELECTなどの操作を行っていきます。

DB作成・テーブル作成

RethinkDBでは、一般的なRDBのようにデータベースとテーブルがあります。Dockerのコンテナ作成時に、デフォルトでtestというデータベースを作成してくれるようです。

仮にtestデータベースが無い場合は、以下で生成できます。

r.dbCreate("test").run(connection);

今回は用意されたtestデータベース上に、salesというテーブルを作成してみます。

r.db("test")
    .tableCreate("sales")
    .run(connection);

DBコマンドでtestデータベースを選択し、そこにtableCreateコマンドでsalesを指定、あとはrunコマンドでRethinkDBサーバーへコマンドを送信するという流れです。

すでにsalesテーブルが存在する場合は、ReqlOpFailedError例外が発生します。
なお、CREATE IF NOT EXISTSのような便利機能はないので、同等のことをやる場合はtableListコマンドでテーブルをリストアップし、存在しなければtableCreateを実行するという手順を踏む必要があります。

INSERT

テーブルができたので、データを追加してみます。
一気に3件のデータを入れてみます。

List<Object> insertData = r.array(r.hashMap("name", "apple")
        .with("price", 100)
        .with("quantity", 5)
        .with("payment", "cash"),
    r.hashMap("name", "banana")
        .with("price", 200)
        .with("quantity", 3)
        .with("payment", "cash"),
    r.hashMap("name", "melon")
        .with("price", 5000)
        .with("quantity", 1)
        .with("payment", "credit")
);
r.table("sales")
    .insert(insertData)
    .run(connection);

実行後、Web管理コンソールで確認すると以下のようなデータが入っています。

id name payment price quantity
29f94218-1ef9-4c3b-9dfb-1f92a231b9e8 apple cash 100 5
1fbf055e-48ba-4061-9001-98ecf8053c1f banana cash 200 3
d94a7dba-4d33-482b-ae0d-2164557469b3 melon credit 5000 1

INSERT時にはidを指定していませんが、これはPKをテーブル作成時に指定しなかったために作成されているデフォルトのカラムになります。
値はUUIDが自動的に挿入されるので、上記の値は一例です。

UPDATE

次にUPDATEをしてみます。
先程INSERTしたmelonのドキュメントを変更してみます。

r.table("sales")
    .get("9769473a-eeb5-4b7f-b877-218cb6942796")
    .update(
        r.hashMap("price", 10000)
            .with("quantity", 10)
    )
    .run(connection);

特定のドキュメントだけを更新するにはまず、getで対象のIDを指定してUPDATEする対象を制限する必要があります。
あとはUPDATEしたい項目だけ、新しい値を指定すればOKです。

getをしない場合は、テーブル内のすべてのドキュメントを一括で更新する挙動になります。

また、ID指定ではなく特定の条件でUPDATEする場合は以下のようにfilterを使います。

r.table("sales")
    .get("9769473a-eeb5-4b7f-b877-218cb6942796")
    .update(
        r.hashMap("price", 10000)
            .with("quantity", 10)
    )
    .run(connection);

SELECT

次にSELECTをしてみます。
UPDATEと同じく、全件取得か特定のドキュメントを取得するかで若干処理が変わります。

まずは全件取得をしてみます。こちらは非常にシンプルです。

・全件取得

Result<Object> selectAllResults = r.table("sales").run(connection);
selectAllResults.forEach(e -> /* Do something. */);

単純にテーブルを指定してrunするだけです。
戻り値のResult<Object>を回せば、各ドキュメント内容を個別に処理できます。
もちろん、Streamにも対応しています。

次は、特定の項目だけフィルタして取得してみます。

・特定の項目だけ取得

Result<Object> selectFilteredResult = r.table("sales")
    .filter(e -> e.g("payment").eq("cash"))
    .run(connection);
selectFilteredResult.forEach(e -> /* Do something. */);

filterに条件を指定します。
ここでいうfilterJavaのStreamのものではなく、ReQL独自のものです。

上記の例では、paymentcashに合致するものだけを取得するようにしています。
取得後のイテレーションも全件取得と同じです。

なお、UPDATEのときのように、GETでPKを指定してドキュメントを取得することも可能です。

DELETE

最後にDELETEをしてみます。まずは簡単な全件削除から。

・全件削除

r.table("sales").delete().run(connection);

テーブルを指定して、deleteを呼ぶだけという非常に簡素で直感的な作りです。
挙動としてはTRUNCATEみたいなものですね。

次に条件にマッチしたドキュメントだけ削除してみます。

・特定の項目だけ削除

r.table("sales")
    .filter(e -> e.g("price").gt(200))
    .delete()
    .run(connection);

やはりここでも、filterを使って条件を指定します。
上記ではpriceが200超のものだけが削除されます。

Change Feeds

さて、ここまで基本的な操作をみてきましたが、やりたいのはそんなことじゃなくて、リアルタイム機能の中核であるChangeFeedsです。

Change Feedsは、RethinkDBにおけるリアルタイム機能の中核をなすものです。
テーブルやドキュメント、あるいは特定のクエリの結果などの変更を、その都度クライアントが受け取ることができます。
ほぼすべてのReQLクエリがChange Feedsの処理対象にできるようです。

早速、変更を受け取る側のコードを組んでみましょう。

・Change Feeds(サブスクライブ側)

Result<Object> changeFeeds = r.table("sales").changes().run(connection);
for (Object change : changeFeeds) {
    // Do something with change feeds.
}

そう、たったこれだけです。
あとはループの中でやりたいことを記述すれば終わりなのです。
上記の例では、salesテーブルに何らかの変更が入る都度ループ内の処理が実行されます。

このループ部分はそのままだと永久に終わらないので、ループの後かつ外に記述した処理はそのままでは実行されません。
この手の機能を使う場合、通常は稼働する限り変更を受け取って処理しようとするのでそれで問題はありませんが、任意のタイミングで止めたいようなケースでは何らかの方法でループを抜け出すように手を加える必要があります。

いじってみた感想

ここまで、RethinkDBの基本的な使い方とChangeFeedsについてみてきました。
ReQLは癖がなく記述しやすい印象を受けました。
あと、ChangeFeedsによる変更の受け取りが極めて楽なことには驚きました。

NoSQLは数も多くそれぞれ得手不得手が分かれるので、うまいことハマるユースケースがあったら採用してみたいところですね。


  • エンジニア中途採用サイト
    ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
    ご興味ありましたら是非ご確認をお願いします。
    20210916153018
    https://career-recruit.rakus.co.jp/career_engineer/

  • カジュアル面談お申込みフォーム
    どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
    以下フォームよりお申込みください。
    rakus.hubspotpagebuilder.com

  • イベント情報
    会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
    rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.