

CBORをPostgreSQLに保存するとき、BYTEAかJSONBか分解保存か
この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。
診断表記のバイト文字列、構文を間違えてた
CBOR診断表記(CBOR Diagnostic Notation)でこう書いてた。
h`0102`これJSのテンプレートリテラル風で、CBOR診断表記としては誤り。RFC 8949 §8とRFC 8610 Appendix Gの定義だと、バイト文字列リテラルはシングルクォートで囲んで、base16なら先頭に h を付ける。
h'0102'バッククォートとシングルクォート、見た目似てるので普通に間違えた。
診断表記からCBORへの変換は仕様というよりツール依存
診断表記はJSONっぽく書けるので、そのままCBORバイナリに変換できるか気になった。
結論はできる。ただし補足がいる。RFC 8949本文の診断表記は「CBOR値を人間に提示する」方向がメインの説明で、逆方向(表記→バイナリ)を厳密に規定した変換仕様ではない。実際にやってるのは cbor-diag や cbor.me みたいなツール側の実装。
用途別に整理するとこう。
| 用途 | 一般性 |
|---|---|
| デバッグ | よく使う |
| テストデータ生成 | よく使う |
| ドキュメント | よく使う |
| 本番システムで診断表記→CBOR変換 | あまりやらない |
本番でやるなら
CBOR Diagnostic Notation → Parser → CBOR encodeという経路になるけど、普通は最初からアプリケーションオブジェクトをCBOR encodeする方が素直。
PostgreSQLへの保存方法、候補は3つ
対象はこういう形式のデータ(キー名は匿名化してある)。
{ "codes": ["0102"], "kind": "string", "range": { "start": 1, "length": 2 }, "scale": null, "base": null, "meta": { "label": "SampleTag", "suffix": "", "hidden": false, "enabled": true }}これがテーブルに大量に入る前提で、保存方法を3パターン検討した。
BYTEA一本化
CBORにエンコードしてそのままBYTEAに突っ込む。PostgreSQLのJSON/JSONBはCBORを理解できないので、CBORのまま持つならBYTEA一択になる。
メリットはシンプルなこと。デメリットは中身で検索やフィルタができないこと。
JSONBに変換して保存
バイト文字列部分(h'0102')を "0102" みたいな文字列に変換してJSONBに入れる。
{ "codes": ["0102"], "kind": "string"}PostgreSQL側から検索できるのがメリット。ただしCBOR固有のバイナリ情報(型タグとか)は失う。
カラム分解
検索に使うフィールドだけ別カラムに出す設計。
CREATE TABLE device_codes ( id UUID PRIMARY KEY, codes BYTEA[], kind TEXT, range_start INT, range_length INT, scale DOUBLE PRECISION, base DOUBLE PRECISION, meta_label TEXT, meta_suffix TEXT, meta_hidden BOOLEAN, meta_enabled BOOLEAN);codes だけBYTEA、あとはTEXTやINTなど素直な型に分ける。
結局どう設計したか
カラム分解にした。理由は3つ。
- DBで検索・部分更新がしやすい
- 型が付くので取り違えが起きにくい
- 最終的にJSONへ組み立てるのは1ステップで済む
保存から復元までの流れはこう。
flowchart TD
A[PostgreSQL device_codes] -->|SELECT| B[アプリケーション]
B --> C[JSON生成]
C --> D[CBOR encode]
D --> E[最終CBOR]
アプリ側で読み出したカラムからJSONを組み立てて、最後にencodeするだけ。
const buffer = encode(json);PostgreSQLにはバイナリとJSONを同時にネイティブで扱える型はない。BYTEA単体かBYTEA+JSONBの組み合わせしかないので、検索したいフィールドがある程度決まってるなら分解しておいた方が後々楽。