TL;DR
BigQuery on-demand nagih berdasarkan jumlah byte yang dipindai query kamu, bukan waktu eksekusi. Partisi motong tabel berdasarkan kolom tanggal atau integer, jadi query yang difilter per tanggal cuma baca partisi yang relevan. Clustering ngurutin data di dalam partisi berdasarkan sampai 4 kolom, bikin filter dan agregasi di kolom itu jauh lebih murah. Kombinasi keduanya plus kebiasaan nulis query yang benar bisa motong byte terpindai sampai lebih dari 90%.
BigQuery nagih kamu berdasarkan jumlah byte yang dipindai query, bukan berapa lama query-nya jalan. Query yang selesai dalam 2 detik bisa lebih mahal dari query yang jalan 30 detik, kalau dia baca lebih banyak kolom.
Ini yang bikin banyak tim kaget waktu lihat tagihan bulan pertama.
Dua alat utama buat nekan angka itu: partisi dan clustering. Keduanya diatur waktu bikin tabel, bukan waktu nulis query. Jadi kalau tabel kamu udah terlanjur jadi tanpa partisi, ya tiap query bakal baca semuanya.
Aku pakai dataset penjualan UMKM di sini. Tabel transaksi_mentah isinya 240 juta baris transaksi dari 2023 sampai 2025, ukurannya 61 GB.
Model on-demand BigQuery nagih per byte yang diproses. Kamu dapat 1 TiB gratis per bulan, sisanya dihitung per TiB dengan harga yang beda-beda per region. Tarif resmi dan terbaru ada di halaman pricing BigQuery.
Yang penting kamu pahami soal cara hitungnya:
SELECT * baca semua kolom. Di tabel 40 kolom, ini bisa 10 kali lebih mahal dari yang kamu butuh.LIMIT nggak ngurangin byte terpindai. BigQuery tetap baca semua data yang lolos filter, baru motong hasilnya.Poin ketiga sering bikin salah paham. SELECT * FROM tabel LIMIT 10 di tabel 61 GB tetap kena biaya 61 GB.
Sebelum eksekusi, BigQuery Studio nampilin estimasi byte di pojok kanan atas editor. Biasakan lihat angka itu.
Kalau kamu pakai command line, ada flag khusus buat dry run:
bq query --use_legacy_sql=false --dry_run \
'SELECT kota, SUM(total_belanja)
FROM penjualan.transaksi_mentah
GROUP BY kota'
Perintah ini nggak jalanin query-nya, cuma ngasih tau berapa byte yang bakal diproses. Gratis.
Tabel partisi adalah tabel yang dipecah jadi bagian-bagian lebih kecil berdasarkan satu kolom. Waktu kamu filter pakai kolom itu, BigQuery cuma baca bagian yang cocok dan lewatin sisanya. Byte terpindai turun sesuai proporsi partisi yang kepakai.
Ada tiga jenis partisi yang didukung:
| Jenis | Basis pemecahan | Cocok buat |
|---|---|---|
| Time-unit column | Kolom DATE, TIMESTAMP, atau DATETIME | Tabel transaksi, log, event |
| Ingestion time | Waktu baris masuk ke BigQuery | Streaming data tanpa kolom tanggal jelas |
| Integer range | Rentang nilai kolom integer | ID toko, kode wilayah numerik |
Buat data transaksi, partisi harian berdasarkan kolom tanggal hampir selalu pilihan yang benar.
CREATE OR REPLACE TABLE penjualan.transaksi_harian
PARTITION BY DATE(waktu_transaksi)
OPTIONS (
partition_expiration_days = 730,
require_partition_filter = TRUE
)
AS
SELECT * FROM penjualan.transaksi_mentah;
Dua opsi di situ yang sering dilewatin orang.
partition_expiration_days otomatis hapus partisi yang lebih tua dari 730 hari. Ini nekan biaya penyimpanan tanpa kamu perlu bikin job pembersihan.
require_partition_filter bikin BigQuery nolak query yang nggak nyantumin filter partisi. Query tanpa WHERE DATE(waktu_transaksi) ... bakal error, bukan jalan mahal. Ini pengaman paling efektif buat tim yang anggotanya banyak.
Kamu nggak bisa nambahin partisi ke tabel yang udah jadi. Harus bikin ulang.
CREATE TABLE penjualan.transaksi_baru
PARTITION BY DATE(waktu_transaksi)
CLUSTER BY kota, kategori_produk
AS SELECT * FROM penjualan.transaksi_mentah;
DROP TABLE penjualan.transaksi_mentah;
ALTER TABLE penjualan.transaksi_baru
RENAME TO transaksi_mentah;
Query CREATE TABLE AS SELECT itu sendiri kena biaya sekali, soalnya dia baca seluruh tabel lama. Anggap saja investasi. Kalau tabelnya dipakai puluhan query per hari, balik modal dalam hitungan hari.
Partisi misahin data ke folder terpisah berdasarkan satu kolom. Clustering ngurutin data di dalam tiap partisi berdasarkan sampai 4 kolom, jadi baris dengan nilai mirip disimpan berdekatan.
Efeknya: waktu kamu filter atau agregasi pakai kolom cluster, BigQuery bisa lewatin blok data yang pasti nggak cocok.
| Aspek | Partisi | Clustering |
|---|---|---|
| Jumlah kolom | 1 | Sampai 4, urutannya penting |
| Tipe kolom | Date, timestamp, atau integer | Hampir semua tipe termasuk STRING |
| Estimasi biaya sebelum jalan | Akurat | Nggak akurat, penghematan baru ketahuan setelah jalan |
| Bisa dipaksa lewat opsi | Ya, require_partition_filter | Nggak ada |
Urutan kolom di CLUSTER BY menentukan efektivitasnya. BigQuery ngurutin dari kolom pertama dulu, baru kolom kedua di dalam grup itu, dan seterusnya.
Jadi kalau kamu tulis CLUSTER BY kota, kategori_produk, filter WHERE kota = 'Surabaya' dapat penghematan penuh. Filter WHERE kategori_produk = 'Sembako' doang tanpa nyebut kota dapat penghematan jauh lebih kecil.
Aturannya: urutkan dari kolom yang paling sering dipakai buat filter.
CREATE OR REPLACE TABLE penjualan.transaksi_partisi
PARTITION BY DATE(waktu_transaksi)
CLUSTER BY kota, kategori_produk
OPTIONS (require_partition_filter = TRUE)
AS
SELECT * FROM penjualan.transaksi_mentah;
Beda sama partisi, clustering bisa diubah di tabel yang udah ada:
ALTER TABLE penjualan.transaksi_partisi
SET OPTIONS (clustering_fields = ['kota', 'metode_bayar']);
Perubahan ini cuma berlaku buat data yang masuk setelahnya. Data lama tetap urutan lama sampai BigQuery ngerapiin sendiri di belakang layar.
Ini perbandingan nyata dari dataset penjualan UMKM tadi. Pertanyaannya sederhana: berapa omzet per kategori produk di Surabaya sepanjang September 2025?
Query versi asal-asalan:
SELECT kategori_produk, SUM(total_belanja) AS omzet
FROM penjualan.transaksi_mentah
WHERE FORMAT_DATE('%Y-%m', DATE(waktu_transaksi)) = '2025-09'
AND kota = 'Surabaya'
GROUP BY kategori_produk;
Query versi benar di tabel yang udah dipartisi dan di-cluster:
SELECT kategori_produk, SUM(total_belanja) AS omzet
FROM penjualan.transaksi_partisi
WHERE DATE(waktu_transaksi) BETWEEN '2025-09-01' AND '2025-09-30'
AND kota = 'Surabaya'
GROUP BY kategori_produk;
| Skenario | Byte diproses | Waktu |
|---|---|---|
| Tabel polos, SELECT * dulu baru filter | 61,0 GB | 18 detik |
| Tabel polos, kolom dibatasi | 9,8 GB | 7 detik |
| Partisi saja | 3,1 GB | 4 detik |
| Partisi + clustering kota | 1,4 GB | 2 detik |
Dari 61 GB ke 1,4 GB. Turun 97,7%.
Perhatiin juga versi asal-asalan tadi. Dia pakai FORMAT_DATE() di kolom partisi. Membungkus kolom partisi dengan fungsi bikin BigQuery nggak bisa mangkas partisi, jadi dia baca semuanya walaupun tabelnya udah dipartisi.
Ini jebakan nomor satu. Filter kolom partisi harus langsung, jangan dibungkus fungsi apa pun.
SELECT *. Ini penghematan terbesar dan paling gampang. Di tabel lebar, bedanya bisa 6 kali lipat.WHERE tanggal >= '2025-09-01', bukan WHERE EXTRACT(MONTH FROM tanggal) = 9.require_partition_filter. Pengaman otomatis buat seluruh tim.CURRENT_TIMESTAMP() ke query yang seharusnya bisa di-cache.SELECT * LIMIT 100 nggak.Detail lengkap soal cara mangkas partisi ada di dokumentasi query tabel partisi.
Partisi di kolom yang jarang dipakai buat filter. Partisi cuma nolong kalau query kamu memang sering filter pakai kolom itu. Kalau 80% query kamu filter pakai kota bukan tanggal, partisi tanggal nggak banyak nolong.
Partisi terlalu halus. Ada batas 10.000 partisi per tabel. Partisi harian selama 27 tahun udah nabrak batas itu. Buat data yang sangat panjang rentangnya, pertimbangin partisi bulanan.
Cluster di kolom dengan nilai unik terlalu banyak. Clustering di kolom transaksi_id nggak berguna. Setiap nilai cuma muncul sekali, nggak ada blok yang bisa dilewatin.
Cluster di kolom dengan nilai terlalu sedikit. Kolom status yang isinya cuma "sukses" dan "gagal" juga nggak banyak nolong. Sasaran yang bagus: puluhan sampai ribuan nilai unik.
Ngandelin estimasi byte buat tabel cluster. Estimasi di editor nggak memperhitungkan clustering. Angka yang tampil bakal lebih besar dari tagihan sebenernya. Cek angka aslinya di detail job setelah query selesai.
Nggak mantau siapa yang habis paling banyak. BigQuery nyimpen riwayat semua job di INFORMATION_SCHEMA. Query ini nunjukin pengguna paling boros 30 hari terakhir:
SELECT
user_email,
COUNT(*) AS jumlah_query,
ROUND(SUM(total_bytes_billed) / POW(1024, 4), 2) AS total_tib
FROM `region-asia-southeast2`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type = 'QUERY'
GROUP BY user_email
ORDER BY total_tib DESC
LIMIT 10;
Jalanin sekali sebulan. Biasanya 2 sampai 3 orang nyumbang sebagian besar tagihan, dan penyebabnya satu dashboard yang refresh tiap 5 menit.
Model on-demand bayar per byte. Model kapasitas bayar per slot yang kamu pesan, berapa pun byte yang diproses.
Patokan kasarnya: kalau pemakaian bulanan kamu udah stabil dan tinggi, model kapasitas biasanya lebih murah dan lebih gampang diprediksi. Kalau pemakaiannya naik turun dan masih di bawah beberapa TiB per bulan, on-demand lebih masuk akal.
Rapiin dulu partisi dan query kamu sebelum mikir ganti model. Banyak tim yang ganti ke kapasitas padahal masalahnya cuma SELECT * di dashboard.
Nggak. LIMIT cuma motong jumlah baris yang ditampilin, bukan jumlah data yang dipindai. Query dengan LIMIT 10 di tabel 61 GB tetap kena biaya 61 GB kalau nggak ada filter partisi. Kalau kamu cuma mau ngintip isi tabel, pakai tab preview di konsol yang gratis.
Empat kolom, dan urutannya menentukan. BigQuery ngurutin data dari kolom pertama dulu. Filter yang nyebut kolom pertama dapat penghematan paling besar. Filter yang cuma nyebut kolom keempat tanpa tiga kolom sebelumnya hampir nggak dapat manfaat.
Nggak bisa langsung. Kamu harus bikin tabel baru dengan CREATE TABLE AS SELECT yang udah pakai PARTITION BY, lalu ganti nama. Clustering beda, itu bisa diubah kapan saja lewat ALTER TABLE SET OPTIONS, tapi cuma berlaku buat data yang masuk setelahnya.
Ada tiga lapis. Pertama, setel maximum bytes billed di tiap query biar query mahal langsung gagal. Kedua, nyalain require_partition_filter di tabel besar. Ketiga, pasang kuota harian per pengguna atau per project di setelan kuota Google Cloud.
Nggak. Clustering nggak nambah biaya penyimpanan dan proses pengurutan ulang di belakang layar juga nggak ditagih ke kamu. Yang bisa nambah biaya cuma proses bikin ulang tabelnya sekali di awal.
Tiga langkah yang paling cepat ngasih hasil. Ganti semua SELECT * di query rutin kamu. Bikin ulang tabel besar dengan partisi tanggal plus require_partition_filter. Tambahin clustering di kolom yang paling sering kamu filter.
Coba jalanin query INFORMATION_SCHEMA di atas minggu ini. Biasanya langsung ketahuan satu atau dua query yang nyedot setengah tagihan.
Mau latihan nulis query agregasi yang efisien? Coba soal interaktif di NgulikSQL. Buat konsep dasarnya, baca juga soal aggregate function dan Snowflake untuk analis kalau kamu lagi bandingin gudang data.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Kolom waktu isinya angka gede kayak 1704067200 dan bikin bingung? Itu Unix timestamp. Ini cara ngubahnya jadi tanggal beneran di SQL, plus balik lagi.
Data transaksi tersimpan UTC, tapi laporan harus jam WIB. Kalau salah konversi, angka penjualan tengah malam bisa kecatat di tanggal yang salah. Ini cara handle timezone di SQL dengan benar.
Nambah 30 hari ke tanggal invoice, ngurangin sebulan buat cari periode lalu, ngitung selisih hari antar order. Semua itu aritmetika tanggal, dan SQL punya operator INTERVAL buat ngerjainnya.