リソースのID、GUIDにするべきかSequentialにするべきか?
(twitter.com/dylayed)特定のリソースを一意に指定するIDをどう作るかという話になると、一般的には大きく2つの方法がよく使われていると思います。1つはDBテーブルのPrimary KeyにAuto Incrementを設定し、そこで得られる連番の整数値をそのまま使う方法で、もう1つはランダムな128ビット値であるGUID(UUIDとも呼ばれます)をその都度生成して使う方法です。
Webで見られる数多くのサービスのデータはRDBMSがかなりの部分を担っており、この種のDBMSのAuto Incrementは内部的に最適化されているうえ、使う開発者の立場から見ても理解しやすく予測しやすく、データが入った順に並べるのも簡単です。単に数字を1ずつ増やしていくだけだからです。しかしこの方法には、特定のケースでは本来セキュリティ上公開したくない情報を外部に露出してしまう可能性があること(たとえば競合他社に自社サービスのユーザー数など主要指標を容易に察知されてしまう)や、分散アーキテクチャで問題になり得ることなどの欠点があります。
GUIDを使う方法は、上とは特徴が正反対です。GUIDは他の依存関係なしに衝突の可能性がゼロに近い、事実上固有の128ビット値をその都度生成して使うため、分散アーキテクチャでもまったく問題がないうえ、外部に別の意味のある情報を意図せず漏らしてしまう心配もありません。しかし、ランダムに生成された値をRDBMSに書き込むことは性能低下を招くことがあり、またそれ自体ではデータが入った順に並べ替えることもできません。こうした弱点を補うために、完全なランダムではなく時間情報を加えて不完全な連続性を持たせたTimeflakeのようなものを使う場合もあります。私自身は直接使ったことはありませんが、Laravelのようなフレームワークでもこの方式が使われているそうです。
個人的には、いま勤めている会社でMicrosoftのOffice 365やGraph APIなど、GUIDを積極的に使う製品と連携するプロダクトを開発していることもあって、GUIDを積極的に使うやり方もかなり悪くないのではないかと思うようになりました。ただ結局のところ、こうした選択は用途や目的によって何がより良いかが変わるものなので、それぞれの方法の長所と短所を明確に把握しておくのがよいでしょう。そこで、これに関連する架空のサービス開発者の日誌を綴ったTwitterスレッドを紹介します。
15件のコメント
最近、シンハンカードで不正利用事故が発生しましたが、これに関連して、当該カード会社がクレジットカード番号を連番で発行していたため、海外からの不正利用にさらされる可能性があるリスクが確認されました。
番号を少し変えただけで「決済」…盗用にさらされたクレジットカード
金融監督院、最近のシンハンカード不正利用などに対する対策を検討
皆さんがコメントを付けてくださったおかげで、よく分かっていなかったこともたくさん知ることができました。
おかげで、Hashids や Nano ID、Instagramが使用している方式 といったものも初めて知りました。
ULIDと似たような動機ですが、提案されているInternet Draftがあったので、私は以前のプロジェクトでこの仕様を使っていました。
https://github.com/uuid6/uuid6-ietf-draft
このように作られるID体系はよく見かけますが、少なくともUUIDライクなものだけでも、そろそろ1つに統一されるべき時期のように思います。
でも、乱立する標準をひとつに統一する新しい標準を作ろうという試みは、たいてい市場に新たな競争相手をひとつ増やすだけだと言いますよね。www
https://xkcd.com/927/
そうですね(笑)。だからみんな新しいIDの案を出しているんでしょうね。
少し前にギュウォンさんが共有してくださっていましたが、実はシンプルな問題ではないでしょうか?
https://byterot.blogspot.com/2013/02/…
私も「シンプルな問題」という話について、補足説明が欲しいです
どういう点でシンプルな問題だとおっしゃっているのでしょうか?
この記事では "With storage nowadays very cheap, this normally is not a problem from the storage point of view." と述べられていますが、ネットワーク上でこの ID が行き来しなければならない場合や、メモリ上でも key の役割を果たさなければならない場合、大容量データで各所で使われるキーである場合など、数バイトでも削減することが重要なケースがあるため、状況によっては UUID を採用しないべき場面もあるように思います。
この記事で述べられている問題は、ランダムに生成された値を primary key として使うときに生じる性能低下のことですが
(ほかの問題にも触れられていて、私が見落としているだけなら教えてください)
その問題にはすでに答えがあります。時系列順の cursor based pagination を行うときと同じ問題なので、みなさんすでに解決したことがあるはずです。
どの点で複雑な問題なのか、私も気になります。
おっしゃっている状況は断るべき状況だとのことですから、シンプルな問題のように思えますが……
複雑な問題というのは、ああでもないこうでもないと結論を出せないときこそ複雑な問題なのではないでしょうか?
「シンプルな問題」という言葉の意味にはいろいろな解釈の余地があるので、どの意味で使われたのか気になってお聞きしました。問題そのものが難しくないという意味なのか、それとも記事で提示されている明快な答え(?)があるため悩む余地が少ないという意味なのか、などです。
前者については上でも述べたように、状況によってはIDがデータベース外でも通用する必要があるため、考慮すべき要素が多くなり、単純な問題ではないと思います。
python/django にはこういうものもあります https://pypi.org/project/django-hashid-field/1.0.0/
おお、Hashidsという方法もあるのですね。
もし Salt が漏えいした場合は、本文で言及されていた情報の外部露出という問題が生じる可能性はありますが、それでも良い方法だと思います。
ULIDもあります。128bitで、時系列順にソート可能です。
https://github.com/ulid/spec
見た目がどれも似たり寄ったりなのを見ると、人の考えることなんてだいたいみんな同じなのかもしれない…?