時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

Google Cloud Firestore datastore modeをいい感じに操作するやつを作った

Google Cloudにおいて、古くはApp Engine Datastore、その後にCloud Datastore、現在はCloud Firestore datastore modeという形で存在するものがある。 俗に単にDatastoreと呼ばれることが多いと思うので、このエントリでは単にDatastoreと呼ぶことにする。

知らない方向けに簡単に説明すると、このDatastoreはスキーマレスでキーバリューストアライクな構造を持つGoogle Cloudのマネージドなドキュメントデータベースになっている。いわゆるNoSQLデータベースの一種。

キーバリューストアとはいうが、インデックスを貼ることである程度柔軟な検索をすることが可能で、キーを分散させる*1ことによってよしなにスケールしてくれるデータベースとして楽に大きなトラフィックを捌くことができるデータベースになってくれる。*2

という感じで、そういった大量のトラフィックと戦うことが多い人にとっては便利なデータベース製品になっている。

しかし、DatastoreにはWebUIこそあるもののCLIツールが提供されていない。

これは地味に不便で、ちょっとしたことをするにもスクリプトを書いて書き換えたり、WebUIからぽちぽちしたりなどする必要がある。 データ量が多いとそもそもそんな簡単にはいかないのだが、開発中にデータ量を気にせずに簡単なデータマイグレーションをするようなケースでもそういうことを要求されるのでちょっと困る。

たとえば、いくつかの限られたデータを他の開発環境からコピーしてきたいようなときでも、そういうスクリプトをわざわざ書かなければならない。 地味に面倒くさい。

dsioというやつもある*3けど、これはマスターデータのインポートとちょっとしたCLIからのデータ参照に便利って感じのもので自分が求めていたものとは少し違った。詳細は本題から逸れ過ぎてしまうので割愛する。

そこで、そういう感じのことが簡単にできるツールを開発した。

github.com

もともと datastore-cli という名前で開発していたが、データストアとのio以外の機能もちょっと入れたくなってきたので dutil という名前に変更した。

dutil io というサブコマンドの下にDatastoreを操作するためのCLIインターフェースが実装されている。

たとえば、あるEntityをキーで指定して取得する場合には dutil io lookup を使う。

% dutil io lookup -p karupanerura 'key(Example, "key1")'
{"key":{"kind":"Example","name":"key1"},"properties":[{"type":"string","value":"string","name":"s","noIndex":true},{"type":"timestamp","value":"2024-02-23T02:23:00Z","name":"d","noIndex":true},{"type":"entity","value":[{"type":"string","value":"value","name":"key"}],"name":"e","noIndex":true},{"type":"float","value":0.5,"name":"f","noIndex":true},{"type":"geo","value":{"lat":0,"lng":0},"name":"g","noIndex":true},{"type":"key","value":{"kind":"Example","name":"key1"},"name":"k","noIndex":true},{"type":"array","value":[{"type":"string","value":"string"}],"name":"a"},{"type":"bool","value":true,"name":"b"},{"type":"string","value":"text\ntext\ntext","name":"t","noIndex":true},{"type":"int","value":123,"name":"i","noIndex":true},{"type":"null","value":null,"name":"n","noIndex":true}]}

このように、入出力はすべてJSON Linesに統一されているのでgrepやsedやjqなどと組み合わせることも簡単。 データ型も明示的なので、たとえばユーザーの自由入力が絡む場面で意図しない型で扱われてしまうといったこともない。

そして、これだけでも jq などで整形して見やすくすることができるが、それでもちょっと面倒なときは dutil io table でJSON形式をテキストテーブル形式に変換する機能も作った。 これは今日GoConで感化されてrange over funcのインターフェースに沿った感じで実装した。

% dutil io lookup -p karupanerura 'key(Example, "key1")' | dutil convert table
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+
| Kind    | Name | a[0]   |    b |                             d | e.key |   f | g                                 | i   | k                   | n    | s      | t    |
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+
| Example | key1 | string | true | 2024-02-23 02:23:00 +0000 UTC | value | 0.5 | geo(lat: 0.000000, lng: 0.000000) | 123 | KEY(Example,"key1") | NULL | string | text |
|         |      |        |      |                               |       |     |                                   |     |                     |      |        | text |
|         |      |        |      |                               |       |     |                                   |     |                     |      |        | text |
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+

こんな感じで表示できる。

他にもGQLをそのまま投げられたりなど様々な機能がある。

そんなわけで、個人的には仕事の中で割と便利に使っています。

Datastoreを使っている皆様方におかれましてはぜひ使ってみて感想を聞かせていただけると嬉しいです。

*1:裏側でレンジシャーディング的なことが行われるので、シーケンシャルなアクセスが発生しにくいように分散させる。

*2:むかしはその代償として制約がかなり厳しかったが、最近はかなり緩和されて使いやすくなっている。

*3:正直、辿り着けなくて後から存在を知ったけど