Chapter Pipeline Performance membuktikan bahwa batching — executemany plus commit per batch — mempercepat stage load puluhan kali, dan Chapter Mengukur Performa Pipeline memverifikasinya kembali dengan delta -90 persen. Satu pertanyaan praktis masih terbuka: berapa ukuran batch yang tepat? Batch 10 baris menanggung overhead pernyataan yang sama dengan batch 10 ribu, tetapi batch 50 ribu harus menimbun puluhan ribu baris di memori lebih dulu. Bab ini menyapu lima ukuran batch di SQLite, membaca CSV 800 ribu baris secara chunked, lalu menutup dengan cara memakai throughput sebagai anggaran kapasitas.

Sweep Ukuran Batch di SQLite #

Enam puluh ribu pesanan TokoKita dimuat ke SQLite dengan lima ukuran batch: 1, 100, 1.000, 10.000, dan 50.000 baris. Dua fungsi pengukur memisahkan dua pertanyaan yang berbeda — kecepatan murni dan memori realistis:

python
def ukur_throughput(batch):
    """Semua baris sudah materialisasi; ukur murni executemany + commit."""
    baris_semua = buat_baris(0, N)
    con.execute("DELETE FROM pesanan")
    t0 = time.perf_counter()
    for awal in range(0, N, batch):
        con.executemany("INSERT INTO pesanan VALUES (?,?,?,?,?,?)",
                        baris_semua[awal:awal + batch])
        con.commit()
    return time.perf_counter() - t0


def ukur_memori(batch):
    """Baris dibangun per batch (realistis saat streaming dari sumber)."""
    con.execute("DELETE FROM pesanan")
    tracemalloc.start()
    for awal in range(0, N, batch):
        segmen = buat_baris(awal, min(batch, N - awal))
        con.executemany("INSERT INTO pesanan VALUES (?,?,?,?,?,?)", segmen)
        con.commit()
        del segmen
    puncak = tracemalloc.get_traced_memory()[1] / 1e6
    tracemalloc.stop()
    return puncak

Sweep-nya sendiri menjalankan kedua pengukur untuk setiap ukuran batch:

python
print("=== A. executemany SQLite: 60.000 baris, sweep ukuran batch ===")
print(f"{'batch':>7}{'waktu (s)':>11}{'throughput':>16}{'memori puncak':>15}")
hasil_sweep = {}
for batch in UKURAN_BATCH:
    d = statistics.median([ukur_throughput(batch) for _ in range(3)])
    m = ukur_memori(batch)
    hasil_sweep[batch] = d
    print(f"{batch:>7}{d:>11.3f}{N / d:>12,.0f} b/s{m:>12.1f} MB")

Output yang Dihasilkan:

text
=== A. executemany SQLite: 60.000 baris, sweep ukuran batch ===
  batch  waktu (s)      throughput  memori puncak
      1      1.752      34,246 b/s         0.0 MB
    100      0.062     960,856 b/s         0.0 MB
   1000      0.044   1,349,015 b/s         0.2 MB
  10000      0.043   1,384,814 b/s         2.6 MB
  50000      0.045   1,340,185 b/s        12.9 MB

Mari kita telaah bagian-bagian penting kode di atas:

  1. Baris 38-40 (ukur_throughput): seluruh 60 ribu baris dibangun lebih dulu sehingga yang terukur murni biaya executemany dan commit — bukan biaya membangun data.
  2. Baris 43-46 (satu commit per batch): batch 1 berarti 60 ribu pernyataan plus 60 ribu commit, pola yang sama dengan baseline lambat di chapter sebelumnya; batch 1.000 memangkasnya menjadi 60 commit.
  3. Baris 50-58 (ukur_memori): baris dibangun per segmen lalu dihapus — meniru pipeline sungguhan yang streaming dari sumber, sehingga angka memorinya realistis, bukan teoretis.
  4. Baris 59 (get_traced_memory): puncak alokasi Python diambil sebelum tracemalloc dimatikan — angka ini yang muncul di kolom terakhir output.
  5. Baris 67-68 (median tiga run): setiap ukuran batch diukur tiga kali lalu diambil mediannya, tahan terhadap satu run yang tersendat.

Kurva yang Melandai dan Sweet Spot #

Pola hasilnya dua arah yang berbeda. Throughput melompat 28 kali saat batch naik dari 1 ke 100, tetapi hanya 1,4 kali dari 100 ke 1.000, dan praktis mendatar di kisaran 1,35-1,38 juta baris/detik setelahnya — bahkan batch 50.000 justru sedikit lebih lambat daripada 10.000. Memori bergerak sebaliknya: nyaris nol hingga batch 100, lalu naik hampir linear sampai 12,9 MB pada batch 50.000, dan akan terus membesar proporsional dengan lebar baris.

Angka absolut di atas khas mesin uji dan berbeda antar perangkat maupun antar run; yang konsisten adalah bentuk kurvanya — lompatan besar di awal, mendatar setelah orde ribuan, memori naik linear. Perilaku executemany didokumentasikan resmi di dokumentasi sqlite3 .

Membaca CSV Besar dengan Chunking #

Memori juga jadi kendala saat membaca: CSV transaksi 77,5 MB berisi 800 ribu baris harus diagregasi per kota. Pekerjanya berjalan di proses Python baru untuk setiap mode, sehingga puncak RSS yang dilaporkan benar-benar milik proses tersebut:

python
if MODE == "kosong":
    pass  # baseline: biaya import pandas saja
elif MODE == "penuh":
    df = pd.read_csv("transaksi_ch6.csv")
    hasil = df.groupby("kota")["nilai"].sum()
    del df
else:
    total = {}
    for chunk in pd.read_csv("transaksi_ch6.csv", chunksize=50_000):
        for kota, nilai in chunk.groupby("kota")["nilai"].sum().items():
            total[kota] = total.get(kota, 0) + nilai
    hasil = total

durasi = time.perf_counter() - t0
puncak_mb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
print(f"mode={MODE:<7} durasi={durasi:.2f} s  puncak RSS={puncak_mb:.0f} MB  "
      f"hasil={len(hasil) if MODE != 'kosong' else 0} kelompok")

Output yang Dihasilkan:

text
=== B. baca CSV 77.5 MB: sekaligus vs chunk 50.000 baris ===
(setiap mode dijalankan di proses Python baru; memori = peak RSS proses)
  mode=kosong  durasi=0.00 s  puncak RSS=114 MB  hasil=0 kelompok
  mode=penuh   durasi=1.05 s  puncak RSS=357 MB  hasil=5 kelompok
  mode=chunk   durasi=1.02 s  puncak RSS=157 MB  hasil=5 kelompok

Bedah singkat pekerjanya: mode kosong mengukur biaya tetap import pandas saja (114 MB); mode penuh memuat seluruh CSV ke satu DataFrame; mode chunk membaca potongan 50 ribu baris, mengagregasi tiap potongan, lalu menjumlahkan hasil parsialnya — total per kota tidak pernah menampung 800 ribu baris sekaligus. Hasil keduanya identik: lima kelompok kota dengan nilai yang sama.

Hasilnya lugas: durasi nyaris sama (1,05 vs 1,02 detik — chunking bukan alat kecepatan), tetapi puncak RSS turun dari 357 ke 157 MB. Setelah biaya tetap 114 MB dikurangkan, memori yang dipakai data menyusut dari 243 menjadi 43 MB — hampir 6 kali. Parameter chunksize didokumentasikan di referensi pandas read_csv .

Throughput sebagai Anggaran Kapasitas #

Angka baris/detik dari sweep bukan sekadar laporan — angka tersebut mengubah keputusan menjadi aritmetika. Lima juta pesanan pada 1,38 juta baris/detik berarti 3,6 detik; jendela batch 10 menit milik TokoKita menyisakan margin besar, dan bila volume suatu hari melonjak sepuluh kali, tabel sweep yang sama memberi tahu bahwa stage load tetap bukan prioritas — stage lain yang lebih lambat harus diukur lebih dulu, persis disiplin Mengukur Performa Pipeline. Kapasitas yang tidak cukup pun kini punya angka dasar: kebutuhan baris/detik dibanding throughput terukur menentukan kapan mesin perlu dinaikkan.

Satu aliran kerja kini terukur dan pas ukurannya. Dimensi berikutnya menjalankan beberapa aliran sekaligus: paralelisme menjanjikan percepatan linear di atas kertas, tetapi jumlah core, kontensi disk, dan urutan hasil menuntut disiplin ukur-sebelum-dan-sesudah yang sama. Chapter Parallelism melanjutkan.