Troubleshooting
Simptom, sebab, dan perbaikan untuk kegagalan yang benar-benar diproduksi workshop ini -- yang benar-benar dialami persiapannya sendiri, bukan daftar generik.
Materi referensi, bukan modul untuk dikerjakan berurutan. Datang ke sini saat sesuatu berperilaku berbeda dari yang dikatakan sebuah modul sebelumnya untuk diharapkan.
Count yang kira-kira sepuluh kali terlalu besar
Simptom: count() bersama apa pun yang menyentuh event_params mengembalikan angka
yang jauh lebih besar dari row count yang Anda harapkan -- kira-kira sepuluh kali terlalu
besar adalah bentuk spesifik yang diproduksi dataset ini.
Sebab: arrayJoin di dalam sebuah SELECT list tidak hanya mentransformasi kolom
yang dipanggilnya. Ia mengekspansi setiap baris di seluruh query menjadi satu baris per
elemen array, dan setiap agregat lain di query yang sama -- termasuk count() -- melihat
baris yang sudah terekspansi, bukan baris aslinya. Instinct UNNEST-di-FROM dari
BigQuery menggunakan arrayJoin di sini dan mendapat kejutan ini.
-- WRONG: count() here counts event_params entries, not events
SELECT
count() AS n,
uniqExact(arrayJoin(event_params).key) AS distinct_keys
FROM bq.events_naive;
-- n: 46,095,652 -- not the event count (4,295,584), but the total number of
-- (key, value) pairs across every event's event_params array: 10.73 per eventPerbaikan: gunakan kombinator -Array sebagai gantinya. uniqExactArray, sumArray,
dan yang lainnya berlaku element-wise, tanpa mengalikan baris:
-- RIGHT: pull the key array out first, no arrayJoin needed
SELECT
count() AS n,
uniqExactArray(event_params.key) AS distinct_keys
FROM bq.events_naive;
-- n: 4,295,584 -- matches the real event countJika sebuah count pernah terlihat lebih besar dari yang Anda harapkan, periksa apakah
sebuah arrayJoin menyelip di SELECT list yang sama dulu, sebelum Anda mencurigai
data-nya.
Materialized view yang tidak mengembalikan apa-apa
Simptom: Anda membuat materialized view di atas tabel yang sudah ada, query-nya, dan mendapat nol baris kembali -- padahal tabel sumbernya jelas tidak kosong.
Sebab: sebuah MATERIALIZED VIEW hanya melihat baris yang di-insert setelah ia
dibuat. Ia adalah trigger pada insert baru, bukan query yang dijalankan ulang terhadap
data yang sudah ada. Baris yang sudah duduk di tabel sumber saat view dibuat tidak pernah
lewat melaluinya.
Perbaikan: backfill secara terpisah, sebagai langkah eksplisitnya sendiri, begitu view-nya ada:
INSERT INTO bq.funnel_agg
SELECT /* the same SELECT the view's definition uses */
FROM bq.events_tuned;Lakukan ini sekali, tepat setelah membuat view-nya, sebelum Anda mempercayai agregat apa pun yang dibaca darinya.
CREATE TABLE gagal dengan "Sorting key contains nullable columns"
Simptom: CREATE TABLE langsung ditolak dengan error yang menyebutkan kolom nullable
di sort key.
Sebab: export BigQuery membuat setiap kolom Nullable, dan sort key MergeTree
tidak bisa dibangun pada kolom nullable secara default -- NULL tidak punya posisi yang
didefinisikan dalam sort order.
Perbaikan: tambahkan setting yang mengizinkannya:
CREATE TABLE bq.events_naive (...)
ENGINE = MergeTree
ORDER BY event_timestamp
SETTINGS allow_nullable_key = 1;atau, lebih baik, jangan bawa nullability itu sama sekali: beri kolom terdepan sort key
Anda tipe non-nullable yang nyata dengan default yang sensible alih-alih Nullable.
Setting-nya melewati error, tapi tipe non-nullable adalah perbaikan yang sebenarnya, dan
itu yang dihargai baik challenge kompresi maupun sort-key.
event_date terurut dengan benar, tapi hanya karena keberuntungan
Simptom: filter range tanggal dan min()/max() di atas event_date memberi
jawaban yang benar, padahal event_date adalah String, bukan Date.
Sebab: event_date adalah string dalam bentuk %Y%m%d -- "20260115". Urutan
leksikografis string dan urutan kalender kebetulan sepakat untuk format yang tepat itu,
sehingga perbandingan keluar dengan benar secara kebetulan, bukan karena ClickHouse
memahaminya sebagai tanggal. Layout lain apa pun dari informasi yang sama --
"1/15/2026", atau locale yang menulis hari lebih dulu -- akan merusak setiap satu
perbandingan ini secara diam-diam, tanpa error untuk menandainya.
Perbaikan: tidak ada yang rusak untuk diperbaiki di dataset ini, tapi jangan membangun
di atas string itu seolah-olah itu tipe tanggal yang nyata. Parse dengan
toDate(parseDateTime64BestEffort(event_date)) atau yang serupa sebelum mengandalkannya
untuk apa pun di luar equality string, dan jangan pernah mengasumsikan tanggal
string-encoded dari export BigQuery akan terurut dengan benar secara umum.
Query yang terlihat tidak lebih cepat di console
Simptom: Anda membangun ulang sebuah tabel secara khusus untuk membuat query lebih cepat, menjalankan kedua versi di SQL console, dan waktu wall-clock-nya nyaris tidak berubah.
Sebab: yang diukur jam console mencakup round trip network yang lengkap antara browser Anda dan service-nya -- puluhan hingga ratusan milidetik, setiap run -- di atas apa pun yang dilakukan query-nya sendiri. Round trip itu tidak mengecil berapa pun bagus Anda menuning query-nya, dan itu bisa lebih besar dari keseluruhan peningkatan yang Anda coba ukur.
Perbaikan: jangan percaya jam console untuk apa pun yang lebih kecil dari round trip
itu. Baca read_rows langsung dari results bar console-nya sebagai gantinya -- tidak
seperti jam dinding, itu tidak berubah antara run dari query yang sama, karena itu
menghitung sesuatu yang benar-benar dilakukan query-nya, bukan seberapa lama round trip-nya
berlangsung. Modul 05 dan 06 membahas ini secara lengkap.
ClickPipes tidak menunjukkan baris apa pun
Simptom: Anda membuat ClickPipe, statusnya running atau completed, dan tabel destination-nya kosong atau kurang dari hitungan yang diharapkan.
Sebab: hampir selalu salah satu dari dua hal. Glob path source-nya sebenarnya tidak
cocok dengan object di bucket-nya -- typo di prefix-nya, * yang hilang, atau path yang
mengarah satu direktori terlalu tinggi atau terlalu rendah. Atau pipe-nya belum benar-benar
selesai; "started" dan "completed" adalah state yang berbeda, dan pipe yang masih
melakukan ingesting akan under-report sampai selesai.
Perbaikan: periksa ulang path yang eksak terhadap yang dipakai modul 03 --
https://storage.googleapis.com/ch-workshop-bq-migration/ga4-events/*.parquet-- dan konfirmasi status pipe-nya membaca Completed, bukan sekadar Running,
sebelum Anda menghitung baris. Begitu sudah, SELECT count() FROM bq.events_naive
seharusnya mengembalikan 4,295,584.
Alias yang membayangi kolom sumbernya sendiri
Simptom: sebuah query berhasil dikompilasi dan mengembalikan baris, tapi sebuah value keluar salah dengan cara yang terlihat seperti membaca kolom yang salah -- paling sering saat meratakan sebuah field bersarang.
Sebab: output alias yang memakai ulang nama kolom sumber resolve ke alias-nya, bukan
kolom yang mendasarinya, di mana pun ia muncul lagi di query yang sama. Ini paling keras
menggigit saat nama yang alami dan jelas untuk sebuah field yang diratakan identik dengan
nama struct induknya sendiri -- traffic_source.source secara alami ingin dialiaskan
traffic_source, yang juga adalah kolom tuple traffic_source top-level milik
bq.events_naive sendiri. Referensikan traffic_source lagi di mana pun kemudian di
query yang sama dan sekarang artinya adalah alias, bukan struct-nya.
Perbaikan: kualifikasikan setiap referensi tabel sumber dengan alias tabel, sehingga tidak ada nama yang tersisa untuk dibayangi sebuah output alias:
-- WRONG: bare traffic_source is ambiguous once aliased below
SELECT traffic_source.source AS traffic_source, ...
FROM bq.events_naive;
-- RIGHT: qualify the source reference with a table alias
SELECT n.traffic_source.source AS traffic_source, ...
FROM bq.events_naive AS n;Ini terjadi tiga kali terpisah saat query referensi workshop ini sendiri dibangun, selalu saat meratakan schema bersarang. Kualifikasikan referensi sumber pada query apa pun yang baik membaca struct bersarang maupun menamai kolom output-nya sesuai nama struct-nya.
Mengukur sort key sebelum part-nya termerge
Simptom: Anda mengubah sort key sebuah tabel secara khusus untuk mengurangi baris yang dibaca, mengukurnya tepat setelah load data, dan peningkatannya terlihat jauh lebih kecil dari yang diharapkan -- atau tidak ada.
Sebab: satu INSERT massal mendarat sebagai beberapa part, masing-masing disortir
secara independen. Value yang sama -- baris satu user, baris satu hari, apa pun yang
dikelompokkan sort key Anda -- duduk di setiap part itu sampai background merge
menggabungkannya, sehingga sebuah query menyentuh satu granule per part alih-alih satu
granule total, berapa pun bagus sort key-nya. Ini adalah artifak transien dari bulk load
yang baru, bukan steady state yang akhirnya disettle tabelnya.
Diukur dalam workshop ini tepatnya: sebuah tabel membaca 57,344 baris di 5 granule yang tersebar tepat setelah loading, dan 16,384 baris di 2 granule yang bersebelahan beberapa menit kemudian, setelah background merge ClickHouse menyusul sendiri -- tabel yang sama, query yang sama, sort key yang sama.
Perbaikan: beri tabel beberapa menit setelah loading, lalu jalankan ulang pengukurannya -- percayai angka yang belakangan itu, bukan yang Anda ukur tepat setelah insert.
Modul 05 membahas ini secara lengkap, bersama query condition cache, yang memproduksi
ilusi yang terkait tapi berbeda -- sebuah baseline naive yang terlihat cepat secara
artifisial karena sudah pernah melihat predikat literal yang eksak yang sedang Anda ukur
waktunya. Lakukan keduanya -- tunggu part-nya termerge, dan matikan condition cache dengan
SETTINGS use_query_condition_cache = 0 -- sebelum mempercayai angka read_rows apa pun
yang diminta workshop ini dari Anda.