Chapter Tipe UUID sudah memperkenalkan tipe uuid, format penulisannya, dan pola DEFAULT gen_random_uuid() sebagai primary key. Pertanyaan yang belum terjawab: dari mana angka-angka itu datang, dan mengapa ada beberapa “versi” UUID yang saling bersaing? Jawabannya berada di lapisan generator. PostgreSQL 13 menaikkan gen_random_uuid() menjadi fungsi bawaan — tanpa extension apa pun — tetapi extension uuid-ossp tetap dipakai luas karena menyediakan generator versi lain: v1 yang berurutan dan bisa dilacak waktunya, v5 yang deterministik dari sebuah nama. Chapter ini membandingkan semuanya, lalu membantu memutuskan kapan UUID sebaiknya dibuat di database dan kapan di aplikasi.

gen_random_uuid(): Bawaan Sejak PostgreSQL 13 #

Untuk kebutuhan paling umum — identifier acak yang unik lintas mesin — tidak ada yang perlu dipasang. Fungsi gen_random_uuid() menghasilkan UUID versi 4: 122 bit acak murni dari generator kriptografis server. Versinya bisa dibuktikan sendiri lewat fungsi bawaan uuid_extract_version(), yang membaca digit versi dari UUID mana pun:

sql bawaan.sql
-- gen_random_uuid() bawaan PostgreSQL 13+: tanpa extension apa pun.
SELECT gen_random_uuid() <> gen_random_uuid() AS selalu_berbeda;

-- Fungsi bawaan ini menghasilkan UUID versi 4.
SELECT uuid_extract_version(gen_random_uuid()) AS versi;

-- Pola tabel: DEFAULT mengisi id saat INSERT tanpa menyebutnya.
CREATE TABLE pemesanan (
    id      uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    catatan text NOT NULL
);

INSERT INTO pemesanan (catatan) VALUES ('pesanan pertama'), ('pesanan kedua');

-- Nilai UUID tidak ditampilkan (acak); yang dibuktikan: terisi dan versinya 4.
SELECT id IS NOT NULL AS id_terisi,
       uuid_extract_version(id) AS versi
FROM pemesanan
ORDER BY catatan;

Output yang Dihasilkan:

text
 selalu_berbeda 
----------------
 t
(1 row)

 versi 
-------
     4
(1 row)

CREATE TABLE
INSERT 0 2
 id_terisi | versi 
-----------+-------
 t         |     4
 t         |     4
(2 rows)

Nilai UUID-nya sendiri sengaja tidak pernah ditampilkan: dua pemanggilan menghasilkan dua nilai berbeda (t), jadi tidak ada output contoh yang tetap. Yang dibuktikan justru sifat-sifatnya — dua baris INSERT tanpa menyebut id terisi otomatis (t), dan keduanya bertanda versi 4. Inilah seluruh yang dibutuhkan mayoritas aplikasi modern; extension di bawah ini hanya masuk hitungan bila versinya harus bukan 4.

uuid-ossp: Generator Versi Lain #

Extension uuid-ossp membungkus library OSSP UUID dan menyediakan generator untuk versi 1, 3, 4, dan 5, plus konstanta pembantu seperti uuid_nil() (UUID semua-nol) dan namespace standar. Nama extension-nya memang mengandung tanda hubung, sehingga penulisannya wajib di dalam tanda kutip:

sql uuid-ossp.sql
-- uuid-ossp: extension legacy yang menyediakan generator versi lain.
CREATE EXTENSION "uuid-ossp";

-- UUID spesial: nil (semua nol) dan namespace URL bawaan.
SELECT uuid_nil() AS uuid_nil;
SELECT uuid_ns_url() AS namespace_url;

-- uuid_generate_v4: acak murni, seperti gen_random_uuid().
SELECT uuid_generate_v4() <> uuid_generate_v4() AS v4_berbeda;

-- Versi tiap generator: v4 bawaan, v4 uuid-ossp, v1 uuid-ossp.
SELECT uuid_extract_version(gen_random_uuid()) AS versi_bawaan,
       uuid_extract_version(uuid_generate_v4()) AS versi_ossp_v4,
       uuid_extract_version(uuid_generate_v1()) AS versi_ossp_v1;

Output yang Dihasilkan:

text
CREATE EXTENSION
               uuid_nil               
--------------------------------------
 00000000-0000-0000-0000-000000000000
(1 row)

            namespace_url             
--------------------------------------
 6ba7b811-9dad-11d1-80b4-00c04fd430c8
(1 row)

 v4_berbeda 
------------
 t
(1 row)

 versi_bawaan | versi_ossp_v4 | versi_ossp_v1 
--------------+---------------+---------------
            4 |             4 |             1
(1 row)

Tiga fakta terbaca langsung dari output. uuid_nil() dan uuid_ns_url() adalah konstanta deterministik — UUID namespace URL 6ba7b811-... mengikuti standar RFC 4122 dan sama di seluruh dunia. uuid_generate_v4() berperilaku persis seperti gen_random_uuid(): acak, tidak pernah berulang. Dan versi berbeda-baca: 4, 4, dan 1 — digit versi membedakan asal usul UUID, dan dua generator v4 menghasilkan nilai yang tidak bisa dibedakan dari versinya saja.

v1 vs v4: Sortable tapi Bocor, vs Acak tapi Amnya #

Mengapa versi menjadi penting? Karena dua versi paling populer itu berdiri di dua filosofi yang bertolak belakang. v1 menyusun UUID dari timestamp presisi tinggi plus alamat MAC mesin pembuatnya — UUID yang dibuat belakangan bernilai lebih besar, sehingga index B-tree bertambah rapi di ujungnya. v4 murni acak — tidak membocorkan apa pun, tetapi nilai baru mendarat di tengah index secara merata, yang pada tabel raksasa membuat penulisan lebih tersebar (banyak halaman index tersentuh). Tabel ringkasnya:

Aspekv1 (uuid_generate_v1)v4 (gen_random_uuid)
Bahan penyusunWaktu + alamat MAC122 bit acak
Urutan pembuatanMeningkat seiring waktuTidak berurutan
Perilaku index B-treeSisipan terkonsentrasi di ujungSisipan tersebar merata
Informasi yang bocorKapan dan mesin apaTidak ada
KetersediaanPerlu uuid-osspBawaan PostgreSQL 13+

Kebocoran v1 bukan teori: waktu pembuatan bisa diekstraksi kembali dari UUID itu sendiri. File berikut memakai contoh UUID dari dokumentasi resmi sebagai literal tetap, lalu membuktikan sifat itu — sekaligus membuktikan v4 tidak menyimpan waktu apa pun:

sql v1-waktu.sql
-- UUID v1 menyimpan waktu pembuatan di dalam bit-bitnya.
-- Contoh UUID dari dokumentasi resmi:
SELECT uuid_extract_timestamp('cdd44840-6ad5-11d1-8c78-00c04fd7c49f') AS waktu_dibuat;

-- Waktu juga bisa dibuat ulang dari UUID v1 yang baru dibuat.
SELECT uuid_extract_timestamp(uuid_generate_v1()) IS NOT NULL AS v1_punya_waktu;

-- v4 tidak menyimpan waktu apa pun.
SELECT uuid_extract_timestamp(gen_random_uuid()) IS NULL AS v4_tanpa_waktu;

Output yang Dihasilkan:

text
         waktu_dibuat          
-------------------------------
 1997-12-02 12:24:28.427475+07
(1 row)

 v1_punya_waktu 
----------------
 t
(1 row)

 v4_tanpa_waktu 
----------------
 t
(1 row)

Dari UUID cdd44840-... yang dibuat tahun 1997, fungsi uuid_extract_timestamp() mengeluarkan 1997-12-02 12:24:28.427475+07 — tanggal dan waktu persis saat UUID itu lahir, tampil dalam zona waktu sesi. Dua perbandingan berikutnya menegaskan pembagian tugas: UUID v1 segar selalu punya waktu (t), UUID v4 selalu tidak (NULL). Untuk data yang sensitif terhadap kapan dibuatnya — nomor pesanan, token sesi, identitas pengguna — sifat ini adalah kebocoran informasi yang disengaja.

v5: UUID Deterministik dari Nama #

Versi 5 menempati ceruk yang lain lagi: identifier yang sama untuk input yang sama. uuid_generate_v5(namespace, nama) menghitung SHA-1 dari pasangan itu, sehingga seluruh sistem yang tahu namanya akan menghasilkan UUID yang persis identik tanpa komunikasi apa pun — pola yang pas untuk deduplikasi data lintas sumber. Berbeda dari v1 dan v4, hasilnya deterministik dan aman ditampilkan:

-- v5: UUID deterministik dari pasangan namespace + nama (hash SHA-1).
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya') AS uuid_v5;

-- Nama yang sama selalu menghasilkan UUID yang sama.
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')
     = uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya') AS deterministik;

-- Nama berbeda menghasilkan UUID berbeda.
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')
     <> uuid_generate_v5(uuid_ns_url(), 'cabang-surabaya') AS nama_berbeda;

-- Versi 5 terbaca dari digit versi di dalam UUID itu sendiri.
SELECT uuid_extract_version(
           uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')) AS versi_v5;

-- Bersih-bersih: uuid-ossp dilepas; gen_random_uuid() tetap tersedia.
DROP EXTENSION "uuid-ossp";

Output yang Dihasilkan:

text
               uuid_v5                
--------------------------------------
 0ee10086-ed5b-5112-bee6-a9c10134c630
(1 row)

 deterministik 
---------------
 t
(1 row)

 nama_berbeda 
--------------
 t
(1 row)

 versi_v5 
----------
        5
(1 row)

DROP EXTENSION

Nama 'toko-maju-jaya' di bawah namespace URL selalu melahirkan 0ee10086-ed5b-5112-bee6-a9c10134c630 — di server ini, di server mana pun, hari ini maupun tahun depan. Nama yang berbeda melahirkan UUID berbeda (t), dan digit versinya membuktikan asal generatornya (5). Nama boleh string apa pun — identifier entitas, path, URL; yang penting seluruh sistem sepakat memakai nama dan namespace yang sama. Saudara kandungnya versi 3 memakai rumus yang sama tetapi berbasis MD5; karena algoritme dasarnya lebih lemah, RFC 4122 merekomendasikan v5 untuk pemakaian baru. File ditutup DROP EXTENSION "uuid-ossp" — generator bawaan gen_random_uuid() tetap berfungsi tanpa extension.

Database atau Aplikasi yang Membuat UUID? #

Pertanyaan terakhir praktis: di mana UUID dibuat? Dengan generator lengkap di kedua sisi, keputusan jatuh ke arsitektur. UUID memungkinkan pembuatannya di mana saja — justru itu nilai utamanya — tetapi tiap pilihan membawa konsekuensi:

PertimbanganDibuat di databaseDibuat di aplikasi
Batch INSERT dari berbagai sumberSatu DEFAULT untuk semua jalurTiap klien harus membawa generatornya
Id sudah ada saat INSERT dieksekusiPerlu RETURNING id untuk mengetahuinyaId diketahui sebelum query dikirim
Audit versi & algoritmeTerpusat di server, bisa diganti seragamTersebar mengikuti banyak codebase
Beban CPU pembuatanBergabung di proses PostgreSQLTersebar di banyak server aplikasi

Dua baris pertama tabel itu paling menentukan. Aplikasi yang perlu tahu id sebelum menyimpan baris — misalnya untuk menyusun payload berisi identitas baris itu sendiri — lebih mudah membuat UUID di kodenya. Sebaliknya, load data dari banyak jalur (ETL, migrasi, skrip manual) paling aman ditangani satu DEFAULT di database sehingga tidak ada jalur yang bisa melupakan generatornya. Untuk v5, posisinya hampir selalu di satu tempat yang sama — dan tempat itu biasanya aplikasi, karena nama yang di-hash umumnya sudah ada di domain logikanya.

Dari identifier acak kita bergeser ke kebutuhan yang sama sekali berbeda: membaca dan menulis data yang tinggal di database lain. postgres_fdw membuat tabel di PostgreSQL seberkas mana pun tampak seperti tabel lokal — foreign data wrapper yang dibedah penuh di chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

bawaan.sql

Unduh berkas
sql
-- gen_random_uuid() bawaan PostgreSQL 13+: tanpa extension apa pun.
SELECT gen_random_uuid() <> gen_random_uuid() AS selalu_berbeda;

-- Fungsi bawaan ini menghasilkan UUID versi 4.
SELECT uuid_extract_version(gen_random_uuid()) AS versi;

-- Pola tabel: DEFAULT mengisi id saat INSERT tanpa menyebutnya.
CREATE TABLE pemesanan (
    id      uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    catatan text NOT NULL
);

INSERT INTO pemesanan (catatan) VALUES ('pesanan pertama'), ('pesanan kedua');

-- Nilai UUID tidak ditampilkan (acak); yang dibuktikan: terisi dan versinya 4.
SELECT id IS NOT NULL AS id_terisi,
       uuid_extract_version(id) AS versi
FROM pemesanan
ORDER BY catatan;

uuid-ossp.sql

Unduh berkas
sql
-- uuid-ossp: extension legacy yang menyediakan generator versi lain.
CREATE EXTENSION "uuid-ossp";

-- UUID spesial: nil (semua nol) dan namespace URL bawaan.
SELECT uuid_nil() AS uuid_nil;
SELECT uuid_ns_url() AS namespace_url;

-- uuid_generate_v4: acak murni, seperti gen_random_uuid().
SELECT uuid_generate_v4() <> uuid_generate_v4() AS v4_berbeda;

-- Versi tiap generator: v4 bawaan, v4 uuid-ossp, v1 uuid-ossp.
SELECT uuid_extract_version(gen_random_uuid()) AS versi_bawaan,
       uuid_extract_version(uuid_generate_v4()) AS versi_ossp_v4,
       uuid_extract_version(uuid_generate_v1()) AS versi_ossp_v1;

v1-waktu.sql

Unduh berkas
sql
-- UUID v1 menyimpan waktu pembuatan di dalam bit-bitnya.
-- Contoh UUID dari dokumentasi resmi:
SELECT uuid_extract_timestamp('cdd44840-6ad5-11d1-8c78-00c04fd7c49f') AS waktu_dibuat;

-- Waktu juga bisa dibuat ulang dari UUID v1 yang baru dibuat.
SELECT uuid_extract_timestamp(uuid_generate_v1()) IS NOT NULL AS v1_punya_waktu;

-- v4 tidak menyimpan waktu apa pun.
SELECT uuid_extract_timestamp(gen_random_uuid()) IS NULL AS v4_tanpa_waktu;

v5-deterministik.sql

Unduh berkas
sql
-- v5: UUID deterministik dari pasangan namespace + nama (hash SHA-1).
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya') AS uuid_v5;

-- Nama yang sama selalu menghasilkan UUID yang sama.
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')
     = uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya') AS deterministik;

-- Nama berbeda menghasilkan UUID berbeda.
SELECT uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')
     <> uuid_generate_v5(uuid_ns_url(), 'cabang-surabaya') AS nama_berbeda;

-- Versi 5 terbaca dari digit versi di dalam UUID itu sendiri.
SELECT uuid_extract_version(
           uuid_generate_v5(uuid_ns_url(), 'toko-maju-jaya')) AS versi_v5;

-- Bersih-bersih: uuid-ossp dilepas; gen_random_uuid() tetap tersedia.
DROP EXTENSION "uuid-ossp";