Heroku の PostgreSQL 担当の人が id には uuid
を使えと発表していたのでメモ。(時間がない人は、↓の動画の15分くらいからを見ましょう)
stop using numbers as IDs.
just use UUIDs. seriously
— Postgres: The Bits You Haven’t Found by pvh
UUID の違い
v1
Generate a UUID from a host ID, sequence number, and the current time.
v3
Generate a UUID from the MD5 hash of a namespace UUID and a name.
v4
Generate a random UUID
v5
Generate a UUID from the SHA-1 hash of a namespace UUID and a name.
この内、ID として利用できるのは v1 と v4 の2つ。v1 は最後 48 ビットがハード固有のノードを表し、uuid
から生成時刻も復元できる。
各バージョンの違いは、この辺りの uuid
ライブラリ実装者からもコメントも参考になる。
pros/cons
スライドには詳細が書かれていないので hacker news の投稿から掘り起こす。
uuid の一番のメリットは、universally unique な ID であること。(UUID そのままですが)
The biggest reason is that your IDs become universally unique – across shards, across database recoveries, rollbacks and session problems, you name it. These are all the kinds of things that can happen over the lifespan of a dataset.
https://news.ycombinator.com/item?id=5310637
これ以外にも
- 外と通信せずに重複しない ID を採番できる
- ID を推測できない。ID を外に公開するようなシステムの場合、特に重要。
- うっかり別の ID を渡したり、別の ID で処理する確率がシーケンスの場合に比べて低い
などがある。
https://news.ycombinator.com/item?id=5311271
instagram ID との比較
Instagram が "timestamp + shard id + sequence"
で ID を生成するようにした時、uuid
も検討している。
Instagram にとっては ID が時系列順にソートされる必要があり、そのために "timestamp + uuid"
で ID 生成することを検討していたが、データサイズが増えることもあり、見送っている。
また、Instagram 式では ID からどのシャードにあるのか簡単に特定できるので、運用面では助かっているそうだ。
—
ここからは、PostgreSQL で uuid を実際に使ってみる。
uuid モジュール(uuid-ossp)をインストール
スーパーユーザで実行する
# \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+------------------------------ plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (1 row) # CREATE EXTENSION "uuid-ossp"; CREATE EXTENSION # \dx List of installed extensions Name | Version | Schema | Description -----------+---------+------------+------------------------------------------------- plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language uuid-ossp | 1.0 | public | generate universally unique identifiers (UUIDs) (2 rows)
PostgreSQL から uuid を生成
バージョン 1, 3, 4, 5 に対応している。
# select uuid_generate_v1(); uuid_generate_v1 -------------------------------------- e4ca39c4-8d9f-11e2-9f70-000c294a4696 (1 row) # SELECT uuid_generate_v3(uuid_ns_url(), 'http://www.postgresql.org'); uuid_generate_v3 -------------------------------------- cf16fe52-3365-3a1f-8572-288d8d2aaa46 (1 row) # SELECT uuid_generate_v4(); uuid_generate_v4 -------------------------------------- e630decd-ea28-4906-98ab-64e665183cc3 (1 row) # SELECT uuid_generate_v5(uuid_ns_url(), 'http://www.postgresql.org'); uuid_generate_v5 -------------------------------------- e1ee1ad4-cd4e-5889-962a-4f605a68d94e (1 row)
テーブルにデータを挿入
ID カラムのデフォルトを uuid v4 に設定し、データを挿入する。
CREATE TABLE t ( uuid uuid PRIMARY KEY DEFAULT uuid_generate_v4(), name text); # insert into t(name) values('a'); INSERT 0 1 # insert into t(name) values('b'); INSERT 0 1 # insert into t(name) values('c'); INSERT 0 1 # select * from t; uuid | name --------------------------------------+------ ca130384-646d-459f-88e7-6ab9aa363070 | a 1b03ade0-54f3-4875-bf4d-a4135bf66cbc | b ca6b4e72-e915-4359-8acc-3a0ea4348652 | c (3 rows)
インデックスサイズを比較
100万データを serial と
uuid
それぞれで生成し、インデックスのサイズを比較。
CREATE TABLE t_uuid ( uuid uuid PRIMARY KEY DEFAULT uuid_generate_v4(), num integer); CREATE TABLE t_serial ( id serial primary key, num integer); # insert into t_uuid(num) select n from generate_series(1, 1000000) as n; INSERT 0 1000000 # insert into t_serial(num) select n from generate_series(1, 1000000) as n; INSERT 0 1000000 -- http://wiki.postgresql.org/wiki/Index_Maintenance#Index_size.2Fusage_statistics SELECT t.tablename, indexname, c.reltuples AS num_rows, pg_size_pretty(pg_relation_size(quote_ident(t.tablename)::text)) AS table_size, pg_size_pretty(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size, CASE WHEN x.is_unique = 1 THEN 'Y' ELSE 'N' END AS UNIQUE, idx_scan AS number_of_scans, idx_tup_read AS tuples_read, idx_tup_fetch AS tuples_fetched FROM pg_tables t LEFT OUTER JOIN pg_class c ON t.tablename=c.relname LEFT OUTER JOIN (SELECT indrelid, max(CAST(indisunique AS integer)) AS is_unique FROM pg_index GROUP BY indrelid) x ON c.oid = x.indrelid LEFT OUTER JOIN ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, idx_scan, idx_tup_read, idx_tup_fetch,indexrelname FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class ipg ON ipg.oid = x.indexrelid JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid ) AS foo ON t.tablename = foo.ctablename WHERE t.schemaname='public' ORDER BY 1,2; tablename | indexname | num_rows | table_size | index_size | unique | number_of_scans | tuples_read | tuples_fetched -----------+---------------+----------+------------+------------+--------+-----------------+-------------+---------------- t_serial | t_serial_pkey | 1e+06 | 35 MB | 21 MB | Y | 0 | 0 | 0 t_uuid | t_uuid_pkey | 1e+06 | 50 MB | 39 MB | Y | 0 | 0 | 0 (2 rows)
uuid
のほうが容量を約2倍消費している。
References
- Heroku Waza 2013 : Postgres: The Bits You Haven’t Found by pvh
http://postgres-bits.herokuapp.com/ - Hacker News
http://news.ycombinator.com/item?id=5310280 - PostgreSQL Manual:8.12. UUID Type
- http://www.postgresql.org/docs/9.2/static/datatype-uuid.html
- PostgreSQL Manual : F.40. uuid-ossp
http://www.postgresql.org/docs/9.2/static/uuid-ossp.html - RFC 4122 – A Universally Unique IDentifier (UUID) URN Namespace
http://tools.ietf.org/html/rfc4122.html - Universally unique identifier (UUID) column type support has been added to Rails 4.
http://blog.remarkablelabs.com/2012/12/a-love-affair-with-postgresql-rails-4-countdown-to-2013
One thought on “シーケンスの代わりにuuidをIDとして使う”