Optimasi dua babak sebelumnya menyasar jumlah document yang diperiksa di server. Babak ketiga terjadi setelahnya: berapa byte yang harus menyeberangi jaringan menuju aplikasi. Query yang mengembalikan 20 field padahal layar hanya menampilkan 3 field mengirim data percuma — dan payload besar berarti transfer lambat, memori klien penuh, plus deserialisasi yang lama. Kabar baiknya, perbaikannya paling murah di antara semua optimasi: cukup projection. Chapter ini mengukur dampaknya dengan bsonsize(), lalu melihat bagaimana cursor mengirim hasil dalam batch.
Peta materi Chapter ini:
- Mengukur ukuran document dan payload dengan
bsonsize()dan$bsonSize. - Perbandingan total byte sebelum dan sesudah projection.
- Cara kerja batch cursor: 101 document pertama dan batas 16 MB.
- Kasus document bersarang besar.
Mengukur dengan bsonsize() #
mongosh menyediakan fungsi bsonsize() yang mengembalikan ukuran sebuah document dalam byte hasil serialisasi BSON. Siapkan data transaksi standar:
use latihan_perf
db.penjualan.drop()
db.penjualan.insertMany([
{ _id: 1, waktu: ISODate("2026-03-02T09:15:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, harga_total: 98000, kota: "Jakarta", channel: "toko" },
{ _id: 2, waktu: ISODate("2026-03-02T11:40:00Z"), judul_buku: "Bumi", jumlah: 2, harga_total: 178000, kota: "Bandung", channel: "online" },
{ _id: 3, waktu: ISODate("2026-03-03T10:05:00Z"), judul_buku: "Filosofi Teras", jumlah: 1, harga_total: 89000, kota: "Jakarta", channel: "online" },
{ _id: 4, waktu: ISODate("2026-03-03T14:30:00Z"), judul_buku: "Atomic Habits", jumlah: 3, harga_total: 345000, kota: "Surabaya", channel: "toko" },
{ _id: 5, waktu: ISODate("2026-03-04T08:20:00Z"), judul_buku: "Rumah Kaca", jumlah: 1, harga_total: 72000, kota: "Bandung", channel: "toko" },
{ _id: 6, waktu: ISODate("2026-03-04T13:55:00Z"), judul_buku: "Bumi", jumlah: 1, harga_total: 89000, kota: "Jakarta", channel: "toko" },
{ _id: 7, waktu: ISODate("2026-03-05T09:45:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 2, harga_total: 130000, kota: "Yogyakarta", channel: "online" },
{ _id: 8, waktu: ISODate("2026-03-05T16:10:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, harga_total: 98000, kota: "Surabaya", channel: "online" },
{ _id: 9, waktu: ISODate("2026-03-06T10:30:00Z"), judul_buku: "Atomic Habits", jumlah: 1, harga_total: 115000, kota: "Jakarta", channel: "online" },
{ _id: 10, waktu: ISODate("2026-03-06T15:25:00Z"), judul_buku: "Bumi", jumlah: 4, harga_total: 356000, kota: "Yogyakarta", channel: "toko" },
{ _id: 11, waktu: ISODate("2026-03-07T09:00:00Z"), judul_buku: "Filosofi Teras", jumlah: 2, harga_total: 178000, kota: "Bandung", channel: "online" },
{ _id: 12, waktu: ISODate("2026-03-07T17:35:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 1, harga_total: 65000, kota: "Jakarta", channel: "toko" }
])Output yang Dihasilkan:
switched to db latihan_perf
true
{
acknowledged: true,
insertedIds: {
'0': 1,
'1': 2,
'2': 3,
'3': 4,
'4': 5,
'5': 6,
'6': 7,
'7': 8,
'8': 9,
'9': 10,
'10': 11,
'11': 12
}
}print("satu document:", bsonsize(db.penjualan.findOne()), "byte")
print("payload penuh (12 document):", bsonsize(db.penjualan.find().toArray()), "byte")Output yang Dihasilkan:
satu document: 125 byte
payload penuh (12 document): 1531 byteAngka pertama adalah ukuran satu document (sekitar seratus lebih byte — field pendek semuanya). Angka kedua mengukur array BSON yang berisi seluruh hasil — inilah perkiraan kasar apa yang harus dikirim server ke aplikasi bila query berjalan tanpa projection.
Total Sebelum dan Sesudah Projection #
Operator agregasi $bsonSize memungkinkan pengukuran dilakukan di server, sehingga kita bisa menjumlahkan dan merata-ratakan ukuran document dalam satu pipeline. Bandingkan payload penuh dengan payload yang hanya membawa field untuk layar daftar transaksi:
db.penjualan.aggregate([
{ $group: { _id: null, totalByte: { $sum: { $bsonSize: "$$ROOT" } }, rataByte: { $avg: { $bsonSize: "$$ROOT" } } } }
])
db.penjualan.aggregate([
{ $project: { judul_buku: 1, jumlah: 1, harga_total: 1, _id: 0 } },
{ $group: { _id: null, totalByte: { $sum: { $bsonSize: "$$ROOT" } }, rataByte: { $avg: { $bsonSize: "$$ROOT" } } } }
])Output yang Dihasilkan:
[ { _id: null, totalByte: 1488, rataByte: 124 } ]
[ { _id: null, totalByte: 748, rataByte: 62.333333333333336 } ]Pipeline pertama menjumlahkan document apa adanya; pipeline kedua memangkas ke tiga field sebelum diukur. Perhatikan bahwa $$ROOT pada pipeline kedua mengacu pada document hasil $project — sehingga yang dihitung adalah ukuran payload yang benar-benar dikirim, bukan document aslinya. Dari 1.488 byte menjadi 748 byte, payload terpangkas hampir separuh; pada document lebar di dunia nyata (puluhan field, teks panjang, array besar), penghematan 80-95% adalah hal biasa.
Batch dan Transfer Cursor #
Cursor tidak mengirim seluruh hasil sekaligus. Hasil dikirim per batch: untuk find(), batch pertama berisi hingga 101 document atau hingga total 16 MB — mana yang tercapai lebih dulu; sisa hasil menunggu perintah getMore dari driver. Perilaku ini bisa diamati langsung dengan memaksa batch kecil:
const cur = db.penjualan.find().batchSize(3)
cur.next()
cur.objsLeftInBatch()
cur.itcount()Output yang Dihasilkan:
{
_id: 1,
waktu: ISODate('2026-03-02T09:15:00.000Z'),
judul_buku: 'Laut Bercerita',
jumlah: 1,
harga_total: 98000,
kota: 'Jakarta',
channel: 'toko'
}
2
11Mari kita bedah baris-per-barisnya:
batchSize(3): meminta batch berisi maksimal 3 document per kali transfer.cur.next(): mengambil document pertama — REPL menampilkannya. Document ini diambil dari buffer batch yang sudah ada di sisi klien.cur.objsLeftInBatch(): mengembalikan2— batch pertama memuat 3 document, satu sudah terpakai.cur.itcount(): menghabiskan cursor dan mengembalikan11— sisa 11 document. Dua di antaranya berasal dari batch pertama, sisanya diambil lewatgetMoretambahan; aplikasi tidak pernah melihat proses ini karena driver mengelolanya di baliknext().
Batas 16 MB per batch ini juga alasan document tunggal dibatasi 16 MB — sebuah document harus muat dalam satu pesan balasan. Batch kecil menambah round-trip; batch besar menunda document pertama. Untuk sebagian besar aplikasi, default (101 document pertama, lalu menyesuaikan) sudah tepat.
Kasus Document Bersarang Besar #
Dampak projection paling terasa pada document dengan array bersarang besar. Collection pelanggan menyimpan riwayat pembelian di dalam document:
db.pelanggan.drop()
db.pelanggan.insertMany([
{
_id: 1, nama: "Budi Santoso", kota: "Jakarta", tier: "emas", poin: 1520,
riwayat: [
{ tanggal: ISODate("2026-01-05T10:00:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, total: 98000 },
{ tanggal: ISODate("2026-01-12T14:30:00Z"), judul_buku: "Bumi", jumlah: 2, total: 178000 },
{ tanggal: ISODate("2026-01-20T09:10:00Z"), judul_buku: "Filosofi Teras", jumlah: 1, total: 89000 },
{ tanggal: ISODate("2026-02-02T16:45:00Z"), judul_buku: "Atomic Habits", jumlah: 3, total: 345000 },
{ tanggal: ISODate("2026-02-14T11:20:00Z"), judul_buku: "Rumah Kaca", jumlah: 1, total: 72000 },
{ tanggal: ISODate("2026-02-21T13:05:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 2, total: 130000 },
{ tanggal: ISODate("2026-03-01T08:55:00Z"), judul_buku: "Bumi", jumlah: 1, total: 89000 },
{ tanggal: ISODate("2026-03-08T15:40:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, total: 98000 },
{ tanggal: ISODate("2026-03-15T10:25:00Z"), judul_buku: "Filosofi Teras", jumlah: 2, total: 178000 },
{ tanggal: ISODate("2026-03-22T17:10:00Z"), judul_buku: "Atomic Habits", jumlah: 1, total: 115000 }
]
},
{
_id: 2, nama: "Siti Rahayu", kota: "Bandung", tier: "perak", poin: 640,
riwayat: [
{ tanggal: ISODate("2026-02-05T10:00:00Z"), judul_buku: "Bumi", jumlah: 1, total: 89000 },
{ tanggal: ISODate("2026-02-19T14:30:00Z"), judul_buku: "Rumah Kaca", jumlah: 2, total: 144000 },
{ tanggal: ISODate("2026-03-06T09:10:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 1, total: 65000 }
]
},
{
_id: 3, nama: "Dewi Lestari", kota: "Surabaya", tier: "perak", poin: 310,
riwayat: [
{ tanggal: ISODate("2026-03-03T11:00:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, total: 98000 },
{ tanggal: ISODate("2026-03-10T15:30:00Z"), judul_buku: "Bumi", jumlah: 1, total: 89000 }
]
}
])Output yang Dihasilkan:
true
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }Halaman daftar pelanggan hanya membutuhkan identitas dan status — seluruh array riwayat tidak dipakai. Ukurlah keduanya:
const penuh = db.pelanggan.aggregate([ { $group: { _id: null, total: { $sum: { $bsonSize: "$$ROOT" } } } } ]).toArray()
const ringkas = db.pelanggan.aggregate([
{ $project: { nama: 1, kota: 1, tier: 1, poin: 1 } },
{ $group: { _id: null, total: { $sum: { $bsonSize: "$$ROOT" } } } }
]).toArray()
print("payload penuh:", penuh[0].total, "byte | hanya identitas:", ringkas[0].total, "byte")Output yang Dihasilkan:
payload penuh: 1423 byte | hanya identitas: 242 byteSelisihnya hampir enam kali lipat — dan array riwayat itu terus bertambah setiap transaksi baru, sehingga jurangnya melebar sendiri seiring waktu. Pola desain yang lebih permanen untuk kasus ini adalah subset pattern: simpan beberapa entri terakhir di dalam document dan pindahkan riwayat penuh ke collection terpisah (kita bahas tuntas di Section Data Modeling). Projection menutup biaya transfernya; subset pattern menutup biaya simpan dan tulisnya juga.
Rangkuman #
bsonsize()mengukur satu document;$bsonSizemengukur di dalam pipeline agregasi sehingga bisa dijumlahkan dan dirata-ratakan.- Projection memangkas payload jaringan secara besar-besaran, terutama pada document dengan array bersarang — tetapi
FETCHtetap membaca document utuh kecuali query ter-cover index. - Cursor mengirim hasil per batch: 101 document pertama atau 16 MB;
batchSize()danobjsLeftInBatch()memperlihatkan mekanismenya. - Untuk array yang terus membesar, projection adalah perbaikan cepat; subset pattern adalah perbaikan struktural.