Dua pelanggan mendaftar di detik yang sama dengan email yang sama, dan salah satunya menerima galat E11000 duplicate key error. Ini salah satu galat production yang paling sering muncul — dan berbeda dari kebanyakan galat lain, E11000 bukan tanda kesalahan kode melainkan penjaga integritas data yang sedang bekerja: unique index menolak salinan kedua. Tugas kita bukan menghilangkan galat ini, melainkan memilih reaksi yang tepat ketika ia muncul: perbarui data lama, sampaikan konflik ke pengguna, atau gagalkan dengan rapi.
Peta materi Chapter ini:
- Reproduksi lengkap
E11000beserta fieldkeyValuedankeyPattern-nya. - Tiga strategi: upsert, tangkap-dan-sampaikan, cek-dulu.
- Jebakan pola cek-dulu: kondisi balapan (race condition).
- Membangun unique index di atas data yang sudah mengandung duplikat.
Reproduksi dan Anatomi E11000 #
Siapkan collection pelanggan dengan unique index di field email:
use latihan_trouble
db.pelanggan.drop()
db.pelanggan.insertMany([
{ nama: "Budi", email: "[email protected]", kota: "Jakarta" },
{ nama: "Siti", email: "[email protected]", kota: "Bandung" }
])
db.pelanggan.createIndex({ email: 1 }, { unique: true })Output yang Dihasilkan:
switched to db latihan_trouble
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad402c3247f1105fe5661eb'),
'1': ObjectId('6ad402c3247f1105fe5661ec')
}
}
email_1Lalu kirim insert dengan email yang sudah dipakai:
db.pelanggan.insertOne({ nama: "Dewi", email: "[email protected]", kota: "Surabaya" })Output yang Dihasilkan:
Uncaught
MongoServerError: E11000 duplicate key error collection: latihan_trouble.pelanggan index: email_1 dup key: { email: "[email protected]" }Pesan ini bisa dibaca tiga bagian tanpa alat bantu apa pun: latihan_trouble.pelanggan adalah collection tempat kejadian, email_1 adalah nama index yang menolak (nama bawaan dari field + arahnya), dan dup key: { email: "[email protected]" } adalah nilai persis yang sudah ada. Untuk penanganan programatik, bagian-bagian itu tersedia dalam bentuk terstruktur:
try {
db.pelanggan.insertOne({ nama: "Dewi", email: "[email protected]" })
} catch (err) {
print("code: " + err.code)
print("keyPattern: " + JSON.stringify(err.keyPattern))
print("keyValue: " + JSON.stringify(err.keyValue))
}Output yang Dihasilkan:
code: 11000
keyPattern: {"email":1}
keyValue: {"email":"[email protected]"}err.code === 11000 adalah cara paling andal mengenali duplikat — cocok untuk cabang if di aplikasi. keyValue memberi tahu kombinasi kunci mana yang bentrok tanpa perlu mem-parsing teks pesan, dan keyPattern menunjukkan bentuk index-nya (satu field, atau gabungan beberapa field untuk compound unique index).
Tiga Strategi Penanganan #
Pilihan strategi ditentukan oleh satu pertanyaan bisnis: bila kunci itu sudah ada, apa yang seharusnya terjadi?
Bila duplikat berarti “perbarui yang lama” — misalnya pelanggan mendaftar ulang dengan email sama — jawabannya upsert. Operasi menjadi atomik: tidak ada celah antara “cek” dan “tulis”:
db.pelanggan.updateOne(
{ email: "[email protected]" },
{ $set: { kota: "Depok" } },
{ upsert: true }
)
db.pelanggan.findOne({ email: "[email protected]" }, { nama: 1, kota: 1, _id: 0 })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
{ nama: 'Budi', kota: 'Depok' }matchedCount: 1 dengan upsertedCount: 0 menandakan document Budi ditemukan dan diperbarui — bukan disalin. Karena filter updateOne memakai field yang sama dengan unique index, pola ini aman dari E11000 sekaligus aman dari balapan antar-request.
Bila duplikat berarti “konflik yang harus dilaporkan” — misalnya alamat email dipakai akun lain — biarkan galat terjadi, tangkap di catch, lalu terjemahkan keyValue menjadi pesan yang manusiawi (“email sudah terdaftar”). Jangan pernah menampilkan pesan mentah E11000 ke pengguna akhir.
Bila ingin mengurangi kemungkinan konflik sejak awal, pola cek-dulu terasa alami:
const emailBaru = "[email protected]"
if (db.pelanggan.findOne({ email: emailBaru })) {
print("email sudah terdaftar")
} else {
db.pelanggan.insertOne({ nama: "Dewi", email: emailBaru })
}Output yang Dihasilkan:
{
acknowledged: true,
insertedId: ObjectId('6ad402c3247f1105fe5661ef')
}Unique Index pada Data yang Sudah Kotor #
Skenario kedua yang sering muncul saat migrasi: unique index justru gagal dibuat karena data lama sudah mengandung duplikat:
db.stok_lama.insertMany([
{ rak: "R1", kode: "SBN-001" },
{ rak: "R2", kode: "SBN-001" }
])
db.stok_lama.createIndex({ kode: 1 }, { unique: true })Output yang Dihasilkan:
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad402c3247f1105fe5661f0'),
'1': ObjectId('6ad402c3247f1105fe5661f1')
}
}
Uncaught
MongoServerError[DuplicateKey]: Index build failed: 8ced8e7a-c732-4e47-be65-500b6b1b0697: Collection latihan_trouble.stok_lama ( f38286d5-b9a4-41a4-8842-98ed78c505d0 ) :: caused by :: E11000 duplicate key error collection: latihan_trouble.stok_lama index: kode_1 dup key: { kode: "SBN-001" }Proses pembangunan index berhenti dengan Index build failed yang diakarinya tetap E11000 — index tidak jadi dibuat, dan data tetap utuh. Dua deretan angka heksadesimal panjang di pesan itu adalah pengenal internal proses build dan collection; nilainya berbeda di setiap eksekusi dan tidak berperan dalam diagnosis. Urutan pemulihannya: temukan duplikatnya lewat agregasi $group + $match jumlah di atas satu, putuskan document mana yang dipertahankan (atau gabungkan datanya), bersihkan, lalu jalankan createIndex ulang. Untuk kasus “duplikat itu sah selama document aktif” — misalnya pola soft-delete — gunakan partial index dengan syarat status: "aktif", dibahas di Section Indexing.
| Gejala | Penyebab | Perbaikan |
|---|---|---|
E11000 ... index: email_1 | Insert memakai nilai unik yang sudah ada | Pilih strategi: upsert, tangkap-dan-laporkan, atau cek-dulu |
E11000 ... index: _id_ | insertOne dengan _id eksplisit yang sudah dipakai | Hilangkan _id agar dihasilkan server, atau upsert dengan filter _id |
E11000 muncul sesekali di traffic tinggi | Balapan antar request pada pola cek-dulu | Pindah ke upsert; tangani kode 11000 di catch |
Index build failed ... E11000 saat createIndex | Data lama mengandung duplikat | Bersihkan duplikat, atau partial index bila duplikatnya sah |
E11000 setelah menambah unique index baru | Aturan baru menabrak data yang sudah berjalan | Audit data dengan agregasi $group sebelum memasang index |
Rangkuman #
E11000adalah unique index yang bekerja, bukan bug; reaksinya ditentukan makna bisnis dari duplikat tersebut.- Pesannya memuat collection, nama index, dan nilai bentrok; versi terstruktur ada di
err.keyValuedanerr.keyPatterndenganerr.code === 11000. - Upsert adalah jawaban atomik untuk “perbarui bila ada”; tangkap-dan-laporkan untuk konflik; cek-dulu hanya penyaring — bukan pengganti penanganan.
- Unique index yang gagal dibangun menandakan data sudah kotor: agregasi dulu, bersihkan, baru pasang ulang.