Sesi login, keranjang belanja sementara, dan kode OTP berbagi satu sifat: sekali lewat batas waktunya, datanya tidak lagi bernilai dan justru menjadi beban penyimpanan. Menulis cron job penghapus di sisi aplikasi selalu bisa, tetapi solusi yang lebih rapi sudah tersedia di server: TTL index — index yang menghapus document secara otomatis setelah usianya melampaui batas waktu. Chapter ini menguji mekanismenya secara nyata, termasuk menunggu thread TTL benar-benar bekerja.

Peta materi Chapter ini:

  • Membuat TTL index dengan opsi expireAfterSeconds.
  • Uji nyata: menunggu thread TTL (berjalan tiap 60 detik) menghapus document.
  • Perilaku khusus: field berupa array Date dan nilai non-Date.
  • Use case umum: sesi login, OTP, dan log.

Membuat TTL Index #

TTL index adalah single field index biasa plus satu opsi: expireAfterSeconds. Field yang di-index wajib berisi nilai Date — tanggal kedaluwarsa dihitung dari nilai field tersebut. Mari siapkan collection sesi berisi empat document yang masing-masing menguji satu perilaku TTL:

js
use latihan_index
db.sesi.drop()
db.sesi.insertMany([
  { pengguna: "Budi", sesiBerakhir: new Date() },
  { pengguna: "Siti", sesiBerakhir: new Date(Date.now() + 3600000) },
  { pengguna: "Dewi", sesiBerakhir: [new Date(Date.now() - 120000), new Date(Date.now() + 3600000)] },
  { pengguna: "Eka", sesiBerakhir: "tidak pernah" }
])
db.sesi.createIndex({ sesiBerakhir: 1 }, { expireAfterSeconds: 5 })

Output yang Dihasilkan:

text
switched to db latihan_index
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3bc8ab02ae8bf3e41cb3c'),
    '1': ObjectId('6ad3bc8ab02ae8bf3e41cb3d'),
    '2': ObjectId('6ad3bc8ab02ae8bf3e41cb3e'),
    '3': ObjectId('6ad3bc8ab02ae8bf3e41cb3f')
  }
}
sesiBerakhir_1

Perhatikan bahwa kita meng-insert document terlebih dahulu, lalu membuat index — keduanya sama sahnya. Nilai 5 detik dipilih agar uji berlangsung cepat; pada produksi, sesi login lazim memakai 1800 (30 menit) dan OTP 300 (5 menit). Pada collection uji di atas, satu-satunya field Date yang usianya melewati 5 detik adalah milik Budi, sehingga hanya document Budi yang calon untuk dihapus.

Thread TTL Bekerja Tiap 60 Detik #

Penghapusan TTL tidak terjadi pada detik yang sama ketika document kedaluwarsa. Server mongod menjalankan TTL monitor — thread latar yang bangun setiap 60 detik, mencari index bertanda expireAfterSeconds, lalu menghapus document yang sudah melewati batas. Konsekuensinya: penghapusan dijamin terjadi paling lama sekitar satu menit setelah kedaluwarsa. Mari buktikan dengan benar-benar menunggu 65 detik:

js
print("Sebelum menunggu: " + db.sesi.countDocuments({}) + " document")
sleep(65000)
db.sesi.find({}, { _id: 0, pengguna: 1, sesiBerakhir: 1 })

Output yang Dihasilkan:

text
Sebelum menunggu: 4 document

[
  { pengguna: 'Siti', sesiBerakhir: ISODate('2026-10-17T19:20:58.948Z') },
  { pengguna: 'Eka', sesiBerakhir: 'tidak pernah' }
]

Dua document bertahan dan dua terhapus. Tabel berikut merangkum nasib keempatnya:

DocumentNilai sesiBerakhirHasil setelah 65 detik
BudiDate saat insertTerhapus — usia melewati 5 detik
SitiDate satu jam ke depanBertahan — kedaluwarsa 1 jam lagi
DewiArray dua DateTerhapus — tanggal terlama sudah 2 menit lalu
EkaString "tidak pernah"Bertahan selamanya — non-Date tak pernah kedaluwarsa

Dua baris terakhir pantas digarisbawahi. Pada array Date, TTL memakai elemen terlama sebagai patokan — document Dewi dianggap kedaluwarsa meskipun elemen keduanya masih dua jam di masa depan. Sedangkan nilai selain Date (string, angka, bahkan timestamp Unix sebagai angka) tidak pernah dianggap kedaluwarsa, termasuk juga document yang tidak memiliki field tersebut sama sekali.

Batasan dan Catatan TTL #

Aturan-aturan berikut berlaku pada TTL index:

AturanPenjelasan
Hanya single fieldCompound index tidak mendukung TTL — galat kode 67
Tidak untuk _idIndex _id tidak boleh memakai expireAfterSeconds — galat kode 197
Field wajib DateNon-Date dan field yang absen tidak pernah dihapus
Boleh lebih dari satuSatu collection boleh punya beberapa TTL index pada field berbeda
Penghapusan best-effortBeban server tinggi dapat menunda eksekusi thread TTL

Mengubah masa hidup tanpa membangun ulang index juga mungkin lewat collMod — topik yang dibahas di Chapter Index Maintenance:

js
db.runCommand({
  collMod: "sesi",
  index: { keyPattern: { sesiBerakhir: 1 }, expireAfterSeconds: 3600 }
})
db.sesi.getIndexes()

Output yang Dihasilkan:

text
{
  expireAfterSeconds_old: Long('5'),
  expireAfterSeconds_new: Long('3600'),
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792261324, i: 1 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792261324, i: 1 })
}
[
  {
    v: 2,
    key: { _id: 1 },
    name: '_id_',
    collation: { locale: 'simple' }
  },
  {
    v: 2,
    key: { sesiBerakhir: 1 },
    name: 'sesiBerakhir_1',
    expireAfterSeconds: 3600,
    collation: { locale: 'simple' }
  }
]

Respon collMod melaporkan expireAfterSeconds_old dan expireAfterSeconds_new, dan getIndexes() kini menampilkan nilai 3600 pada spesifikasi index. Kita baru saja mengubah batas sesi dari 5 detik menjadi 1 jam tanpa menghapus indexnya.

Rangkuman #

  • createIndex({ field: 1 }, { expireAfterSeconds: N }) menghapus document otomatis setelah N detik dihitung dari nilai Date pada field.
  • Thread TTL monitor bangun tiap 60 detik, sehingga penghapusan terjadi paling lama sekitar satu menit setelah kedaluwarsa — dibuktikan nyata dengan sleep(65000).
  • Array Date kedaluwarsa mengikuti elemen terlama; nilai non-Date dan field yang absen tidak pernah dihapus.
  • TTL hanya untuk single field index, tidak berlaku pada _id, dan nilainya bisa diubah kapan pun lewat collMod.