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:

js
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:

text
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
  }
}
js
print("satu document:", bsonsize(db.penjualan.findOne()), "byte")
print("payload penuh (12 document):", bsonsize(db.penjualan.find().toArray()), "byte")

Output yang Dihasilkan:

text
satu document: 125 byte

payload penuh (12 document): 1531 byte

Angka 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:

js
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:

text
[ { _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:

js
const cur = db.penjualan.find().batchSize(3)
cur.next()
cur.objsLeftInBatch()
cur.itcount()

Output yang Dihasilkan:

text
{
  _id: 1,
  waktu: ISODate('2026-03-02T09:15:00.000Z'),
  judul_buku: 'Laut Bercerita',
  jumlah: 1,
  harga_total: 98000,
  kota: 'Jakarta',
  channel: 'toko'
}
2
11

Mari kita bedah baris-per-barisnya:

  1. batchSize(3): meminta batch berisi maksimal 3 document per kali transfer.
  2. cur.next(): mengambil document pertama — REPL menampilkannya. Document ini diambil dari buffer batch yang sudah ada di sisi klien.
  3. cur.objsLeftInBatch(): mengembalikan 2 — batch pertama memuat 3 document, satu sudah terpakai.
  4. cur.itcount(): menghabiskan cursor dan mengembalikan 11 — sisa 11 document. Dua di antaranya berasal dari batch pertama, sisanya diambil lewat getMore tambahan; aplikasi tidak pernah melihat proses ini karena driver mengelolanya di balik next().

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:

js
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:

text
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:

js
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:

text
payload penuh: 1423 byte | hanya identitas: 242 byte

Selisihnya 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; $bsonSize mengukur di dalam pipeline agregasi sehingga bisa dijumlahkan dan dirata-ratakan.
  • Projection memangkas payload jaringan secara besar-besaran, terutama pada document dengan array bersarang — tetapi FETCH tetap membaca document utuh kecuali query ter-cover index.
  • Cursor mengirim hasil per batch: 101 document pertama atau 16 MB; batchSize() dan objsLeftInBatch() memperlihatkan mekanismenya.
  • Untuk array yang terus membesar, projection adalah perbaikan cepat; subset pattern adalah perbaikan struktural.