OLTA TECH BLOG

テクノロジーと好奇心で事業を成長させる

TECH BLOG

ULID移行から最適な自己流IDの設計まで・その1【背景編】:ULIDに移行する話

◇ アノキミモノガタリその壱

アノ日のULID、キミとまた出会えるまで十億億回の輪廻のタイムスリップ、A.D.10889年まで。

君の名は、01FZG96YPZK4SANAG1ZM5T2K9Z、忘れないその目。

令和4年4月1日、ミッドナイトの零時23分、55秒の999ミリ秒、

キミと、出会えた。

君の名は、01FZG96YPZK4SANAG1ZM5T2K9Z、忘れないその一目惚れ。

なんと可愛らしく、愛しいその目、忘れられない。

はじめに

こんにちは、転生したら相変わらずエンジニアだった OLTA(オルタ)プロダクトグループ研究開発チームのタイトルの前置きがとにかく長い B.です。

INVOYカードの開発を担当しています。

今回、UUID*1RFCRFC4122からRFC9562に刷新されたことをきっかけに、約2年間眠っていたテックブログの記事を加筆して発表しようと思います。

2024年5月に正式的に発表されたRFC9562では、time-basedでソート可能なUUID v7が新しく提案されました。UUID v7ではULID*2の利点もたっぷり吸収されたようで、いまさら振り返ってみるとULIDの素晴らしさに対して再認識できた部分もあるので、ULIDとUUIDv7との比較の部分も追加で書きました。また、もしULIDや既存のID仕様などでは、システムのニーズに合わない場合、どのように最適な自己流のID仕様を設計するのかについても詳しく解説しました。

*1 UUID: Universally Unique Identifier, 汎用的一意識別子。

*2 ULID: Universally Unique Lexicographically Sortable Identifier, 汎用的一意の辞書式にソート可能な識別子。

ULIDを採用した背景

INVOYは新しいカード払い機能でULIDを採用しました。

カード払いとはBPSP*3の文脈において、手元の請求書をクレジットカードで支払いできるとても便利なサービスです。

今度は、整数型自動採番(auto increment)方式から、ULIDに成功に移行できた事例として参考になればうれしいです。

*3 BPSP: Business Payment Service Provider, 商業決済代行業者。

請求書発行から受取、支払いまで素早くかんたんにできるクラウド請求書プラットフォーム「INVOY」

自分がOLTAに入社してからの半年くらいの間に、新人の皆さんと同じように「いろいろなことを改善したい!」というワクワクした気持ちでした。技術負債の解消はスタートアップ企業にとって非常に重要な課題です。

ユーザーにとって必要な価値を迅速に提供するためには、素早い開発が重要であり、それを維持するためには定期的な技術負債の解消が求められます。そこで、いつ、どこまで行うのかについて、優先度・期間・機運などを同時に考えた上で、決断力が求められると思います。

例えば、INVOYでは2021年7月から9月に3ヶ月間もかけて、プロダクトの開発を3ヶ月間も止めてバックエンドとフロントエンドのコードを成功に分離させるリアーキテクチャを成功させました。これは「天の時、地の利、人の和」が揃った、機運の良い技術負債の解消の成功例だと思います。

もちろん、技術負債の解消という名目の改善が必ずしも良い結果をもたらすとは限りません。特にスタートアップ企業において早期の過度な「改善」は逆に技術負債を増やすリスクもあります。今回、リリース直前にULIDへ移行することを決断した理由には、事業の成長に伴うセキュリティ面やユーザーエクスペリエンスの改善などがあります。ただ、正直なところ、この「改善」は本当に良い結果をもたらすのかと不安もありました。しかし、約2年経った今から振り返ってみると、急速に成長してきたカード払いの事業を見て、今回ULIDへの移行を決断したことは本当に良かったと思います。

整数型自動採番に関する思惑

整数型自動採番は特に悪いところばかりではありません。まず、メリットとして以下の点が挙げられます。

  • 自然数で連番なので親近感があり、分かりやすい。

  • ほとんどのデータベース管理システムではデフォルトで使える。

  • B木索引方式(Balanced Tree)を採用したデータベース管理システムとは相性がよく、性能も優れている。

  • データベースのテーブル規模をすぐに把握できる。

そのため、個人プロジェクトや実験的なプロダクト、相対的に小中規模のサービスなどでは、問題なく利用できると考えられます。

一方で、デメリットとして以下の点が挙げられます。

  • リソースIDとして連番だとURLから推測しやすいため、特定のシステムではセキュリティリスクが高い。

  • レコード数が推測できる。
  • それを使ってトラクションを測ることもできる。

したがって、整数型自動採番を採用すべきかどうかはケースバイケースになります。

ULID到来のきっかけ

INVOYのシステム構成の技術スタックの一部として、PythonベースのDjangoフレームワークと、Django REST Framework (DRF) APIと、PostgreSQL関係データベース管理システムを採用しました。DRFデフォルトではモデルのid領域に整数型自動採番が利用されます。

すると、新しい機能において、もしソートもでき、利用できる範囲も少なくとも100億以上と十分に大きく、また推測しにくい乱数文字列のような番号を利用したい場合、どうすれば良いのか悩みました。

典型的な Django Framework エコシステム構成の例

まず整数型自動採番の代替案として、最初は UUID v4を採用しようと考えていました。

しかし、UUID v4はソートできない痛点もあり、データベースの B+tree 構造のインデックスにおいて実行効率が低下するため、採用に躊躇しました。

この時、前職で使っていた MongoDB の ObjectID のようなものがあればいいなと思い、Slackの個人times分報チャンネルでつぶやきました。すると、先輩からほぼ同時に「ULIDですかね」と教えられ、その瞬間に「あ、まさにこれじゃないですか?!」と目が閃きました。

今までの伝統的な関係データベース管理システム(RDBMS)において、特にMySQL系のRDBMSでは、各記録の主キー(PK)の自動採番は主に自然数1, 2, 3...のような1ずつ増える感じでのauto increment方式が主流です。許容できる範囲は符号付き整数型の最大値までです。

例えば、32ビットの整数型の場合、1ビットの符号を除いて約21億5千万まで許容できます。

# 2^31-1 = 2,147,483,647 ≒ 2.15e+9
>> 2**31-1
2147483647

さらに、もし64ビットの整数型の場合、同じく1ビットの符号を除いて約922京まで許容できます。いわゆる大桁整数 BigInt 型です。

# 2^63-1 = 9,223,372,036,854,775,807 ≒ 9.22e+18
>> 2**63-1
9223372036854775807

NoSQL系データベース代表であるMongoDB
一方、MongoDBを代表とするNoSQL系データベースの誕生により、UUIDのような一意性(uniqueness)を持ち、かつ auto_increment のように単調的に増えるソート可能なObjectIDという自動採番方式も注目されるようになりました。
その一番の特徴は、最初の数桁にUNIX時刻を採用し、実世界の時間の単調増加の特性を利用することで、UUIDのソートできないデメリットをある程度克服しました。

また、UNIX時刻部分の後ろに、大抵数十ビットのランダム文字列の部分を加えることで、2つのIDが衝突 (hash collision) する可能性を極めて低い数十億分の一以上に抑えています。許容できる範囲も32ビット符号付き整数型の最大値2,147,483,647より何億倍もはるかに広がります。ただし、安全で容量が大きい代わりに、96-bit128-bitなど、作成できる範囲が大きく増えるにつれてIDを保存するために消費するデータベースのストレージ空間も増えるため、これが良いかどうかはケースバイケースです。

例えば Twitter が2010年に発足した Snowflake は、同じく時間ベースで分散システムでも単調増加が担保できる一意識別子であり、バランス良くBigIntと同じサイズの64-bitを採用しました。しかし、残念ながらSnowflakeプロジェクトは2014年5月末にOSSとしての開発が中止されました。また、Snowflakeを実運用するためのインフラ整備は非常に大変で、気軽に導入できるものではありません。

もちろん、時間ベースの方式にもデメリットがあります。たとえば、もしTwitterやStackOverflow、Qiita、Zennなどのようなユーザー生成コンテンツ(UGC, User Generated Contents)をメインとするサービスでは、すべてのポストのURLがユーザーに見えてしまいます。このような神のような俯瞰視点で見ると、秒精度ないしミリ秒やマイクロ秒精度で、ユーザーの増加率とか、文章の増加率とか、重要な商業情報やサーバー性能のセキュリティ情報などがバレてしまいます。

一方、INVOYのような個人や企業様向けのサービスでは、自分の権限内のものしか見れないため、特にデメリットではありません。ただし、プライバシー的にURLを自ら他人へ共有した場合、作成の時刻がバレてしまうことには注意が必要です。この点を考慮し、INVOYの請求書共有リンクの機能では時間情報を含まないUUID v4を採用しました。

まとめ

以上は背景篇の内容です。主になぜULIDを採用したかについて話しました。次の基礎編では、全面的に整数型ID、Snowflake ID、MongoDBのObjectID、ULIDまたUUIDなどの特徴、比較と選択について詳しく述べます。この記事、もしご参考になればとてもうれしいです。

OLTAでは Tech Vision の元、一緒にユーザーに価値を提供し、その結果事業を成長させるサービス作り続けるための仲間を募集しています。 もし、この投稿にご興味を持っていただいたら、是非カジュアルにお話しさせてください。

corp.olta.co.jp

 

IDシリーズ記事の一覧

techblog.olta.co.jp