Sejak insert pertama di Chapter Verifikasi Instalasi, kita selalu melihat field _id berisi deretan heksadesimal seperti ObjectId('6ad377ca2919fa3d7374eab2') — dibuatkan otomatis, tidak pernah kita minta. Nilai ini bukan angka acak sembarangan: di dalamnya tersembunyi waktu pembuatan, identitas proses, dan penghitung yang membuatnya nyaris mustahil kembar. Chapter ini membedah anatomi ObjectId, membuktikan klaimnya lewat shell, lalu membahas kapan kita lebih baik memakai identifier buatan sendiri.

Peta materi Chapter ini:

  • Struktur 12 byte ObjectId: timestamp, nilai acak, dan counter.
  • Membuat serta membedah ObjectId di mongosh.
  • Jaminan unik _id per collection dan galat duplikasi E11000.
  • Identifier bisnis (SKU, email) beserta pola unique index.

Anatomi 12 Byte #

ObjectId adalah tipe BSON khusus sepanjang 12 byte, dan setiap bagiannya punya tugas:

BagianUkuranIsi
Timestamp4 byte pertamaDetik sejak Unix epoch saat ObjectId dibuat
Random5 byte berikutnyaNilai acak yang dibuat sekali per proses
Counter3 byte terakhirPenghitung yang naik setiap ObjectId baru, diawali nilai acak

Kombinasi ketiganya menghasilkan identifier yang unik tanpa perlu koordinasi antar proses: timestamp membedakan detik pembuatan, nilai acak membedakan proses yang berjalan bersamaan, dan counter membedakan pembuatan berturut-turut dalam satu detik. Konsekuensi yang menyenangkan: ObjectId yang dibuat lebih belakangan hampir selalu bernilai lebih besar, sehingga pengurutan berdasarkan _id mendekati pengurutan berdasarkan waktu pembuatan.

Satu detail yang sering mengejutkan: ObjectId dibuat di sisi klien — oleh driver atau mongosh — bukan oleh server. Saat insertOne() berangkat, _id sudah terisi; itulah sebabnya balasan insert menampilkan insertedId yang identik dengan yang tersimpan.

Membuat dan Membedah ObjectId #

mongosh menyediakan constructor ObjectId() untuk membuat nilai baru di luar insert sekalipun:

js
use latihan_objectid
const id = ObjectId()
id

Output yang Dihasilkan:

text
switched to db latihan_objectid

ObjectId('6ad3807531cfdca88cca3513')

Bentuk tampilannya adalah 24 karakter heksadesimal (12 byte × 2 karakter). Empat byte pertama — delapan karakter pertama pada heksadesimal — adalah timestamp, dan getTimestamp() membacanya langsung:

js
id.getTimestamp()

Output yang Dihasilkan:

text
ISODate('2026-10-17T14:04:37.000Z')

Waktu yang tercetak adalah saat baris ObjectId() dijalankan — nilai ini berbeda pada setiap eksekusi, sesuai momen pembuatannya. Kita bisa membuktikan klaim struktur tersebut dengan membedah sebuah ObjectId yang sudah kita kenal. Nilai 6ad377ca2919fa3d7374eab2 adalah _id document Budi dari Chapter Verifikasi Instalasi:

js
ObjectId("6ad377ca2919fa3d7374eab2").getTimestamp()
parseInt("6ad377ca", 16)
new Date(1792243658 * 1000)

Output yang Dihasilkan:

text
ISODate('2026-10-17T13:27:38.000Z')
1792243658
ISODate('2026-10-17T13:27:38.000Z')

Mari kita bedah hasilnya:

  1. getTimestamp() melaporkan ISODate('2026-10-17T13:27:38.000Z') — waktu pembuatan ObjectId tersebut.
  2. parseInt("6ad377ca", 16) mengubah delapan karakter heksadesimal pertama menjadi angka desimal 1792243658.
  3. new Date(1792243658 * 1000) mengubah angka detik itu kembali menjadi tanggal — hasilnya sama persis dengan getTimestamp(), membuktikan bahwa 4 byte pertama memang timestamp Unix.

Untuk keperluan logging dan URL, ObjectId juga bisa dikonversi menjadi string biasa:

js
id.toString()

Output yang Dihasilkan:

text
6ad3807531cfdca88cca3513

toString() dan toHexString() menghasilkan bentuk heksadesimal yang sama — string 24 karakter tanpa pembungkus ObjectId(...).

_id Unik per Collection #

Uniknya _id dijalankan lewat index _id_ yang otomatis dibuat pada setiap collection. Pelanggarannya langsung ditolak dengan galat kode E11000 (duplicate key). Kita tidak terikat pada ObjectId — _id boleh string, seperti SKU produk berikut:

js
db.produk.insertOne({ _id: "SKU-001", nama: "Kopi Gayo", harga: 45000 })
db.produk.insertOne({ _id: "SKU-001", nama: "Kopi Robusta", harga: 38000 })
db.produk.find()

Output yang Dihasilkan:

text
{ acknowledged: true, insertedId: 'SKU-001' }
Uncaught 
MongoServerError: E11000 duplicate key error collection: latihan_objectid.produk index: _id_ dup key: { _id: "SKU-001" }
[ { _id: 'SKU-001', nama: 'Kopi Gayo', harga: 45000 } ]

Insert kedua gagal: E11000 duplicate key error pada index _id_, dan find() membuktikan hanya document pertama yang tersimpan. Penting dicatat bahwa jaminan unik ini berlaku per collection — SKU-001 tetap boleh dipakai sebagai _id di collection lain. Nilai ObjectId sendiri juga tidak dijamin unik secara global di seluruh server; yang dijamin adalah tidak ada dua document dalam satu collection dengan _id sama.

Identifier Bisnis dan Unique Index #

Kapan lebih baik memakai identifier sendiri? Aturan praktisnya: pakai identifier bisnis (SKU, ISBN, kode voucher) sebagai _id hanya jika nilai tersebut stabil selamanya — ingat bahwa _id bersifat immutable, sedangkan email dan nama bisa berubah. Ketika kunci bisnis berpeluang berubah, pola yang lebih aman adalah tetap memakai ObjectId sebagai _id lalu melindungi kunci bisnis dengan unique index:

js
db.pelanggan.insertOne({ nama: "Budi", email: "[email protected]" })
db.pelanggan.createIndex({ email: 1 }, { unique: true })
db.pelanggan.insertOne({ nama: "Budi Santoso", email: "[email protected]" })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: ObjectId('6ad3807531cfdca88cca3514')
}
email_1
Uncaught 
MongoServerError: E11000 duplicate key error collection: latihan_objectid.pelanggan index: email_1 dup key: { email: "[email protected]" }

Bedah hasilnya: createIndex() melaporkan nama index yang dibuatnya, yaitu email_1, lalu insert kedua ditolak E11000 — kali ini pada index email_1, bukan _id_. Dengan pola ini email tetap unik dan mudah dicari, sementara _id berupa ObjectId tetap menjadi identitas stabil yang tidak pernah perlu berubah meskipun email pelanggan berganti.

Sebagai penutup, bersihkan database latihan:

js
db.dropDatabase()

Output yang Dihasilkan:

text
{ ok: 1, dropped: 'latihan_objectid' }

Rangkuman #

  • ObjectId panjangnya 12 byte: 4 byte timestamp + 5 byte nilai acak per proses + 3 byte counter.
  • Timestamp bisa dibaca lewat getTimestamp(); empat byte pertama heksadesimal adalah detik Unix epoch.
  • ObjectId dibuat di sisi klien (driver/mongosh), bukan server, sehingga insertedId sudah diketahui sebelum balasan tiba.
  • _id unik per collection dan immutable; pelanggaran duplikasi ditolak galat E11000.
  • Kunci bisnis yang stabil boleh jadi _id; yang bisa berubah sebaiknya dilindungi unique index dan dibiarkan bukan _id.

_id sudah tuntas. Dua Chapter terakhir Section ini memperdalam dua pola nilai yang paling sering menyertai _id di aplikasi nyata: nested documents dan arrays.