GCP BigQuery FinOps slot reservations

BigQuery Editions - zapytania zablokowane w kolejce po przejściu na sloty

Naprawa zapytań BigQuery zablokowanych w kolejce: weryfikacja capacity commitment, assignment, concurrency i autoscaling slotów.

·
Po przejściu z on-demand na BigQuery Editions (slot reservations) zapytania, które wcześniej wykonywały się w sekundach, teraz wiszą minutami lub godzinami w stanie PENDING/QUEUED. Dashboardy BI przestają odpowiadać, pipeline'y danych opóźniają się, a użytkownicy raportują timeout. Problem pojawia się tuż po włączeniu slot-based pricing - wygląda na regresję wydajności, ale przyczyna jest konfiguracyjna.

Ten runbook opisuje diagnozowanie zapytań BigQuery zablokowanych w kolejce po migracji na slot reservations. Szczegółowy przewodnik po optymalizacji kosztów BigQuery znajdziesz w artykule Optymalizacja kosztów BigQuery - pipeline danych finansowych. W kwestii planowania capacity i konfiguracji reservations umów konsultacje.

Objaw

Zapytania pozostają w stanie PENDING zamiast przechodzić do RUNNING. Użytkownicy widzą błędy timeout lub zapytania nigdy się nie rozpoczynają:

-- Sprawdz zapytania w stanie PENDING w ostatniej godzinie
SELECT
  job_id,
  user_email,
  state,
  creation_time,
  start_time,
  TIMESTAMP_DIFF(COALESCE(start_time, CURRENT_TIMESTAMP()), creation_time, SECOND) AS queue_seconds,
  reservation_id,
  total_slot_ms
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
  AND state IN ('PENDING', 'RUNNING')
ORDER BY creation_time ASC;

-- Zapytania ktore czekaly dluzej niz 60 sekund na start
SELECT
  job_id,
  user_email,
  creation_time,
  start_time,
  TIMESTAMP_DIFF(start_time, creation_time, SECOND) AS wait_seconds,
  reservation_id,
  query
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 24 HOUR)
  AND TIMESTAMP_DIFF(start_time, creation_time, SECOND) > 60
ORDER BY wait_seconds DESC
LIMIT 20;
# Sprawdz status konkretnego joba via bq CLI
bq show --format=prettyjson -j <project_id>:<job_id>

# Lista aktywnych jobow w projekcie
bq ls -j --all --max_results=20 --format=prettyjson <project_id>

# Typowe komunikaty bledow:
# "Resources exceeded during query execution: The query could not be executed in the allotted slot capacity"
# "Query exceeded resource limits. Slot capacity: 0 slots available for this reservation."
# Job state: PENDING (bez przejscia do RUNNING przez minuty/godziny)

Zapytania pozostają w PENDING z reservation_id wskazującym na rezerwację z niewystarczającą pojemnością. W skrajnych przypadkach joby są odrzucane z błędem Resources exceeded.

Przyczyna

Po przejściu z on-demand (autoskalowane sloty Google) na slot-based pricing (BigQuery Editions) - projekt traci dostęp do współdzielonej puli slotów Google i jest ograniczony wyłącznie do zakupionej pojemności. Zapytania blokują się, gdy:

  • Commitment zbyt mały na workload: Zakupiono np. 100 slotów, ale workload w godzinach szczytu wymaga 300-500. W modelu on-demand Google dynamicznie przydzielał tysiące slotów - po przejściu na editions projekt ma sztywny limit.

  • Assignment na złym poziomie hierarchii: Rezerwacja jest przypisana na poziomie organizacji lub folderu, ale projekt używa innej ścieżki w hierarchii GCP. Projekt nie dziedziczy przypisania i efektywnie nie ma żadnych slotów.

  • Brak autoscaling: Commitment ma ustawiony wyłącznie baseline capacity bez autoscaling. W godzinach szczytu nie ma możliwości chwilowego zwiększenia pojemności - zapytania czekają w kolejce.

  • Zły podział rezerwacji interactive vs batch: Wszystkie sloty przypisane do jednej rezerwacji obsługującej zarówno zapytania interaktywne (dashboardy, ad-hoc) jak i batch (ETL, scheduled queries). Długie joby batch blokują krótkie zapytania interaktywne.

  • Limit jednoczesnych zapytań (concurrency): BigQuery w trybie slot-based ma limit jednocześnie wykonywanych zapytań na rezerwację (domyślnie 100 concurrent queries w ramach reservation). Przy dużej liczbie małych zapytań z BI tooli - sam concurrency limit powoduje kolejkowanie, nawet jeśli sloty są wolne.

Rozwiązanie

A) Sprawdź aktualne zużycie slotów vs pojemność:

-- Zuzycie slotow w ostatniej godzinie (per minuta)
SELECT
  TIMESTAMP_TRUNC(period_start, MINUTE) AS minute,
  reservation_id,
  SUM(period_slot_ms) / 60000 AS avg_slots_used
FROM `region-eu`.INFORMATION_SCHEMA.JOBS_TIMELINE
WHERE period_start > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY minute, reservation_id
ORDER BY minute DESC;

-- Porownanie zuzycia vs pojemnosc reservacji
SELECT
  r.reservation_name,
  r.slot_capacity,
  r.edition,
  COUNT(j.job_id) AS active_jobs,
  SUM(j.total_slot_ms) / 1000 AS total_slot_seconds
FROM `region-eu`.INFORMATION_SCHEMA.RESERVATIONS r
LEFT JOIN `region-eu`.INFORMATION_SCHEMA.JOBS j
  ON j.reservation_id = r.reservation_name
  AND j.creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)
GROUP BY r.reservation_name, r.slot_capacity, r.edition;
# Sprawdz capacity commitments via gcloud
gcloud beta bq capacity-commitments list \
  --location=eu \
  --project=<project_id> \
  --format="table(name,slotCount,plan,state,edition)"

# Sprawdz reservations
gcloud beta bq reservations list \
  --location=eu \
  --project=<admin_project_id> \
  --format="table(name,slotCapacity,edition,autoscale.maxSlots)"

# Sprawdz assignments
gcloud beta bq reservations assignments list \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name> \
  --format="table(name,assignee,jobType,state)"

B) Zwiększ commitment lub włącz autoscaling:

# Opcja 1: Wlacz autoscaling na istniejacym reservation (zalecane)
gcloud beta bq reservations update <reservation_name> \
  --location=eu \
  --project=<admin_project_id> \
  --autoscale-max-slots=500

# Opcja 2: Zwieksz baseline capacity (zmiana commitment)
# Najpierw sprawdz aktualny commitment
gcloud beta bq capacity-commitments list \
  --location=eu \
  --project=<admin_project_id>

# Utworz dodatkowy commitment (Editions - FLEX plan, mozna anulowac po 1 min)
gcloud beta bq capacity-commitments create \
  --location=eu \
  --project=<admin_project_id> \
  --slots=200 \
  --plan=FLEX \
  --edition=ENTERPRISE

# Przypisz dodatkowe sloty do reservacji
gcloud beta bq reservations update <reservation_name> \
  --location=eu \
  --project=<admin_project_id> \
  --slot-capacity=300

C) Napraw hierarchię assignment:

# Sprawdz do czego jest przypisana reservacja
gcloud beta bq reservations assignments list \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name>

# Jesli assignment jest na organizacji ale projekt nie dziedziczy:
# Usun stary assignment
gcloud beta bq reservations assignments delete \
  <assignment_id> \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name>

# Utworz assignment bezposrednio na projekcie
gcloud beta bq reservations assignments create \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name> \
  --assignee=projects/<target_project_id> \
  --job-type=QUERY

# Lub na folderze zawierajacym projekt
gcloud beta bq reservations assignments create \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name> \
  --assignee=folders/<folder_id> \
  --job-type=QUERY

D) Rozdziel rezerwacje na interactive i batch:

# Utworz oddzielna reservacje dla zapytan batch/ETL
gcloud beta bq reservations create etl-batch \
  --location=eu \
  --project=<admin_project_id> \
  --slot-capacity=100 \
  --edition=ENTERPRISE

# Przypisz projekty ETL do reservacji batch
gcloud beta bq reservations assignments create \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=etl-batch \
  --assignee=projects/<etl_project_id> \
  --job-type=QUERY

# Glowna reservacja (interactive) - zostaw dla dashboardow i ad-hoc
# Opcjonalnie: uzyj job-type=PIPELINE dla scheduled queries
gcloud beta bq reservations assignments create \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=etl-batch \
  --assignee=projects/<etl_project_id> \
  --job-type=PIPELINE

# Ustaw concurrency target na reservacji interaktywnej (opcjonalne)
gcloud beta bq reservations update interactive-prod \
  --location=eu \
  --project=<admin_project_id> \
  --concurrency=0
# concurrency=0 oznacza automatyczne zarzadzanie przez BigQuery

E) Tymczasowy powrót na on-demand (awaryjnie):

# UWAGA: Usun assignment aby projekt wrocil na on-demand billing
# To przywroci pelna wydajnosc kosztem wyzszych rachunkow

# Znajdz assignment ID
gcloud beta bq reservations assignments list \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name> \
  --format="table(name,assignee)"

# Usun assignment (projekt natychmiast wraca na on-demand)
gcloud beta bq reservations assignments delete \
  <assignment_id> \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name>

# Weryfikacja - projekt nie powinien miec zadnej reservacji
bq show --reservation --location=eu --project_id=<target_project_id>
# Expected: "No reservation found" = on-demand mode

Walidacja

-- 1. Brak zapytan w stanie PENDING dluzej niz 10 sekund
SELECT
  COUNT(*) AS pending_jobs
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 10 MINUTE)
  AND state = 'PENDING'
  AND TIMESTAMP_DIFF(CURRENT_TIMESTAMP(), creation_time, SECOND) > 10;
-- Expected: 0

-- 2. Sredni czas oczekiwania w kolejce ponizej 5 sekund
SELECT
  AVG(TIMESTAMP_DIFF(start_time, creation_time, SECOND)) AS avg_queue_seconds,
  MAX(TIMESTAMP_DIFF(start_time, creation_time, SECOND)) AS max_queue_seconds,
  COUNT(*) AS total_jobs
FROM `region-eu`.INFORMATION_SCHEMA.JOBS
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 MINUTE)
  AND state = 'DONE';
-- Expected: avg_queue_seconds < 5

-- 3. Zuzycie slotow nie przekracza pojemnosci reservacji
SELECT
  TIMESTAMP_TRUNC(period_start, MINUTE) AS minute,
  SUM(period_slot_ms) / 60000 AS slots_used
FROM `region-eu`.INFORMATION_SCHEMA.JOBS_TIMELINE
WHERE period_start > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 MINUTE)
GROUP BY minute
ORDER BY slots_used DESC
LIMIT 5;
-- Expected: slots_used <= slot_capacity reservacji (lub autoscale max)
# 4. Sprawdz slot utilization w Cloud Monitoring
gcloud monitoring metrics list \
  --filter='metric.type="bigquery.googleapis.com/slots/total_available" OR metric.type="bigquery.googleapis.com/slots/allocated_for_reservation"'

# 5. Potwierdz ze reservacja i assignment sa poprawne
gcloud beta bq reservations describe <reservation_name> \
  --location=eu \
  --project=<admin_project_id> \
  --format="json(slotCapacity,autoscale,edition)"

gcloud beta bq reservations assignments list \
  --location=eu \
  --project=<admin_project_id> \
  --reservation=<reservation_name> \
  --format="table(assignee,jobType,state)"
# Expected: state = "ACTIVE" dla wszystkich assignmentow

Jeśli brak zapytań PENDING, średni czas oczekiwania < 5s i slot utilization jest stabilne poniżej limitu - problem jest rozwiązany. Monitoruj przez 24-48h obejmując godziny szczytu (ETL + dashboardy BI), aby upewnić się że pojemność jest wystarczająca.

Zapytania zablokowane w kolejce po włączeniu slot reservations mają natychmiastowy wpływ biznesowy. Dashboardy BI (Looker, Data Studio, Tableau) przestają odpowiadać - użytkownicy widzą puste raporty lub timeout. Pipeline'y ETL opóźniają się, co powoduje przestarzałe dane w hurtowni. Scheduled queries nie wykonują się w oknach czasowych, generując kaskadowe opóźnienia w downstream dependencies. W skrajnych przypadkach SLA wobec klientów na dostarczanie raportów zostaje naruszone. Problem nie jest widoczny na pierwszy rzut oka - brak błędów w logach, tylko rosnący czas oczekiwania.

 

Jerzy Kopaczewski

Zapytania BigQuery utknęły w kolejce po włączeniu slotów?

Umów bezpłatną 30-minutową rozmowę. Przejrzymy konfigurację capacity commitments, reservations i autoscaling, aby przywrócić wydajność zapytań.