課題
DynamoDB で時系列データを扱うときに参考になる記事は、まず DynamoDB で時系列データを処理するベストプラクティス ですが、降順にデータを取り出そうとするとパーティションキーを指定する必要があり、クエリ実行にひと手間必要です。
Primary Key | Attributes | |||
---|---|---|---|---|
Partition Key | Sort Key | Attr1 | Attr2 | ... |
2024-01-01 | 00:00:00.000 | ... | ... | ... |
2024-01-01 | 00:00:01.000 | ... | ... | ... |
2024-01-02 | 00:00:00.000 | ... | ... | ... |
2024-01-02 | 00:00:01.000 | ... | ... | ... |
... | ... | ... | ... | ... |
- 降順ソート可能な Query はパーティションキーの指定が必要
- Scan は降順ソートできない
ひと手間とは、パーティションキーに現在日を指定し、1日づつ過去日に遡って複数回 Query を実行することです。ちょっと面倒ですね。
RDBMS だと...
RDBMS だと、以下のような日時とメッセージを持つテーブルに対して
create table logs (
partition_key date not null,
sort_key time not null,
attribute_1 text
);
日時の降順でデータを取り出す SQL は、次のように簡単なんですけどね。
select * from logs order by parition_key desc, sort_key desc;
解決策
ではどのように DynamoDB で時系列の降順スキャンを実現するか、1つの解決策を紹介します。
メインの Key Schema か Global Secondary Index で次の 3点を行います。
- パーティションキーを数字、ソートキーを文字列で定義する
- パーティションキーには、日でまるめた負の UnixTime を保存する
- ソートキーには UUID V7 の XOR 値を保存する
これだけで Scan が降順になります!
実行例
Partition Key(日で丸めた負の UnixTime) | Sort Key (UUID V7 の XOR) |
---|---|
-1704034800 | fe6f5b49-df3a-8ecf-596f-e78e69ad53c2 |
-1704034800 | fe6f5b49-5c9e-8e33-61fe-813dc262ced2 |
... | ... |
ポイント
UUID V7 はランダム性を備えつつ、作った順にソート可能な識別子です。そのまま使うと昇順になってしまうので一工夫して XOR 演算します。以下 go での UUID の XOR 例です。
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
uu := uuid.Must(uuid.NewV7())
fmt.Printf("Before %s\n", uu.String())
for i, u := range uu {
uu[i] = ^u
}
fmt.Printf("After %s\n", uu.String())
}
実行結果
Before 0190a4cd-d3e7-7222-9f99-fdb74feee752
After fe6f5b32-2c18-8ddd-6066-0248b01118ad