CBORをPostgreSQLに保存するとき、BYTEAかJSONBか分解保存か

  • CBOR
  • PostgreSQL
  • Database

この記事は、構成と文章の整理に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の組み合わせしかないので、検索したいフィールドがある程度決まってるなら分解しておいた方が後々楽。