Optymalizacja kosztów BigQuery w pipeline danych finansowych
W polskim sektorze fintech i usług finansowych BigQuery staje się standardem dla analitycznych procesów przetwarzania danych. Firmy przetwarzają dane transakcyjne, generują raporty dla KNF, obliczają wskaźniki ryzyka i zasilają modele scoringowe. Wszystko to na danych, które mają naturalną strukturę czasową (data transakcji, dzień rozliczeniowy) i wymiarową (klient, produkt, waluta) - co oznacza, że BigQuery można zoptymalizować bardzo agresywnie, jeśli się wie jak.
Jak BigQuery nalicza opłaty
BigQuery oferuje dwa modele cenowe:
| Model | Opłata | Kiedy opłacalny |
|---|---|---|
| On-demand | $6.25/TB przetworzonych danych | Sporadyczne zapytania, mały zespół, prototypy |
| Editions (sloty) | Od $0.04/slot/godzinę (Standard), autoscaling | Regularne pipeline, duże zespoły, przewidywalne obciążenie |
Kluczowa zasada: W modelu on-demand płacisz za ilość danych, którą zapytanie SKANUJE - nie za ilość wyników. Zapytanie SELECT * FROM transactions WHERE date = '2026-07-01' na niepartycjonowanej tabeli 500 GB skanuje 500 GB, nawet jeśli zwraca 1 GB danych.
To oznacza, że każda optymalizacja, która ogranicza ilość skanowanych danych, bezpośrednio obniża koszt.
Partycjonowanie - fundamentalna optymalizacja
Co to jest i dlaczego jest krytyczne
Partycjonowanie dzieli tabelę na segmenty fizyczne po wybranej kolumnie (najczęściej dacie). BigQuery skanuje tylko partycje, które spełniają warunek WHERE - reszta jest pomijana.
Dla danych finansowych partycjonowanie po dacie transakcji to absolutne minimum. Bez tego każde zapytanie dzienne skanuje całą tabelę historyczną.
Przykład: tabela transakcji
-- Tabela BEZ partycjonowania (zły wzorzec)
CREATE TABLE `project.dataset.transactions` (
transaction_id STRING,
account_id STRING,
amount NUMERIC,
currency STRING,
transaction_date DATE,
category STRING
);
-- Zapytanie dziennego raportu skanuje CAŁĄ tabelę (np. 2 TB = $12.50)
SELECT account_id, SUM(amount)
FROM `project.dataset.transactions`
WHERE transaction_date = '2026-07-01'
GROUP BY account_id;
-- Tabela Z partycjonowaniem (właściwy wzorzec)
CREATE TABLE `project.dataset.transactions`
(
transaction_id STRING,
account_id STRING,
amount NUMERIC,
currency STRING,
transaction_date DATE,
category STRING
)
PARTITION BY transaction_date
CLUSTER BY account_id, currency;
-- To samo zapytanie skanuje TYLKO partycję 2026-07-01 (np. 5 GB = $0.03)
SELECT account_id, SUM(amount)
FROM `project.dataset.transactions`
WHERE transaction_date = '2026-07-01'
GROUP BY account_id;
Oszczędność: z $12.50 na $0.03 - redukcja o 99.8% (przy 2 TB tabeli z dziennymi partycjami po ~5 GB).
Strategie partycjonowania dla danych finansowych
| Typ danych | Partycjonowanie | Clustering |
|---|---|---|
| Transakcje | PARTITION BY transaction_date | CLUSTER BY account_id, currency |
| Pozycje portfela (EOD) | PARTITION BY valuation_date | CLUSTER BY fund_id, instrument_type |
| Dane rynkowe (ticki) | PARTITION BY DATE(timestamp) | CLUSTER BY symbol, exchange |
| Raporty regulacyjne | PARTITION BY reporting_date | CLUSTER BY entity_id, report_type |
| Logi audytowe | PARTITION BY DATE(event_time) | CLUSTER BY user_id, action |
Wymuszanie użycia partycji (partition filter required)
Dla tabel, gdzie ktoś może przypadkowo zapomnieć o filtrze daty:
ALTER TABLE `project.dataset.transactions`
SET OPTIONS (require_partition_filter = true);
Teraz zapytanie bez WHERE transaction_date = ... zakończy się błędem zamiast skanować całą tabelę. To zabezpieczenie przed przypadkowym wydatkiem $12 na jedno zapytanie.
Clustering - druga warstwa optymalizacji
Clustering sortuje dane wewnątrz partycji po wybranych kolumnach (max 4). BigQuery pomija bloki danych, które nie pasują do warunków WHERE na kolumnach użytych w clustering.
Kiedy clustering daje największe oszczędności:
- Zapytania często filtrują po account_id, currency lub instrument_type
- Tabela ma miliony wierszy na partycję
- Kolumna clustering ma rozsądną kardynalność (100-100,000 unikalnych wartości)
Kiedy clustering nie pomoże:
- Zapytania zawsze skanują całą partycję (np. agregacja na dzień bez filtrów)
- Kolumna ma zbyt niską kardynalność (np. 3 wartości - lepsze jako PARTITION BY)
- Kolumna ma zbyt wysoką kardynalność (np. UUID - brak sensownego grupowania)
Materialized Views - cache dla powtarzalnych zapytań
Pipeline’y finansowe mają przewidywalny wzorzec: te same agregacje są przeliczane codziennie (dzienne pozycje, sumy per klient, sumy per waluta). Materialized Views zapisują wynik agregacji i automatycznie go odświeżają.
CREATE MATERIALIZED VIEW `project.dataset.daily_account_summary`
AS
SELECT
transaction_date,
account_id,
currency,
SUM(amount) AS total_amount,
COUNT(*) AS transaction_count
FROM `project.dataset.transactions`
GROUP BY transaction_date, account_id, currency;
Jak to oszczędza pieniądze:
- Zapytania, które pasują do wzorca materialized view, czytają z view (mniejszy skan)
- BigQuery automatycznie routuje zapytania do view, jeśli uzna, że to możliwe
- Koszt odświeżania view: inkrementalny (tylko nowe dane), nie pełny reskan
Typowa oszczędność: 50-80% na powtarzalnych zapytaniach analitycznych.
BI Engine - cache w pamięci dla dashboardów
Jeśli Twój zespół korzysta z Looker Studio (dawniej Data Studio) lub Tableau podłączonego do BigQuery, BI Engine utrzymuje dane w pamięci RAM:
- Koszt: od $2.50/GB/godzinę rezerwacji (minimum 1 GB)
- Efekt: każdy dashboard odpowiada w milisekundach zamiast sekund
- Oszczędność: zapytania z BI Engine nie zużywają slotów ani bajtów on-demand
Dla zespołu 10-20 analityków, którzy odświeżają każdy dashboard kilkadziesiąt razy dziennie, BI Engine z rezerwacją 5-10 GB ($12-25/godzinę roboczą) jest tańszy niż on-demand skanowanie tych samych danych setki razy.
Model Editions (sloty) vs on-demand - kiedy się przełączyć
Przeliczanie progu opłacalności
On-demand: $6.25/TB Standard Edition: $0.04/slot/godzinę (autoscaling, bez zobowiązania)
Jeden slot przetwarza dane z prędkością zależną od zapytania, ale w przybliżeniu:
- Proste agregacje: ~0.5-1 TB/slot/godzina
- Złożone JOINy: ~0.1-0.3 TB/slot/godzina
Orientacyjny próg:
- Jeśli przetwarzasz <5 TB/miesiąc: on-demand jest tańszy (~$31/miesiąc)
- Jeśli przetwarzasz 5-20 TB/miesiąc: są porównywalne - zależy od wzorca
- Jeśli przetwarzasz >20 TB/miesiąc: sloty prawie zawsze są tańsze
Dla finansowych pipeline (przewidywalne zapytania, dzienne batch joby): sloty są zazwyczaj korzystniejsze od 10 TB/miesiąc wzwyż, bo zapytania są regularne i planowalne.
Konfiguracja autoscaling
-- Rezerwacja z autoscaling (Standard Edition)
-- Baseline: 100 slotów, max: 400 slotów
CREATE RESERVATION `project.region-europe-central2.prod_reservation`
OPTIONS (
edition = 'STANDARD',
slot_capacity = 100,
autoscale_max_slots = 400
);
Płacisz za baseline 24/7, a dodatkowe sloty są doliczane per-sekundowo gdy pipeline potrzebuje więcej mocy.
Kontrola kosztów - budżety i alerty
Budżety w Cloud Billing
Ustaw budżety na poziomie projektu z alertami:
- 50% budżetu: powiadomienie email (wcześnie ostrzeżenie)
- 80% budżetu: alert Slack/PagerDuty (działanie wymagane)
- 100% budżetu: alert krytyczny (eskalacja do menadżera)
Custom cost controls na BigQuery
-- Maksymalny koszt zapytania per-użytkownik (1 GB = ~$0.006)
-- Ustaw na 10 GB aby jedno zapytanie nie przekroczyło $0.06
ALTER PROJECT `my-project`
SET OPTIONS (
default_query_job_timeout_ms = 300000, -- 5 minut max
maximum_bytes_billed = 10737418240 -- 10 GB max per zapytanie
);
Można też ustawić na poziomie zapytania:
SELECT account_id, SUM(amount)
FROM `project.dataset.transactions`
WHERE transaction_date = '2026-07-01'
GROUP BY account_id
OPTIONS (maximum_bytes_billed = 1073741824); -- 1 GB max
Jeśli zapytanie potrzebuje większej skali - zakończy się błędem zamiast generować nieoczekiwany koszt.
Typowe błędy w finansowych pipeline
1. SELECT * w scheduled queries
Scheduled query, który raz dziennie wykonuje SELECT * na tabeli historycznej, generuje stały, wysoki koszt. Rozwiązanie: zawsze wybieraj konkretne kolumny i dodawaj filtr partycji.
2. JOIN na niepartycjonowanych tabelach tymczasowych
Wzorzec, który widzimy regularnie:
-- ZŁY wzorzec: JOIN na pełnej tabeli temp
WITH temp AS (SELECT * FROM big_table)
SELECT * FROM temp JOIN other_table ON ...
BigQuery materializuje temp jako tabelę tymczasową BEZ partycjonowania. JOIN skanuje ją w całości. Rozwiązanie: filtruj WEWNĄTRZ CTE.
3. Brak expire time na tabelach staging
Pipeline’y ETL tworzą tabele tymczasowe (_staging, _temp) i nie usuwają ich. Nie generują kosztów zapytań, ale generują koszty storage ($0.02/GB/miesiąc dla aktywnego storage).
-- Ustaw automatyczne wygaśnięcie na tabelach staging
ALTER TABLE `project.dataset.transactions_staging`
SET OPTIONS (expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY));
4. Widoki (VIEW) zamiast materialized views
Zwykły VIEW nie cache’uje wyniku - każde odpytanie wykonuje pełne zapytanie bazowe. Jeśli dashboard odpytuje VIEW 100 razy dziennie, płacisz 100x za ten sam skan. Materialized View rozwiązuje ten problem.
5. Streaming inserts gdzie wystarczy batch load
Streaming inserts kosztują $0.05/GB (oprócz storage). Dla finansowego pipeline, który ładuje dane raz dziennie (EOD batch), streaming nie ma sensu - batch load jest darmowy.
Monitoring kosztów BigQuery
INFORMATION_SCHEMA - analiza zużycia
-- Top 10 najdroższych zapytań w ostatnim tygodniu
SELECT
user_email,
query,
total_bytes_processed / POW(1024, 4) AS tb_processed,
total_bytes_processed / POW(1024, 4) * 6.25 AS estimated_cost_usd,
creation_time
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
AND job_type = 'QUERY'
AND state = 'DONE'
ORDER BY total_bytes_processed DESC
LIMIT 10;
-- Koszty per-użytkownik w ostatnim miesiącu
SELECT
user_email,
COUNT(*) AS query_count,
SUM(total_bytes_processed) / POW(1024, 4) AS total_tb,
SUM(total_bytes_processed) / POW(1024, 4) * 6.25 AS total_cost_usd
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND job_type = 'QUERY'
GROUP BY user_email
ORDER BY total_cost_usd DESC;
Same te zapytania kosztują mało (skanują metadane, nie dane użytkowników).
Checklist optymalizacji BigQuery dla fintech
Natychmiastowe (dzień 1)
- Włącz
require_partition_filterna wszystkich dużych tabelach - Ustaw
maximum_bytes_billedna 10 GB jako default projektu - Przejrzyj scheduled queries - usunięcie
SELECT *i dodanie filtrów partycji - Skonfiguruj budżety i alerty w Cloud Billing
Krótkoterminowe (tydzień 1-2)
- Dodaj partycjonowanie po dacie do tabel, które go nie mają
- Dodaj clustering na kolumnach najczęściej używanych w WHERE
- Stwórz materialized views dla powtarzalnych agregacji w panelach analitycznych
- Ustaw expire time na tabelach staging/temp
Średnioterminowe (miesiąc 1-2)
- Oceń próg opłacalności slotów vs on-demand
- Wdróż BI Engine dla dashboardów Looker Studio
- Skonfiguruj monitoring INFORMATION_SCHEMA (cotygodniowy raport kosztów)
- Rozważ model showback per-zespół na podstawie zużycia BigQuery
Jak możemy pomóc
W Devopsity optymalizujemy infrastrukturę GCP dla polskich firm z sektora finansowego i fintech. Typowe zaangażowanie w optymalizację BigQuery:
- Audyt istniejących tabel i zapytań (identyfikacja top-10 kosztowych zapytań) - 1 dzień
- Implementacja partycjonowania, clustering i materialized views - 2-3 dni
- Konfiguracja budżetów, alertów i monitoringu - 1 dzień
- Analiza progu opłacalności slotów i migracja modelu cenowego - 1-2 dni
Jeśli Twoje rachunki za BigQuery rosną proporcjonalnie do liczby analityków zamiast do ilości danych - prawdopodobnie problem leży w strukturze tabel, nie w skali biznesu.
Rachunki za BigQuery wymykają się spod kontroli?
Umów się na bezpłatną 30-minutową rozmowę. Bez sprzedaży - techniczna dyskusja o Twoich procesach danych.
Najczęściej zadawane pytania
Ile kosztuje BigQuery on-demand?
$6.25 za terabajt przetworzonych danych. Pierwszy terabajt miesięcznie jest darmowy (Free Tier). Storage kosztuje $0.02/GB/miesiąc (aktywne dane) lub $0.01/GB/miesiąc (dane niemodyfikowane >90 dni).
Czy partycjonowanie jest darmowe?
Tak. Partycjonowanie nie wprowadza dodatkowych kosztów - po prostu zmienia fizyczny układ danych. Oszczędności pojawiają się natychmiast, bo zapytania skanują mniej danych.
Sloty czy on-demand dla pipeline z 50 scheduled queries dziennie?
Zależy od wolumenu danych. Jeśli Twoje 50 zapytań łącznie skanuje >500 GB/dzień (>15 TB/miesiąc), sloty będą tańsze. Policz: 15 TB x $6.25 = $94/miesiąc on-demand vs 50 slotów Standard Edition = $0.04 x 50 x 730h = $1,460/miesiąc. W tym przypadku on-demand jest tańszy. Przy 100 TB/miesiąc ($625 on-demand) sloty zaczynają wygrywać.
Czy mogę ograniczyć budżet BigQuery per-zespół?
Tak. Użyj osobnych projektów GCP per-zespół z indywidualnymi budżetami Cloud Billing. Alternatywnie: BigQuery Reservations pozwalają przypisać sloty do konkretnych projektów, zapobiegając sytuacji, gdzie jeden zespół “zjada” zasoby innego.
Jak szybko zobaczę oszczędności po wdrożeniu partycjonowania?
Natychmiast - od pierwszego zapytania na partycjonowanej tabeli. Jeśli migrujesz istniejącą tabelę, musisz ją odtworzyć z partycjonowaniem (CTAS - CREATE TABLE AS SELECT) lub użyć bq cp z opcją time partitioning.