Skip to content

refactor(bigquery): route via SET @@reservation instead of multi-project#185

Draft
MaxHalford wants to merge 1 commit intomainfrom
refactor/bq-reservations
Draft

refactor(bigquery): route via SET @@reservation instead of multi-project#185
MaxHalford wants to merge 1 commit intomainfrom
refactor/bq-reservations

Conversation

@MaxHalford
Copy link
Copy Markdown
Member

@MaxHalford MaxHalford commented Apr 15, 2026

Context

BigQuery now supports setting a reservation per query via the @@reservation system variable:

SET @@reservation = 'projects/P/locations/L/reservations/R';  -- run in R
SET @@reservation = 'none';                                   -- force on-demand

lea previously routed queries between compute projects with different reservation assignments, so BigQueryClient juggled a dict of bigquery.Client instances. This PR replaces that with a single client and picks reservations via SQL.

Changes

  • BigQueryClient: single self.client. Replaces script_specific_compute_project_ids with script_specific_reservations. Adds determine_reservation_for_script and a _open_session/_attach_session pair that emits SET @@reservation = '<value>' inside a BQ session and carries it to the main query via session_id. Skips the SET when a script has no override (the compute project's GCP-assigned reservation applies) and in dry-run.
  • BigBluePickAPI: one constructor arg (reservation). ON-DEMAND picks map to 'none', RESERVATION picks map to that string. BigQueryClient no longer inherits from it β€” composition only.
  • Precedence: LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS > Pick API > project's GCP assignment. Static overrides deliberately win so routed scripts aren't re-routed behind the user's back.
  • Env vars:
    • LEA_BQ_SCRIPT_SPECIFIC_COMPUTE_PROJECT_IDS β†’ LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS (value: "none" or projects/.../reservations/...; parsed with validation so typos surface at startup).
    • LEA_BQ_BIG_BLUE_PICK_API_{ON_DEMAND,REVERVATION}_PROJECT_ID β†’ LEA_BQ_BIG_BLUE_PICK_API_RESERVATION (single var; fixes the REVERVATION typo).
  • Drive-by: BigQueryJob.metadata was reading statistics.reservationUsage[0].name, which is not populated on real jobs β€” switched to statistics.reservation_id. The log line now correctly reports reservation billing (<id>) / on-demand billing.

Caveat β€” overriding to on-demand is gated

During validation, SET @@reservation = 'none' (or equivalently bq query --reservation_id=none) on int-data-kaya-prod was rejected:

Override to 'none' is not enabled. The option 'reservation_override_mode' is set to 'RESERVATION_OVERRIDE_MODE_UNSPECIFIED'. See https://cloud.google.com/bigquery/docs/default-configuration for configuration details.

The referenced doc does not list this option or its enum; ~25 brute-force guesses all failed. Routing to a reservation path works fine out of the box; a GCP support ticket is probably needed to unlock the on-demand override. This refactor is still strictly better regardless (single client, single compute project, typo fix, fixed billing-model log line).

Test plan

  • uv run pytest lea/ β€” 75/75 pass (4 new tests for reservation resolution precedence).
  • Live dry-run: lea run --scripts ../carbonfact/kaya/scripts --select core.accounts --dry-run --production with LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS={"kaya.core__accounts":"projects/int-data-kaya-prod/locations/EU/reservations/default"} β€” logs Using reservation '<path>' for materializing ... and SUCCESS ... (reservation billing (int-data-kaya-prod:EU.default)).
  • Live run without --dry-run once the repo owner is ready.
  • Override-to-on-demand path β€” blocked until reservation_override_mode is enabled at the project/org level.

Migration

Existing users must:

  1. Replace LEA_BQ_SCRIPT_SPECIFIC_COMPUTE_PROJECT_IDS={...} with LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS={...} whose values are "none" or projects/.../reservations/....
  2. Replace LEA_BQ_BIG_BLUE_PICK_API_ON_DEMAND_PROJECT_ID / LEA_BQ_BIG_BLUE_PICK_API_REVERVATION_PROJECT_ID with a single LEA_BQ_BIG_BLUE_PICK_API_RESERVATION=projects/.../reservations/....

πŸ€– Generated with Claude Code


Summary by cubic

Route BigQuery jobs via per-query reservations using SET @@reservation instead of switching compute projects. This simplifies config to a single client and improves billing visibility.

  • Refactors

    • Use one BigQueryClient and set reservations in a session (SET @@reservation), carried to the main query via session_id.
    • Replace script-specific compute projects with script_specific_reservations. Precedence: LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS > Big Blue Pick API > project's assignment.
    • Big Blue Pick API now maps picks to reservations: ON-DEMAND β†’ 'none', RESERVATION β†’ LEA_BQ_BIG_BLUE_PICK_API_RESERVATION. Composition only.
    • Fix billing logs by reading statistics.reservation_id; now reports reservation billing (<id>) or on-demand billing.
    • Note: forcing on-demand ('none') may be disabled by your project's reservation_override_mode.
  • Migration

    • Replace LEA_BQ_SCRIPT_SPECIFIC_COMPUTE_PROJECT_IDS with LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS values: "none" or projects/.../reservations/....
    • Replace LEA_BQ_BIG_BLUE_PICK_API_ON_DEMAND_PROJECT_ID and LEA_BQ_BIG_BLUE_PICK_API_REVERVATION_PROJECT_ID with LEA_BQ_BIG_BLUE_PICK_API_RESERVATION.

Written for commit 47a3a44. Summary will update on new commits.

BigQuery now supports picking a reservation per query via the
`@@reservation` system variable (see
https://docs.cloud.google.com/bigquery/docs/reservations-assignments#sql_3).
Previously we juggled several `bigquery.Client` instances to route
queries to different compute projects that had reservation assignments.
With this change there's a single client and a single compute project;
reservations are chosen via SQL.

- Replace `LEA_BQ_SCRIPT_SPECIFIC_COMPUTE_PROJECT_IDS` with
  `LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS` (value: `"none"` or a full
  `projects/.../reservations/...` path).
- Replace Big Blue Pick API's two on-demand/reservation project-id vars
  with a single `LEA_BQ_BIG_BLUE_PICK_API_RESERVATION`. On-demand is
  hardcoded as `"none"` (the only legal value for @@reservation).
- Static `LEA_BQ_SCRIPT_SPECIFIC_RESERVATIONS` takes priority over Pick
  API, so deliberately routed scripts aren't re-routed behind the
  user's back.
- Drop the `self.clients` dict and `default_client` property; drop
  `BigQueryClient`'s inheritance from `BigBluePickAPI` (composition).
- Fix `BigQueryJob.metadata`: it was reading the non-populated
  `statistics.reservationUsage[0].name` field; actual BQ jobs expose
  `statistics.reservation_id`. The log line now correctly reports
  `reservation billing (<id>)` / `on-demand billing`.
- Also fix the `REVERVATION` typo in the Big Blue env var.

Caveat: `SET @@reservation = 'none'` to force on-demand requires
the project's `reservation_override_mode` to be configured; the valid
enum value is not publicly documented. Routing to a specific
reservation path works out of the box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant