Kita sudah melihat mengapa collection reguler mahal untuk data sensor. Sekarang kita pakai fitur yang dirancang khusus untuk pekerjaan itu: time series collection, tersedia sejak MongoDB 5.0. Kita hanya mendeklarasikan field waktu, field metadata, dan frekuensi data — server yang mengelola bucket, kompresi kolom, dan index di balik layar. Chapter ini membuat collection cuaca sungguhan, mengintip bucket internalnya, lalu menguji batas operasi update dan delete-nya secara langsung.
Membuat Collection Cuaca #
Pembuatannya lewat db.createCollection() dengan opsi timeseries. Tiga field wajib dipahami: timeField (nama field bertipe Date), metaField (identitas seri data), dan granularity (frekuensi datanya):
use latihan_timeseries
db.dropDatabase()
db.createCollection("cuaca", {
timeseries: {
timeField: "waktu",
metaField: "sensor",
granularity: "minutes"
},
expireAfterSeconds: 604800
})
db.cuaca.insertMany([
{ waktu: ISODate("2026-12-10T02:00:00Z"), sensor: { sensor_id: "JKT-01", kota: "Jakarta" }, suhu: 27.4, kelembapan: 78 },
{ waktu: ISODate("2026-12-10T02:01:00Z"), sensor: { sensor_id: "JKT-01", kota: "Jakarta" }, suhu: 27.6, kelembapan: 77 },
{ waktu: ISODate("2026-12-10T02:02:00Z"), sensor: { sensor_id: "JKT-01", kota: "Jakarta" }, suhu: 27.9, kelembapan: 75 },
{ waktu: ISODate("2026-12-10T02:03:00Z"), sensor: { sensor_id: "BDG-07", kota: "Bandung" }, suhu: 22.1, kelembapan: 85 }
])
db.cuaca.find({}, { _id: 0, waktu: 1, sensor: 1, suhu: 1 }).sort({ waktu: 1 })Output yang Dihasilkan:
switched to db latihan_timeseries
{ ok: 1, dropped: 'latihan_timeseries' }
{ ok: 1 }
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3dff997eae5fc8738dccb'),
'1': ObjectId('6ad3dff997eae5fc8738dccc'),
'2': ObjectId('6ad3dff997eae5fc8738dccd'),
'3': ObjectId('6ad3dff997eae5fc8738dcce')
}
}
[
{
waktu: ISODate('2026-12-10T02:00:00.000Z'),
sensor: { kota: 'Jakarta', sensor_id: 'JKT-01' },
suhu: 27.4
},
{
waktu: ISODate('2026-12-10T02:01:00.000Z'),
sensor: { kota: 'Jakarta', sensor_id: 'JKT-01' },
suhu: 27.6
},
{
waktu: ISODate('2026-12-10T02:02:00.000Z'),
sensor: { kota: 'Jakarta', sensor_id: 'JKT-01' },
suhu: 27.9
},
{
waktu: ISODate('2026-12-10T02:03:00.000Z'),
sensor: { kota: 'Bandung', sensor_id: 'BDG-07' },
suhu: 22.1
}
]Mari kita bedah konfigurasinya:
timeField: "waktu"— setiap measurement wajib membawa field ini bertipe Date; nilainya menentukan urutan dan posisi data di dalam bucket.metaField: "sensor"— nilai field ini menjadi identitas seri: pembacaan dengansensoryang sama dikumpulkan ke bucket yang sama.granularity: "minutes"— memberi tahu server bahwa data datang kira-kira per menit, sehingga rentang bucket diatur per 24 jam.expireAfterSeconds: 604800— TTL: measurement otomatis dihapus saatwaktu-nya berumur lebih dari 7 hari; ambangnya dihitung dari nilaitimeField, bukan waktu insert.
Dari sisi aplikasi, insertMany dan find terasa persis seperti collection biasa — perbedaannya sepenuhnya di penyimpanan internal.
Mengintip Bucket Internal #
db.getCollectionInfos() mengonfirmasi tipe collection-nya, dan opsi rawData pada perintah find membuka bentuk mentah bucket-nya:
db.getCollectionInfos({ name: "cuaca" })
db.runCommand({ find: "cuaca", rawData: true, filter: {} })Output yang Dihasilkan:
[
{
name: 'cuaca',
type: 'timeseries',
options: {
expireAfterSeconds: Long('604800'),
timeseries: {
timeField: 'waktu',
metaField: 'sensor',
granularity: 'minutes',
bucketMaxSpanSeconds: 86400,
fixedBucketing: true
}
},
info: { readOnly: false, uuid: UUID('cfe87688-404d-4054-8c19-a6ef0cdfdcab') }
}
]
{
cursor: {
firstBatch: [
{
_id: ObjectId('6b1a07a0387cb63c7f8ee9b3'),
control: {
version: 2,
min: {
waktu: ISODate('2026-12-10T02:00:00.000Z'),
suhu: 22.1,
kelembapan: 85,
_id: ObjectId('6ad3dff997eae5fc8738dcce')
},
max: {
waktu: ISODate('2026-12-10T02:03:00.000Z'),
suhu: 22.1,
kelembapan: 85,
_id: ObjectId('6ad3dff997eae5fc8738dcce')
},
count: 1
},
meta: { kota: 'Bandung', sensor_id: 'BDG-07' },
data: {
_id: Binary.createFromBase64('BwBq09/5l+rl/Ic43M4A', 7),
kelembapan: Binary.createFromBase64('EABVAAAAAA==', 7),
waktu: Binary.createFromBase64('CQAgiLBdogEAAAA=', 7),
suhu: Binary.createFromBase64('AQCamZmZmRk2QAA=', 7)
}
},
{
_id: ObjectId('6b1a07a0387cb63c7f8ee9b4'),
control: {
version: 2,
min: {
waktu: ISODate('2026-12-10T02:00:00.000Z'),
suhu: 27.4,
kelembapan: 75,
_id: ObjectId('6ad3dff997eae5fc8738dccb')
},
max: {
waktu: ISODate('2026-12-10T02:02:00.000Z'),
suhu: 27.9,
kelembapan: 78,
_id: ObjectId('6ad3dff997eae5fc8738dccd')
},
count: 3
},
meta: { kota: 'Jakarta', sensor_id: 'JKT-01' },
data: {
_id: Binary.createFromBase64('BwBq09/5l+rl/Ic43MuALQAAAAAAAAAA', 7),
kelembapan: Binary.createFromBase64('EABOAAAAgB0AAAAMAAAAAA==', 7),
waktu: Binary.createFromBase64('CQAAya1dogEAAIANTB0AAAAAAAA=', 7),
suhu: Binary.createFromBase64('AQBmZmZmZmY7QKBNAAAAGAAAAAA=', 7)
}
}
],
id: Long('0'),
ns: 'latihan_timeseries.cuaca'
},
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1792270329, i: 4 }),
signature: {
hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
keyId: Long('0')
}
},
operationTime: Timestamp({ t: 1792270329, i: 4 })
}Empat pembacaan ternyata disimpan sebagai dua bucket. Anatomi setiap bucket:
meta— nilaimetaFielddisimpan sekali untuk seluruh isinya; inilah sumber penghematan terbesar.data— setiap field measurement (waktu,suhu,kelembapan) menjadi satu kolom biner terkompresi; nilai-nilai sejenis berdampingan sehingga kompresi bekerja optimal.control— ringkasanmin/max/countper kolom, dipakai server untuk memangkas bucket saat query rentang waktu tanpa membongkar isinya.
Perhatikan juga keluaran getCollectionInfos: granularity: "minutes" diterjemahkan menjadi bucketMaxSpanSeconds: 86400 — satu bucket menampung maksimum data 24 jam. Sejak MongoDB 9.0 seluruh data ini hidup dalam satu namespace; mengakses system.buckets.cuaca secara langsung ditolak server, dan rawData adalah jalur resmi untuk membaca format mentah.
Batasan Update dan Delete #
Time series collection dirancang untuk data yang tidak berubah, tetapi bukan berarti sepenuhnya read-only. Sejak MongoDB 5.1, update dan delete diizinkan dengan syarat ketat: filter hanya boleh menyentuh metaField, dan update hanya boleh memodifikasi metaField. Kita uji ketiganya:
db.cuaca.updateOne({ waktu: ISODate("2026-12-10T02:03:00Z") }, { $set: { suhu: 22.2 } })Output yang Dihasilkan:
Uncaught
MongoServerError: Command error committing internal transaction :: caused by :: Cannot perform a non-multi update on a time-series collectionGalat pertama muncul karena updateOne selalu mengirim multi: false. Ganti ke updateMany dengan filter metaField (pakai dot notation agar bebas dari urutan field subdocument):
db.cuaca.updateMany({ "sensor.sensor_id": "BDG-07" }, { $set: { sensor: { sensor_id: "BDG-07", kota: "Kota Bandung" } } })
db.cuaca.find({ "sensor.sensor_id": "BDG-07" }, { _id: 0, waktu: 1, suhu: 1 })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
[ { waktu: ISODate('2026-12-10T02:03:00.000Z'), suhu: 22.1 } ]Operasi berikutnya berhasil karena filter dan perubahannya sama-sama menyentuh metaField saja — mengoreksi nama kota pada seluruh pembacaan sensor tersebut. Sebaliknya, mengubah suhu akan ditolak dengan pesan galat serupa (“updates a field that is not the metaField”). Untuk delete, aturan yang sama berlaku: filter hanya boleh metaField atau _id:
db.cuaca.deleteOne({ _id: db.cuaca.findOne({ "sensor.sensor_id": "BDG-07" })._id })
db.cuaca.countDocuments()Output yang Dihasilkan:
{ acknowledged: true, deletedCount: 1 }
3Batasan lain yang perlu dicatat: document maksimum 4 MB (bukan 16 MB), tidak bisa diubah menjadi tipe collection lain, tidak mendukung schema validation, dan tidak bisa ditulis di dalam transaksi. Change streams juga tidak tersedia untuk time series collection — kita akan melihat buktinya di Chapter Querying Time Series.
Rangkuman #
db.createCollection()dengan opsitimeseries(timeField,metaField,granularity) plusexpireAfterSecondsmembuat collection yang tampak normal dari luar tetapi berformat bucket kolom di dalam.rawData: truepadafindmemperlihatkan bucket mentah:metasekali per seri,databerupa kolom terkompresi,controlmenyimpan min/max/count untuk pemangkasan query.- Update/delete hanya boleh menyentuh
metaField(filter dan perubahan), wajibmulti: true; koreksi data pengukuran berarti tulis ulang measurement baru. - Retensi data sebaiknya diserahkan ke
expireAfterSeconds, bukan delete massal.