Mengapa Anda Harus Menghindari Kunci Utama UUID Versi 4 di Postgres
(andyatkinson.com)- UUID v4 memiliki tingkat keacakan yang tinggi sehingga memicu inefisiensi indeks dan I/O berlebihan, dan jika digunakan sebagai kunci utama di PostgreSQL dapat menurunkan performa
- Karena penyisipan berlangsung secara acak, page split dan fragmentasi indeks lebih sering terjadi, serta menyebabkan ukuran log WAL membesar dan latensi tulis meningkat
- UUID berukuran 16 byte, dua kali lebih besar daripada bigint, yang berujung pada penurunan cache hit ratio dan pemborosan memori
- Sering disalahpahami sebagai pengenal yang aman, tetapi menurut RFC 4122, UUID bukan sarana keamanan untuk mencegah tebakan
- Untuk database baru, disarankan menggunakan kunci berbasis sequence bertipe integer, dan bila tak terhindarkan, gunakan UUID v7 yang berurutan berdasarkan waktu
Masalah performa UUID v4
- Database PostgreSQL yang menggunakan kunci utama UUID v4 selama 10 tahun terakhir secara konsisten menunjukkan penurunan performa dan I/O berlebihan
- UUID v4 menghasilkan 122 bit acak sehingga indeks tidak bisa diurutkan
- Saat penyisipan, data tidak disimpan ke halaman yang berurutan sehingga terjadi akses acak, dan saat update maupun delete juga diperlukan penelusuran yang tidak efisien
- Indeks B-Tree mengasumsikan data terurut, tetapi UUID v4 tidak memiliki keterurutan sehingga efisiensi insert rendah
- Setiap insert ditulis ke halaman acak sehingga page split di tengah sering terjadi
- Akibatnya muncul latensi tulis dan peningkatan WAL
Struktur UUID dan alternatifnya
- UUID adalah pengenal berukuran 128 bit (16 byte), dan di PostgreSQL disimpan sebagai tipe binary uuid
- UUID v4 berbasis bit acak, sedangkan UUID v7 menyertakan timestamp pada 48 bit pertama sehingga efisiensi indeks lebih baik
- PostgreSQL 18 (direncanakan pada 2025) akan mendukung UUID v7 secara bawaan
- UUID v7 dapat diurutkan secara kronologis sehingga kepadatan halaman dan efisiensi cache meningkat
Alasan memilih UUID dan keterbatasannya
- UUID digunakan saat dibutuhkan pembuatan pengenal tanpa benturan di lingkungan multi-klien atau microservices
- Contoh: membuat ID secara bersamaan di beberapa instance database
- Namun, RFC 4122 menyatakan “jangan menganggap UUID sulit ditebak”, sehingga tidak cocok sebagai pengenal keamanan
- Probabilitas benturan mencapai 50% setelah menghasilkan 2.71×10¹⁸ UUID; dalam praktiknya peluang benturan rendah, tetapi biaya performanya tinggi
Inefisiensi ruang dan I/O pada UUID
- UUID memakan ruang dua kali lipat dari bigint (8 byte) dan empat kali lipat dari int (4 byte)
- Pada tabel besar, hal ini menyebabkan kebutuhan penyimpanan meningkat serta waktu backup dan restore bertambah
- Hasil eksperimen kepadatan halaman indeks
- indeks integer: 97.64%
- indeks UUID v4: 79.06%
- indeks UUID v7: 90.09%
- Dalam pengujian Cybertec, pencarian pada indeks UUID v4 membutuhkan tambahan 8.5 juta akses halaman, dengan I/O naik 31229%
- Dalam kondisi yang sama, indeks bigint membutuhkan 27,332 akses buffer, sedangkan UUID v4 membutuhkan 8,562,960 akses buffer
Dampak pada cache dan memori
- Karena distribusinya acak, UUID memiliki buffer cache hit ratio yang rendah
- Lebih banyak halaman harus dimuat ke cache, dan halaman yang dibutuhkan lebih sering mengalami eviction
- Penurunan efisiensi cache menimbulkan latensi query dan kenaikan penggunaan memori
- Untuk menjaga performa, disarankan melakukan rekonstruksi indeks berkala (
REINDEX CONCURRENTLY) atau menggunakan pg_repack
Cara mengurangi dampak performa
- Menambah memori: disarankan menyediakan RAM 4 kali ukuran database (contoh: DB 25GB → memori 128GB)
- Menyesuaikan
work_mem: performa dapat meningkat dengan mengalokasikan lebih banyak memori untuk operasi sort - Di lingkungan Rails, gunakan pengaturan
implicit_order_columnagar memakai kolom yang bisa diurutkan seperticreated_at, bukan UUID - Dengan perintah
CLUSTER, tabel dapat ditata ulang berdasarkan kolom yang bisa diurutkan, tetapi memerlukan exclusive lock
Rekomendasi kunci integer dan sequence
- Untuk database baru, disarankan memakai kunci berbasis sequence bertipe integer
integer(4 byte) menyediakan sekitar 2 miliar nilai unik, sedangkanbigint(8 byte) menyediakan jauh lebih banyak
- Sebagian besar aplikasi bisnis sudah cukup dengan integer, sementara layanan berskala besar lebih cocok menggunakan bigint
- Sebagai alternatif yang realistis selain UUID v4, gunakan UUID v7 atau ekstensi sequential_uuids
Ringkasan
- UUID v4 menyebabkan inefisiensi indeks, I/O tinggi, dan efisiensi cache rendah karena sifatnya yang acak
- UUID v4 tidak dapat digunakan sebagai pengenal keamanan dan juga boros ruang
- Kunci sequence bertipe integer lebih cocok untuk sebagian besar aplikasi
- Jika UUID memang harus digunakan, pilih UUID v7 yang berurutan berdasarkan waktu
- Sebaiknya hindari penggunaan
gen_random_uuid()sebagai kunci utama di PostgreSQL
1 komentar
Komentar Hacker News
Ini adalah contoh klasik optimisasi prematur
Memasukkan data ke dalam pengenal permanen adalah hal yang tabu dalam pengelolaan data
Jika tanggal lahir dimasukkan ke ID seperti nomor identitas penduduk Norwegia, nanti bisa muncul imigran yang ternyata tanggal lahirnya dulu salah diketahui, atau masalah kehabisan nomor karena terlalu banyak yang lahir pada 1 Januari
Pada era katalog kartu dulu, mencampur data dan pengenal untuk menekan biaya pencarian masih bisa dipahami, tetapi sekarang kita punya database yang kuat jadi tidak perlu sejauh itu
Masalahnya adalah menetapkan ulang tahun yang tidak diketahui sebagai 1 Januari, bukan soal memasukkan tanggal ke dalam kunci itu sendiri
Kalau memakai nilai non-tanggal seperti 00 atau 99, bentrok tidak akan terjadi
Memasukkan timestamp ke UUID bukan untuk memberi makna, melainkan demi optimisasi performa
Kunci yang meningkat seiring waktu mengurangi biaya penulisan ulang B-tree dan meningkatkan performa insert DB
“Jangan memasukkan data ke pengenal permanen” hanyalah prinsip umum; tergantung situasinya kita bisa saja menerima trade-off
Misalnya, jika hash md5 dipakai sebagai UUID untuk membangun indeks, fragmentasi memang muncul tetapi masih pada tingkat yang bisa dikelola
Pilihan antara UUID acak vs berbasis waktu bisa menghasilkan perbedaan performa dalam hitungan detik, bukan milidetik
Di DB skala besar, sharding dan distribusi itu wajib sehingga UUID bekerja lebih baik daripada auto-increment
Kalau formatnya DDMMYYXXXXX, itu bisa menampung sampai 100 ribu orang, jadi saya ingin tahu apakah memang bisa menumpuk sebanyak itu
Mungkin ini situasi khusus seperti masuknya pengungsi dalam jumlah besar pada tahun tertentu
UUID tidak boleh dipakai seperti token keamanan
Menggunakannya sebagai fitur keamanan hanya karena sulit ditebak itu berbahaya
Tujuan nilai acak bukan cuma mencegah tebakan, tetapi juga menyembunyikan hubungan antar-ID yang berurutan
Bergantung pada jenis DB, strategi PK bisa benar-benar berbeda
Di Postgres, PK acak tidak efisien, tetapi di DB terdistribusi seperti Cockroach atau Spanner justru kunci yang meningkat monoton menimbulkan masalah hot shard
UUIDv7 punya bit atas yang bisa diurutkan dan bit bawah yang acak, jadi bisa mendapatkan distribusi antarnode sekaligus efisiensi penyimpanan lokal
Pada DB umum yang tidak di-shard, kunci acak memicu fragmentasi B-tree
Tetapi jika range query sering dilakukan, kunci acak menjadi merugikan
Pada akhirnya pilihan harus disesuaikan dengan karakteristik workload
Inti tulisan ini memang tepat dalam menyoroti kekurangan UUIDv4 sebagai PK, tetapi metode obfuscation bilangan bulat yang diajukan tampak tidak cocok untuk layanan produksi
Untuk DB kecil, UUIDv7 adalah kompromi yang masuk akal
Karena saya tidak ingin waktu pembuatannya terekspos
Selama datanya tidak cukup besar sampai keacakan UUIDv4 menimbulkan masalah performa, v4 adalah pilihan yang lebih aman
Ada sedikit kebocoran informasi, tetapi secara operasional cukup samar
Misalnya diubah dengan AES-128 lalu dienkode dengan base64 agar terlihat seperti ID video YouTube
Dalam due diligence teknis saya melihat banyak perusahaan, dan kemungkinan melakukan sharding dengan cepat adalah kunci pertumbuhan perusahaan
Jika semua tabel memakai UUID, saat sharding kita bisa melakukan ekspansi tanpa perubahan struktur
Ini memberi keuntungan skalabilitas yang jauh lebih besar daripada sedikit kerugian ruang dan waktu
Pada akhirnya, karena model datanya rumit, migrasinya sendiri lebih sulit daripada ada atau tidaknya UUID
Aplikasi kami mengenkripsi PK integer agar terlihat seperti UUID
Karena jika ID berurutan terekspos, orang bisa memperkirakan jumlah pelanggan atau melakukan serangan kamus
ID terenkripsi memungkinkan upaya pemindaian langsung terdeteksi lewat kegagalan dekripsi
Pernyataan “2 miliar sudah cukup” itu berbahaya
Setiap DBA biasanya punya setidaknya satu kisah mimpi buruk yang berawal dari keputusan seperti itu
Tulisan itu mengatakan “nilai acak tidak efisien untuk diurutkan”, tetapi sebenarnya pengurutan berdasarkan urutan byte tetap memungkinkan
Hanya saja, karena kunci acak bukan insert berurutan, penyeimbangan ulang B-tree lebih sering terjadi sehingga performa menurun
PK integer membuat indeks lebih pas di memori, sedangkan UUIDv4 membutuhkan lebih banyak akses halaman sehingga latensi meningkat
Tulisan ini terlihat seperti optimisasi prematur yang mendahulukan solusi daripada masalahnya
UUIDv4 sudah cukup baik untuk kebanyakan kasus
Masalah performa sebaiknya dipikirkan saat benar-benar muncul
Singkatnya, di Postgres UUIDv7 menunjukkan performa yang sedikit lebih baik daripada v4
Di versi terbaru, dukungan UUIDv7 tanpa plugin juga sudah dimungkinkan
uuidv7(), tetapi belum jelas apakah fitur tambahannya lebih banyak