Amazon Keyspacesを試してみてる
- keyspacesはcassandraというデータベースのマネージドサービス
- サーバー立てなくていい
- cassandra は NoSQLの一種。ワイドカラム型のDBらしい。
ワイドカラム型のDBという概念を初めて知った。
どういうメリット、デメリットがあるのかも全くの未知だったが、調べてある程度理解した。
- primary key は partition key と clustering key の組み合わせ。
- partition key は保存領域を特定するキー。
- 保存領域の中で clustering key でソートされる。
- クエリを書けるときは partition key と一致検索してから clustering key で絞り込む。
- こうすることで抽出時の走査対象がpartition key で指定した保存領域だけに限定されるのでメモリ節約でき、速い。
- そのかわりRDBMSのような汎用性は持たない。
- 検索条件に制約も多い。
モデリングするときはどのように使われるのかを想定してモデリングしないといけない感じだと思う。
- RDBMSみたいにモデリングしてはいけない
- Primary keyの重要性を理解する
- クエリに沿った形でモデリングする
みたいなことを書いてるのかな。
今回keyspaces使いたかったのは、個人で開発してるiOSアプリから位置情報を記録、参照する用途に使いたかった。
現在地を送信して記録しておいて、最近記録された、表示範囲内の位置一覧を抽出するようなアプリ。
ユースケースが限定的なので、RDBMSでなくてもモデリングできるんじゃないかと思った。
以下のようなアプリの要件がある。
- 安く済ませたい。月100円くらいで済ませたい。データ数はそんなに発生しない見込み。
- 最近2週間の位置情報を抽出したい。それ以前のものは参照しない。
- 地図の表示領域内で検索したい。
- サーバー管理したくないのでマネージドサービスが望ましい。
- メンテしなくてもずっと動いててほしい。
RDSはインスタンス立ててるだけで金がかかるので採用したくない。
料金表見てるとたぶんRDSよりKeyspacesのほうがだいぶ安そう。
領域内検索したい(lat, lng の範囲抽出が必要)なので単なるKVSは使えない。
なのでDynamoDBは不採用。
Cassandraのデータ構造見てると、日付でpartitionを分けてlat, lng をclustering key にしたらいけんじゃないかと思ったので試してみてる。
最初に作ったテーブルはこんな感じ。(ゴミ拾いの位置記録なのでgomi_logという名前にしてる。)
実際はkeyspaces のGUIで指定した。
create table gomi_log (
created_on date,
lat double,
lng double,
log_type int,
created_at timestampuuid,
user_name text,
PRIMARY KEY (created_on, lat, lng, log_type, log_uuid)
);
primary key の先頭が partition key になる。
created_on をpartition key にして、lat, lng でソートするようにしたら、ある日の範囲内のレコード一覧が取れそう、というテーブル設計。
(最初キャメルケースでテーブル名やフィールド名を設定したら、ダブルクォーテーションで囲まないとフィールドやテーブル名を認識しなくてハマったのでスネークケースに変えた。初めて設計する人は気をつけてください。)
↓データ追加クエリ。
INSERT INTO gomi_map.gomi_log (created_on, lat, lng, log_type, created_at, user_name)
VALUES (toDate(now()), 50.0, 100.0, 1, now(), 'naomi');
↓それでこういうクエリを実行してみた。
SELECT * FROM gomi_map.gomi_log
where created_on = '2021-04-25'
and lat >= 49.9 and lat <= 50.1
and lng >= 99.9 and lng <= 100.1;
するとエラーになってしまった。
複数のclustering key への検索はできないらしい。
↓のクエリならclustering key を 1個だけ指定してるのでOK。
SELECT * FROM gomi_map.gomi_log
where created_on = '2021-04-25'
and lat >= 49.9 and lat <= 50.1
それでどうしようか悩んで考えてみて、以下のようなテーブル構成を思いついた。
create table gomi_lat (
created_on date,
lat double,
log_uuid uuid,
primary key (created_on, lat, log_uuid)
)
create table gomi_lng (
created_on date,
lng double,
log_uuid uuid,
primary key (created_on, lng, log_uuid)
)
create table gomi_log (
created_on date,
log_uuid uuid,
user_name text
log_type int,
lat double,
lng double,
created_at timestamp,
PRIMARY KEY (createdOn, log_uuid)
)
lat をclustering key にしたテーブル (gomi_lat) と、lngをclustering key にしたテーブル(gomi_lng)を追加した。gomi_log を保存したときには gomi_lat と gomi_lng にも保存する。
検索条件に指定された lat と lng を使って gomi_lat と gomi_lng から log_uuid を取得し、その積のlog_uuidを抽出する。
それを使って gomi_log から対象ログを抽出する。
↓こういう3段階のクエリで抽出する。
select * from gomi_map.gomi_lat
where created_on = '2021-04-25'
and lat >= 49.9
and lat <= 50.1;
select * from gomi_map.gomi_lng
where created_on = '2021-04-25'
and lng >= 99.9
and lng <= 100.1;
select * from gomi_map.gomi_log
where created_on = '2021-04-25'
and log_uuid in (c37d661d-7e61-49ea-96a5-68c34e83db3a);
このクエリを試してみたが、 in句がサポートされてないということで3番目のクエリがエラーになった。
色々制限があるんやな。
じゃあしょうがないから1件ずつ取って来るのがいいのかな。
select * from gomi_map.gomi_log
where created_on = '2021-04-25'
and log_uuid = c37d661d-7e61-49ea-96a5-68c34e83db3a;
これなら通った。
これで一応位置の範囲内検索が実現できた。
けどこれが良い設計なのかはよくわからない。
詳しい人に教えてほしい。