Pada tulisan tentang Normalisasi Database, gw ada sedikit membahas tentang Primary Key. Nah, di sini gw akan membahas lebih dalam lagi macam-macam pendekatan untuk menentukan Primary Key. Secara umum pendekatan yang digunakan untuk menentukan Primary Key yaitu menggunakan Candidate Key, Auto-Increment Key, UUID Key, dan ULID Key. Masing-masing pendekatan tentu saja punya kelemahan dan kelebihan.
Candidate Key
Cara pertama adalah menggunakan Candidate Key sebagai Primary Key. Misalnya menggunakan NIK, Email, Username, atau generated id yang dibuat programmatically sebagai Primary Key.
Tabel user
nik (PK) | name | origin |
---|---|---|
1772011234567890 | Joko | DKI Jakarta |
7732011234567899 | Joki | DKI Jakarta |
Keuntungan Candidate Key
Keuntungannya adalah Candidate Key paling gampang dikenali. Kita juga ga perlu menambahkan kolom tambahan lain sebagai Primary Key sehingga secara ukuran storage tentu pendekatan ini lebih hemat. Secara teknis juga mudah diimplementasi dan ga perlu ribet setup Unique Constraint karena Primary Key sudah pasti unik.
Kelemahan Candidate Key
Kelemahannya adalah susah dimaintain. Misalkan Candidate Key tersebut adalah value yang tidak dibuat oleh system secara programmatically, seperti kolom nik
pada tabel user
. Hal seperti ini rentan terjadi kesalahan. Misalnya tabel tersebut sudah berelasi ke tabel lain seperti user_address
, user_contact
, user_data
, dan lainnya. Lalu ternyata NIK yang diinput typo, tentu akan ribet memaintain data seperti ini. Walaupun bisa dihandle menggunakan cascades, tetap saja ga worth it mengubah beberapa data pada tabel hanya karena kesalahan ini. Melakukan penggantian Primary Key dapat mengakibatkan data lock terutama jika menggunakan database yang mengimplementasi 2 phase locking, apalagi saat traffic lagi tinggi, tentu bisa menurunkan performa database. Kelemahan lainnya adalah ketika menghadapi Composite Candidate Key. Itu artinya ketika tabel tersebut berelasi ke tabel lain, kita harus mereferensikan semua kolom Candidate Key ke tabel lain. Tentu saja ini cukup kompleks, apalagi kalau Composite-nya lebih dari 2 kolom😵.
Auto-Increment Key (Sequential ID)
Auto-Increment atau Sequential ID adalah salah satu alternatif dari Candidate Key. Di sini kita akan menggunakan constraint Unique Key terhadap Candidate Key. Lalu kita akan menambahkan kolom baru yang disebut Surrogate Key atau Technical Key, yaitu kolom yang fungsinya menggantikan Candidate Key untuk mewakili data secara teknis sebagai Primary Key, bukan secara bisnis. Kolom khusus tersebut berisi angka yang melakukan increment secara sequential setiap melakukan insertion. Biasanya kolom khusus tersebut diberi nama id
. Pendekatan ini menyelesaikan permasalahan yang ada pada pendekatan Candidate Key di atas.
Tabel user
id (PK) | nik (UK) | name | origin |
---|---|---|---|
1 | 1772011234567890 | Joko | DKI Jakarta |
2 | 7732011234567899 | Joki | DKI Jakarta |
Keuntungan Auto-Increment Key
Karena Candidate Key-nya bukan Primary Key, sekarang kita bisa memaintain data dengan lebih gampang. Tabel lain yang memiliki relasi dengan tabel ini hanya perlu melakukan referensi data lewat kolom id
. Jika terjadi kesalahan penginputan pada Candidate Key bisa di-update saja value-nya tanpa ribet. Begitu juga ketika Candidate Key tersebut Composite, kita tidak perlu mereferensikan semua kolom Composite tersebut, cukup kolom id
saja. Tabelnya sekarang jadi sangat fleksibel setiap terjadi perubahan. Melakukan pencarian menggunakan Candidate Key secara performa juga tidak ada bedanya karena Unique Key di-index oleh database, jadi kita tetap bisa melakukan pencarian dengan cepat walaupun tanpa melibatkan Primary Key pada “where clause”.
Kelemahan Auto-Increment Key
Auto-Increment Key juga bukan solusi yang sempurna. Secara ukuran storage, tentu kita perlu menambahkan satu kolom baru. Kita perlu melakukan setup Unique Key terhadap Candidate Key. Secara keamanan juga kita perlu hati-hati. Misalkan pada url sebuah website ada sebuah API untuk melihat detail user seperti ini https://website.com/users/2
untuk melihat detail user dengan id 2
. Karena id tersebut incremental, kita bisa tahu bahwa user tersebut adalah user pada urutan ke-2. Kompetitor bisa “mencuri” informasi pada website tersebut untuk memperkirakan jumlah berapa orang user yang sudah terdaftar pada website tersebut. Kompetitor bisa melakukan input integer berapapun ke url pada range tertentu untuk mendapatkan infomasi tersebut. Secara bisnis ini cukup beresiko buat perusahaan. Untuk itu jika menggunakan Auto-Increment Key sebaiknya yang digunakan sebagai parameter untuk melihat detail user adalah tetap menggunakan Candidate Key. Jadi url-nya seperti ini https://website.com/users/7732011234567899
sehingga informasi id internal yang digunakan pada sistem tidak bisa diketahui oleh user dari luar. Begitu juga pada Response, sebaiknya sequential id
juga tidak diikutsertakan. Kelemahan lainnya adalah ketika kita menggunakan lebih dari satu database untuk menyimpan data (Distributed Database). Misalkan data yang disimpan terlalu banyak dan penambahannya masif, biasanya database akan sangat lambat jika hanya menggunakan satu database. Untuk itu system perlu menggunakan lebih dari satu database agar load insertion-nya terdistribusi ke beberapa database. Ketika menggunakan lebih dari satu database tentu akan sangat mungkin terjadi konflik Primary Key antar database.
UUID Key
Universally Unique Identifiers (UUID) adalah Id yang di-generate secara unik oleh system. UUID juga bisa dijadikan sebagai Surrogate Key. Pendekatan ini menyelesaikan permasalahan pada Auto-Increment Key di atas. UUID secara algoritma didesain sedemikian rupa untuk generate jutaan value secara unik sepersekian detik. Walaupun secara teori tentu saja masih ada kemungkinan konflik jika melakukan generate jutaan value dalam waktu kurang sedetik. Tapi itu sangat jarang terjadi, jadi harusnya cukup aman.
Tabel user
id (PK) | nik (UK) | name | origin |
---|---|---|---|
550e8400-e29b-41d4-a716-446655440000 | 1772011234567890 | Joko | DKI Jakarta |
e58ed763-928c-4155-bee9-fdbaaadc15f3 | 1732011234567899 | Joki | DKI Jakarta |
Keuntungan UUID Key
Karena UUID ini sudah unik, konflik yang terjadi ketika menggunakan lebih dari satu database hampir tidak mungkin terjadi. Ini juga lebih aman daripada Auto-Increment Id untuk dimunculkan di url karena tidak mencerminkan jumlah data yang disimpan di database. Jadi sah-sah saja kalau kita ingin membuat url detail user seperti ini https://website.com/users/550e8400-e29b-41d4-a716-446655440000
.
Kelemahan UUID Key
Kurang lebih sama seperti Auto-Increment Key, kita perlu tambahan satu kolom khusus atau Surrogate Key sebagai Id. Selain itu, secara ukuran storage UUID lebih besar daripada Auto-Increment Key. Tentu ini tidak efisien untuk system yang tidak terlalu besar. Kelemahan lainnya adalah performa indexing akan lebih berat dibandingkan Auto-Increment Key. Secara umum, indexing pada Primary Key database menggunakan BTree (Balanced Tree) seperti yang pernah gw bahas. Jadi value dari kolom tersebut disimpan secara Tree dan akan melakukan rebalancing ketika data yang disimpan tidak seimbang. Kalau menggunakan UUID, proses menyeimbangkan Tree tersebut akan lebih berat karena value yang dihasilkan itu random. Berbeda dengan Auto-Increment Key yang berurutan sehingga lebih cepat saat rebalancing.
ULID Key
Universal Lexicographically Identifiers (ULID) adalah Id yang juga di-generate secara random oleh system dengan prefix timestamp. ULID didesain agar men-generate value secara unik sebanyak 26 karakter yang di-encode yang diawali unix timestamp dengan presisi waktu hingga milliseconds ditambah karakter random setelahnya. Ini dapat dijadikan opsi pengganti UUID pada distributed system.
Tabel user
id (PK) | nik (UK) | name | origin |
---|---|---|---|
01HBGWRQZFEVA40RJ9FE7Y9Q32 | 1772011234567890 | Joko | DKI Jakarta |
01HD8JGQ7AF2DAR7NPP2V20Z8G | 1732011234567899 | Joki | DKI Jakarta |
Keuntungan ULID Key
Keunggulannya mirip dengan UUID, dapat digunakan pada distributed system karena di-generate unik. Dengan prefix unix timestamp, otomatis valuenya akan terurut sehingga walaupun di-generate secara unik & random tapi secara performa lebih baik daripada UUID saat rebalancing index😎. ULID lebih hemat storage daripada UUID, hanya memiliki ukuran 26 karakter alphanumeric tanpa karakter aneh-aneh dibanding UUID v4 yang dipakai secara umum pada beberapa bahasa pemrograman atau database dengan ukuran 36 karakter termasuk karakter "-". Kita juga dapat menggunakannya pada request sebuah website seperti https://website.com/users/01HBGWRQZFEVA40RJ9FE7Y9Q32
karena tidak mencerminkan jumlah data.
Kekurangan ULID Key
ULID masih tergolong baru sehingga belum terlalu terdengar permasalahan production yang terjadi pada aplikasi yang menggunakan ULID. Kita mungkin dapat menggunakan ULID pada request sebuah website, tapi value tersebut mengandung informasi kapan id itu dibuat dan dapat di-decode. Jika itu dirasa merupakan sesuatu yang tidak ingin dipublikasikan, maka sebaiknya jangan menggunakan ULID pada request, gunakan Candidate Key saja. Perusahaan besar yang gw tau sukses mengimplementasi ULID adalah Shopify. Pada bahasa pemrograman atau database setau gw belum ada tipe data atau fitur ULID by default. Untuk menggunakannya kita perlu pasang library ULID lalu nanti generated value-nya disimpan ke dalam varchar dengan ukuran 26 karakter pada database. Saat ini sudah cukup banyak library ULID untuk beberapa bahasa pemrograman, bisa dicek di github.
Verdict
Menggunakan Candidate Key sebagai Primary Key merupakan pilihan yang sangat sederhana tapi tidak tahan perubahan. Candidate Key cocok dijadikan Primary Key ketika valuenya dibuat programmatically oleh system. Jika Candidate Key tersebut diinput manual atau bisa berubah, lebih baik Candidate Key tersebut tidak usah dijadikan Primary Key, pilih pendekatan lain saja. Auto-Increment Key merupakan salah satu alternatif yang bisa digunakan agar datanya mudah dimaintain. Implementasinya juga cukup gampang dan storage-nya tidak besar. Ini sangat cocok untuk system yang tidak terlalu besar. Tapi untuk system yang menggunakan lebih dari satu database, cara ini tidak dianjurkan karena dapat mengakibatkan konflik Id antar database. Untuk system yang menggunakan lebih dari satu database lebih baik menggunakan UUID Key karena Id di-generate secara unik dan relatif aman dari konflik antar database. Akan tetapi resource yang digunakan cukup besar karena ukurannya cukup panjang dan performa indexing-nya lebih berat karena value yang dihasilkan random. Alternatifnya, kita bisa mempertimbangkan ULID untuk performa indexing yang lebih baik dan storage yang lebih hemat dibanding UUID pada distributed system. Gw sendiri belum berpengalaman menggunakan ULID di production, jadi gw masih belum terlalu kenal dengan ULID. Tapi menurut gw ini cukup menjanjikan. Untuk itu kita harus bijak menentukan kapan menggunakan Candidate Key, Auto-Increment Key, UUID Key, atau ULID Key. Menggunakan Technical Key atau Surrogate Key memang memberikan fleksibilitas, tapi bukan berarti semua tabel harus didesain seperti itu. Tetap pikir dengan matang sebelum memutuskan. Selain itu juga ada CUID2 (Collision-resistant Unique Identifier v2) sebagai alternatif dari UUID dengan ukuran yang lebih hemat. Lalu ada juga Nano ID dengan karakter yang lebih bervariasi dan ukuran yang bisa di-custom. Tapi keduanya ga sortable seperti ULID sehingga performa indexing keduanya kurang lebih seperti UUID. ULID juga punya turunan sejenis seperti TSID (Time-Sorted Identifier) yang formatnya berupa angka, dan KSUID (Key-Sorted Unique Identifier) yang presisi waktunya hanya sampai detik saja tapi jumlah karakter randomnya lebih banyak untuk mengurangi konflik. Semuanya kembali lagi tergantung kebutuhan bisnis dan system.