<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Id-Generation on Rindrics Mumbles</title>
    <link>https://rindrics.com/tags/id-generation/</link>
    <description>Id-Generation</description>    
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Mon, 28 Sep 2026 00:00:00 +0900</lastBuildDate>
    <atom:link href="https://rindrics.com/tags/id-generation/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>データ登録処理に関する技術選定: Revision IDの生成アルゴリズム</title>
      <link>https://rindrics.com/posts/tech-selection-revision-id/</link>
      <pubDate>Mon, 28 Sep 2026 00:00:00 +0900</pubDate>
      
      <guid>https://rindrics.com/posts/tech-selection-revision-id/</guid>
      <description>&lt;p&gt;data-registryによるRevision ID生成アルゴリズムにUUID v7を選択した。&lt;/p&gt;
&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;資源評価に利用するデータの永続化を担うdata-registryの開発をすすめている。








&lt;div class=&#34;card&#34;&gt;
  &lt;a href=&#34;https://rindrics.com/tags/data-registry/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34; class=&#34;card-link&#34;&gt;
    &lt;div class=&#34;card-content&#34;&gt;
      &lt;h4 class=&#34;card-title&#34;&gt;Tag &amp;#34;data-registry&amp;#34; | rindrics.com&lt;/h4&gt;
      
      &lt;span class=&#34;card-url&#34;&gt;https://rindrics.com/tags/data-registry/&lt;/span&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;../tech-selection-idempotency-key-hash-algorithm/&#34;&gt;前回の記事&lt;/a&gt;では、冪等性キー生成に用いるハッシュアルゴリズムを選んだ。
本記事では、永続化処理が実行される際のRevision IDの生成方法について考える。&lt;/p&gt;
&lt;h2 id=&#34;要件&#34;&gt;要件&lt;/h2&gt;
&lt;p&gt;現時点で、data-registryには下記のユースケースが見つかっている:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;版の履歴閲覧: あるデータセットの変更履歴を時系列で表示したい&lt;/li&gt;
&lt;li&gt;データ登録要求の並行受信: 複数人のユーザーが複数シナリオを並列計算し、それらのクライアントからの登録要求を受信した&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上記から、Revision IDの生成は、以下の要件を満たす必要がある:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;要件&lt;/th&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;説明&lt;/th&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;要件ID&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;一意性&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;各Revisionは一意のIDを持つ&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;SR001&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;不変性&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;一度生成されたら変更されない&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;SR002&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;時系列順序付け可能性&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;同じRevision chain内で自然にソート可能&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;SR019&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;分散生成可能性&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Coordinatorなしで各プロセスから生成可能&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;SR020&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ここで、不変性（&lt;code&gt;SR002&lt;/code&gt;）については実装の責務として考慮外とし、残りの要件を満たすID生成アルゴリズムを選定する。
アルゴリズムには複数の候補が考えうるが、詳細な検討の手間を軽減するには、まず時系列順序付け可能性を根拠に選択肢を刈り込むとよさそう。&lt;/p&gt;
&lt;h2 id=&#34;候補アルゴリズム&#34;&gt;候補アルゴリズム&lt;/h2&gt;
&lt;p&gt;ID群が時系列で順序付けされるためには、IDは時刻情報を含むtimestamp-basedな手法で生成される必要がある。&lt;/p&gt;
&lt;p&gt;これに着目すると、例えば&lt;a href=&#34;https://github.com/twitter-archive/snowflake&#34;&gt;Nanoid&lt;/a&gt;やUUID v3, v4, v5, v8は検討対象から除外でき、候補としては下記が残る:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;連番&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/twitter-archive/snowflake&#34;&gt;Snowflake ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.rfc-editor.org/info/rfc9562/#section-5.7&#34;&gt;UUID v7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なお、timestamp-basedな手法としてはUUID v1およびv6も含まれるが、&lt;a href=&#34;https://www.rfc-editor.org/info/rfc9562/#section-5.6:~:text=6-,UUIDv6,instead&#34;&gt;これらと比較した場合にはv7が推奨されている&lt;/a&gt;（スーパー意訳）ため検討候補から除外した。&lt;/p&gt;
&lt;h2 id=&#34;候補アルゴリズムの比較検討&#34;&gt;候補アルゴリズムの比較検討&lt;/h2&gt;
&lt;h3 id=&#34;連番-不採用&#34;&gt;連番（✘ 不採用）&lt;/h3&gt;
&lt;p&gt;その名の通り、連番の整数をIDに使う方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ID: 1, 2, 3, 4, ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この手法の利点はストレージのオーバーヘッドが少ないことだろう。
表現はシンプルだが、それを実現するアーキテクチャや実装は必ずしもシンプルとは限らない。
2種類の実現方法「データベースレベル」「アプリケーションレベル」それぞれについて、pros/consを整理してみる。&lt;/p&gt;
&lt;h4 id=&#34;方法a-データベースレベル&#34;&gt;方法A: データベースレベル&lt;/h4&gt;
&lt;p&gt;PostgreSQL の SERIAL/BIGSERIAL（自動採番機能）を利用する方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pros:
&lt;ul&gt;
&lt;li&gt;✔ 採番の責務をデータベースに持たせられる&lt;/li&gt;
&lt;li&gt;✔ ロック時間が短い&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cons:
&lt;ul&gt;
&lt;li&gt;✘ 単一のprimary writerに依存&lt;/li&gt;
&lt;li&gt;✘ スケーラビリティ: 並行タスクが全て同じDBに集約&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;方法b-アプリケーションレベル&#34;&gt;方法B: アプリケーションレベル&lt;/h4&gt;
&lt;p&gt;採番の責務をアプリケーションで持つ構成。
この方法をとるとデータベースについては水平スケールが自由になるが、結局ほアプリケーションレベルに移ってきた採番の責務がボトルネックになる。&lt;/p&gt;
&lt;p&gt;つまり、連番形式は宿命的に「最後の値は何か」というグローバルな状態管理に依存する方法で、この状態は必ずどこかに集約されることになる。
結果として、&lt;code&gt;SR020&lt;/code&gt;（分散生成可能性）を満たさないため不採用とする。&lt;/p&gt;
&lt;h3 id=&#34;snowflake-id-不採用&#34;&gt;Snowflake ID（✘ 不採用）&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake&#34;&gt;Twitter&lt;/a&gt;などの大規模分散システムで運用実績のある手法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ID: 175638572750991360
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     ├─ timestamp(ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     ├─ machine id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     └─ sequence
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;pros:
&lt;ul&gt;
&lt;li&gt;✔ 大規模分散システムでの運用実績あり&lt;/li&gt;
&lt;li&gt;✔ ストレージ効率がよい&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cons:
&lt;ul&gt;
&lt;li&gt;✘ Machine IDの事前割り当てが必須（新規マシン追加時に管理が必要）&lt;/li&gt;
&lt;li&gt;✘ 各machineが自身のsequenceを管理する必要がある&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ストレージ効率の良さは魅力的だが、プラットフォームの潜在ユーザーの規模を考慮すると、machine IDの割り当てなどの運用オーバーヘッド増加がペイされるとは考えにくい。
data-registryにおける採用は見送ることにする。&lt;/p&gt;
&lt;h3 id=&#34;uuid-v7-採用&#34;&gt;UUID v7（✔ 採用）&lt;/h3&gt;
&lt;p&gt;UUIDの一つ。
タイムスタンプベースのフィールドを備えつつ、同タイプの先行バージョン（v1およびv6）に比べてランダム特性が向上している。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ID: 0189a5c5-a92b-7b47-b5ad-e6c3cb6c41e9
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   └─ from timestamp ─┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;pros:
&lt;ul&gt;
&lt;li&gt;✔ 決定的に生成できるため各ノードで独立生成可能&lt;/li&gt;
&lt;li&gt;✔ 高いB-tree局所性による高速書き込み&lt;/li&gt;
&lt;li&gt;✔ 標準性（cf: &lt;a href=&#34;https://www.rfc-editor.org/info/rfc9562/#section-5.7&#34;&gt;RFC 9562&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;✔ エコシステム充実: Clojureでも&lt;a href=&#34;https://github.com/danlentz/clj-uuid&#34;&gt;ライブラリ&lt;/a&gt;を利用可能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cons:
&lt;ul&gt;
&lt;li&gt;✘ タイムスタンプ衝突にはランダム&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;部分のみで対処する必要がある
&lt;ul&gt;
&lt;li&gt;TODO: ランダム部分のビット長が最悪ケースをカバーするか確認する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;短所もあるものの、これらはdata-registryのユースケースにおいては問題にならないかもしれないので、実際に試算してみる。&lt;/p&gt;
&lt;h4 id=&#34;同タイムスタンプ内における一意性違反リスク&#34;&gt;同タイムスタンプ内における一意性違反リスク&lt;/h4&gt;
&lt;p&gt;UUID v7では、同じ&lt;a href=&#34;https://www.rfc-editor.org/info/rfc9562/#section-5.7:~:text=Layout-,unix%5Fts%5Fms,5%29%2E&#34;&gt;ミリ秒&lt;/a&gt;内に複数のIDを生成する場合、一意性の保証はランダム部分（74ビット）で対処する必要がある。
この問題をキューイングなどのアーキテクチャで解決しない場合において、UUID v7のエントロピー性能がdata-registryの最悪ケースをカバーできるかを見積もる。&lt;/p&gt;
&lt;p&gt;この問題は&lt;a href=&#34;https://en.wikipedia.org/wiki/Birthday_problem&#34;&gt;誕生日問題&lt;/a&gt;と同じなので、並行プロセス数を\(n\)は、ランダム部分のビット長を\(b\)とすると、衝突確率は下式で表される:&lt;/p&gt;
$$
P(\text{1組以上が衝突}) = 1 - \frac{1}{e^{\frac{n (n - 1)}{2^{b+1}}}}
$$&lt;p&gt;いま1,000,000の並行プロセス（1,000ユーザー × 1,000シミュレーション）が同ミリ秒内にIDを生成する場合、\(n = 10^6\)、\(b = 74\)なので、衝突確率は&lt;/p&gt;
$$
\begin{aligned}
P(\text{1組以上が衝突})
  &amp;= 1 - \frac{1}{e^{\frac{10^6 (10^6 - 1)}{2^{75}}}} \\
  &amp;\approx 1 - \frac{1}{e^{\frac{10^{12}}{3.8 \times 10^{22}}}} \\
  &amp;\approx \frac{10^{12}}{3.8 \times 10^{22}} \\
  &amp;\approx2.63\times10^{-11} \\
  &amp;= 2.63\times10^{-9} \text{\%}
\end{aligned}
$$&lt;p&gt;となり、ピーク時リクエストに対してアーキテクチャによる緩衝を挟まない場合にも、UUID v7のエントロピー性能は充分な安全マージンを持つことがわかった。&lt;/p&gt;
&lt;p&gt;以上から、data-registryにおけるRevision IDの生成したアルゴリズムにはUUID v7を採用する。&lt;/p&gt;
&lt;h2 id=&#34;所感&#34;&gt;所感&lt;/h2&gt;
&lt;p&gt;本記事でも、&lt;a href=&#34;../tech-selection-idempotency-key-hash-algorithm/&#34;&gt;前回の記事&lt;/a&gt;と同様に、最悪ケースに対するキャパシティ見積もりに基づいて技術選定した。
実際には、最悪ケースに対してアーキテクチャ的な解決を挟まない場合にはID衝突以前に別の問題（スループットなど）が生じそうだが、ひとまずの物理的な猶予は把握できた。
大規模アーキテクチャにおける成功例: Snowflake IDも一応は検討したが、個人開発における運用性を考えると選べなかった。&lt;/p&gt;
&lt;p&gt;次回は、Revision IDと冪等性キーの永続化方法について考える予定。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;疑似乱数であり、実際は決定的に生成する。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
    </item>
    
  </channel>
</rss>
