BigQuery MigrationClickHouse Workshops

03 Migrasi yang malas

Biarkan ClickHouse menginfer schema export BigQuery sendiri, ingest tanpa diubah, dan lihat migrasi yang berfungsi tapi biasa-biasa saja.

Langkah pertama yang jujur

Hal paling menggoda untuk dilakukan terhadap sumber data baru apa pun adalah mengambil schema yang sudah diberikan dan memakainya begitu saja. Itulah yang dilakukan modul ini: tidak ada redesign, tidak ada opini soal tipe data, tidak ada apa pun yang ditanam khusus untuk lab ini. Apa pun yang diinfer ClickHouse dari export Parquet BigQuery sendiri adalah persis apa yang dibuat dan diload.

Schema: hasil generate, bukan tulisan tangan

DDL di bawah bukan sesuatu yang ditulis tangan oleh siapa pun. DDL ini keluar langsung dari menjalankan DESCRIBE TABLE terhadap bucket dari modul 02 dan membungkus setiap pasangan nama/tipe yang diinfer menjadi definisi kolom -- inferensi yang sama persis yang sudah Anda lihat:

CREATE DATABASE IF NOT EXISTS bq;

CREATE TABLE bq.events_naive
(
  `event_date` Nullable(String),
  `event_timestamp` Nullable(Int64),
  `event_name` Nullable(String),
  `event_params` Array(Tuple( key Nullable(String), value Tuple( string_value Nullable(String), int_value Nullable(Int64), float_value Nullable(Float64), double_value Nullable(Float64)))),
  `event_previous_timestamp` Nullable(Int64),
  `event_value_in_usd` Nullable(Float64),
  `event_bundle_sequence_id` Nullable(Int64),
  `event_server_timestamp_offset` Nullable(Int64),
  `user_id` Nullable(String),
  `user_pseudo_id` Nullable(String),
  `privacy_info` Tuple( analytics_storage Nullable(Int64), ads_storage Nullable(Int64), uses_transient_token Nullable(String)),
  `user_properties` Array(Tuple( key Nullable(Int64), value Tuple( string_value Nullable(Int64), int_value Nullable(Int64), float_value Nullable(Int64), double_value Nullable(Int64), set_timestamp_micros Nullable(Int64)))),
  `user_first_touch_timestamp` Nullable(Int64),
  `user_ltv` Tuple( revenue Nullable(Float64), currency Nullable(String)),
  `device` Tuple( category Nullable(String), mobile_brand_name Nullable(String), mobile_model_name Nullable(String), mobile_marketing_name Nullable(String), mobile_os_hardware_model Nullable(Int64), operating_system Nullable(String), operating_system_version Nullable(String), vendor_id Nullable(Int64), advertising_id Nullable(Int64), language Nullable(String), is_limited_ad_tracking Nullable(String), time_zone_offset_seconds Nullable(Int64), web_info Tuple( browser Nullable(String), browser_version Nullable(String))),
  `geo` Tuple( continent Nullable(String), sub_continent Nullable(String), country Nullable(String), region Nullable(String), city Nullable(String), metro Nullable(String)),
  `app_info` Tuple( id Nullable(String), version Nullable(String), install_store Nullable(String), firebase_app_id Nullable(String), install_source Nullable(String)),
  `traffic_source` Tuple( medium Nullable(String), name Nullable(String), source Nullable(String)),
  `stream_id` Nullable(Int64),
  `platform` Nullable(String),
  `event_dimensions` Tuple( hostname Nullable(String)),
  `ecommerce` Tuple( total_item_quantity Nullable(Int64), purchase_revenue_in_usd Nullable(Float64), purchase_revenue Nullable(Float64), refund_value_in_usd Nullable(Float64), refund_value Nullable(Float64), shipping_value_in_usd Nullable(Float64), shipping_value Nullable(Float64), tax_value_in_usd Nullable(Float64), tax_value Nullable(Float64), unique_items Nullable(Int64), transaction_id Nullable(String)),
  `items` Array(Tuple( item_id Nullable(String), item_name Nullable(String), item_brand Nullable(String), item_variant Nullable(String), item_category Nullable(String), item_category2 Nullable(String), item_category3 Nullable(String), item_category4 Nullable(String), item_category5 Nullable(String), price_in_usd Nullable(Float64), price Nullable(Float64), quantity Nullable(Int64), item_revenue_in_usd Nullable(Float64), item_revenue Nullable(Float64), item_refund_in_usd Nullable(Float64), item_refund Nullable(Float64), coupon Nullable(String), affiliation Nullable(String), location_id Nullable(String), item_list_id Nullable(String), item_list_name Nullable(String), item_list_index Nullable(String), promotion_id Nullable(String), promotion_name Nullable(String), creative_name Nullable(String), creative_slot Nullable(String)))
)
ENGINE = MergeTree
ORDER BY event_timestamp
SETTINGS allow_nullable_key = 1;

Setiap kolom adalah Nullable. event_date adalah String, bukan Date. event_timestamp adalah Int64 mentah berisi microsecond, bukan DateTime. user_properties diinfer sebagai tuple all-Int64 hanya karena export ini tidak pernah mengisi field itu -- itu adalah bentuk export BigQuery dan data nyata workshop ini, bukan penyederhanaan yang dibuat khusus untuk lab.

Mengapa allow_nullable_key bukan opsional

Jalankan CREATE TABLE ini tanpa setting itu dan langsung ditolak: kolom nullable BigQuery tidak bisa membentuk sort key sendiri, dan event_timestamp -- satu-satunya kolom yang disortir schema naive ini -- adalah Nullable(Int64) karena itulah yang diinfer dari sumbernya. Ini adalah friksi yang langsung ditemui kopi yang setia, tipe-demi-tipe, dan ini adalah bagian dari bentuk schema BigQuery, bukan sesuatu yang ditanam untuk workshop ini.

event_date, yang duduk tepat di sebelahnya, punya masalah kebalikannya yang tersembunyi di dalam non-masalah: ia adalah String dalam bentuk %Y%m%d, sehingga min(), max(), dan perbandingan range di atasnya kebetulan keluar dengan benar -- urutan leksikografis dan urutan kalender sepakat untuk format yang tepat itu. Itu keberuntungan, bukan desain. Layout tanggal lain apa pun dalam string yang sama akan merusak ini secara diam-diam, tanpa error yang memperingatkan Anda.

Load dengan ClickPipes

Tabelnya sudah ada; sekarang isi dari bucket yang sama yang dibaca modul 02 secara langsung. Di console service Anda:

  1. Buka Data sources di menu kiri, lalu mulai ClickPipe baru (diberi label kira-kira Create ClickPipe atau Set up a ClickPipe, bergantung pada versi console Anda).

    Halaman Data sources ClickHouse Cloud, dengan tombol "Create ClickPipe" di bawah "Effortless data ingestion with ClickPipes"

  2. Pilih Google Cloud Storage sebagai source.

    Langkah "Select the data source" pada wizard ClickPipe, dengan Google Cloud Storage disorot di antara pilihan object storage

  3. Atur autentikasi ke public, tanpa kredensial -- bucket-nya menerima anonymous read, sama dengan NOSIGN yang Anda berikan ke gcs() di modul 02 -- dan paste path-nya:

    https://storage.googleapis.com/ch-workshop-bq-migration/ga4-events/*.parquet

    Langkah "Setup your ClickPipe connection", dengan Authentication method diatur ke Public dan GCS file path sudah ditempel

  4. Atur format file ke Parquet.

    Langkah "Incoming data", menampilkan daftar objek Parquet yang cocok di bucket dengan File type diatur ke Parquet

  5. Di langkah destination, arahkan pipe ke tabel yang baru Anda buat, bq.events_naive, bukan membiarkannya membuat tabel baru. Kolom Parquet yang masuk cocok dengannya berdasarkan nama.

    Langkah "Parse information", dengan "Existing table" dipilih, database bq dan tabel events_naive terpilih, dan setiap kolom source otomatis terpetakan ke kolom tabelnya berdasarkan nama

  6. Di langkah terakhir, klik Create ClickPipe -- tidak ada tombol "start" terpisah, ini sekaligus membuat dan menjalankannya -- lalu tunggu status-nya berubah menjadi Completed.

    Langkah "Details and settings", menampilkan panel Permissions diatur ke Full access dan tombol Create ClickPipe

    Daftar Data sources beberapa saat setelah dibuat, menampilkan pipe BqMigration baru dengan status Provisioning dan 0 record -- wajar jika ini masih berjalan, belum Completed, tepat setelah Anda klik Create

    Provisioning, belum Completed -- ini adalah pipe beberapa detik setelah dibuat. Beri waktu sedikit lebih lama dan periksa lagi statusnya sebelum melanjutkan.

Periksa apakah sudah masuk

SELECT count() FROM bq.events_naive;

Harapkan 4,295,584 -- hitungan yang sama yang didapat modul 02 saat membaca bucket secara langsung, kali ini dari tabel yang dimiliki ClickHouse di disk.

Jalankan query funnel

SELECT event_name, count() AS n
FROM bq.events_naive
WHERE event_name IN ('view_item', 'add_to_cart', 'begin_checkout', 'purchase')
GROUP BY event_name
ORDER BY n DESC;

Empat angka yang sama seperti modul 02, dalam urutan ORDER BY n DESC yang sama, karena ini data yang sama:

event_namen
view_item386,068
add_to_cart58,543
begin_checkout38,757
purchase5,692

Yang berubah adalah tempat data itu tinggal: query ini membaca baris yang sudah dimiliki ClickHouse di disk, bukan Parquet yang harus diambil dan diparsing ulang lewat network setiap kali dipanggil. Diukur pada service yang sama dengan 0.178s di modul 02:

0.041selapsed, satu kali run
1 replica, 12 GiBukuran service, sama seperti modul 02

Rasa lambat dari modul 02 sudah hilang.

Di mana Anda berakhir

Ini adalah migrasi yang berfungsi. Tabelnya ada, menyimpan setiap baris yang diekspor BigQuery, dan query funnel di atas mengembalikan jawaban yang benar. Itulah seluruh modul ini: tidak ada yang mengoptimalkan apa pun, tidak ada yang memilih satu tipe pun, dan tetap berfungsi.

Ini juga biasa-biasa saja, dengan sengaja. Setiap kolom masih Nullable. Sort key-nya adalah satu Int64 timestamp tanpa hubungan apa pun dengan cara data ini benar-benar diquery siapa pun. Kolom event_params dan items masih berupa array of tuples bersarang, persis seperti diekspor BigQuery. Tidak ada satu pun dari itu yang sudah menimbulkan biaya, karena belum ada yang mengajukan pertanyaan yang lebih sulit ke tabel ini selain "berapa banyak baris." Ini migrasi yang berfungsi, dan biasa-biasa saja -- itulah keseluruhan poin modul ini, bukan setup untuk sebuah kejutan.

Lihat seberapa besar tabel naive-nya

"Belum ada yang menimbulkan biaya" adalah klaim yang bisa Anda periksa. Lihat berapa biaya migrasi yang biasa-biasa saja dan belum di-tuning ini dalam byte:

SELECT
  sum(data_compressed_bytes)   AS compressed_bytes,
  sum(data_uncompressed_bytes) AS uncompressed_bytes
FROM system.parts
WHERE active AND database = 'bq' AND table = 'events_naive';

Diukur pada service ClickHouse Cloud saat menyiapkan workshop ini -- perkirakan service Anda sendiri berada di kisaran yang sama, tidak harus identik bit-demi-bit:

348,759,309 Bterkompresi (332.60 MiB)
5,897,273,025 Btidak terkompresi (5,624.08 MiB)

Tidak ada yang memilih tipe, codec, atau sort key untuk mendapatkan angka itu -- itu adalah apa pun yang dilakukan kompresi default ClickHouse terhadap schema hasil inferensi BigQuery sendiri, pada baris yang belum pernah ditanya pertanyaan lebih sulit dari "berapa banyak." Modul 04 meminta Anda menyimpan 4,295,584 baris yang sama, tanpa kehilangan informasi apa pun, dalam byte yang lebih sedikit dari ini.

Selesai jika

bq.events_naive menyimpan 4,295,584 baris, query funnel mengembalikan empat angka yang sama seperti modul 02, Anda bisa menjelaskan dalam satu kalimat mengapa allow_nullable_key diperlukan, dan Anda sudah melihat berapa byte compressed yang dibutuhkan tabel naive-nya. Lanjut ke 04 Mengecilkan tabel jika Anda sudah siap.

Di halaman ini

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

ID