Skip to content

feat(firestore-bigquery-export): reinstate the Cloud Tasks write buffer - #3103

Closed
cabljac wants to merge 8 commits into
kitsfrom
feat/kits-fbe-cloud-tasks-buffer
Closed

feat(firestore-bigquery-export): reinstate the Cloud Tasks write buffer#3103
cabljac wants to merge 8 commits into
kitsfrom
feat/kits-fbe-cloud-tasks-buffer

Conversation

@cabljac

@cabljac cabljac commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Ports the extension's Cloud Tasks write buffer into the kit, per the option D decision on #3031: migrating users keep the failure behaviour and cost profile they have today.

A failed inline write enqueues onto a new syncBigQuery task queue (5 attempts, 60s minimum backoff, throttled by MAX_DISPATCHES_PER_SECOND, default 100) after up to MAX_ENQUEUE_ATTEMPTS in-process attempts (default 3). The task handler runs ensureInitialized as a self-heal, attempts the write even if that fails, and rethrows so Cloud Tasks retries; tracker 2.1.0 writes the rows to BACKUP_COLLECTION on every terminal insert failure. The trigger declares no retry policy and a failed enqueue is logged, published as onError, and dropped, both exactly as the extension does. Enqueue targets the bare function name (firebase-admin 14.2.0+ resolves the FIREBASE_KIT_INSTANCE_ID prefix, Firebase CLI 15.28.0+), in the CLI-set FUNCTION_REGION, with a task id derived from the event id so a retried enqueue cannot buffer the same event twice. One deliberate change: the success event is published after the insert and swallowed on failure, closing a duplicate-row route the extension has.

91 unit tests pass. No live queue E2E has been run: that the deployed env carries FIREBASE_KIT_INSTANCE_ID and FUNCTION_REGION, that the gen2 task function accepts the SDK's ID token, queue creation with the resolved throttle, and the task size limit on large documents are all unverified. The tracker pins firebase-admin ^13, so a second admin copy is nested under it; a follow-up should widen that range.

Fixes #3031

@cabljac

cabljac commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Live E2E checklist (dev-extensions-testing), to run before marking ready for review:

  • Deploy with firebase-tools >= 15.28.0; confirm kit-<id>-syncBigQuery queue is provisioned and FIREBASE_KIT_INSTANCE_ID is set on the functions
  • Induce an insert failure (retype a changelog column, or revoke roles/bigquery.dataEditor); confirm the trigger execution succeeds and a task lands on the queue (bare-name enqueue resolves the kit prefix on admin 14)
  • Observe 5 task attempts at >= 60s spacing, dispatch rate capped at MAX_DISPATCHES_PER_SECOND
  • With BACKUP_COLLECTION set: confirm the backup doc exists (keyed by event id) at exhaustion; restore BigQuery, reconcile via the event_id anti-join MERGE, verify the row lands
  • With BACKUP_COLLECTION unset: confirm the row is dropped after attempt 5 (extension parity) and the error log carries retry_count
  • Revoke roles/cloudtasks.enqueuer: confirm the enqueue failure is logged at error level and the Firestore event is redelivered by Eventarc (not lost)
  • Confirm onSuccess is published from the task handler when a buffered write lands
  • Deploy once with an older CLI (< 15.28.0) to confirm the documented loud-failure degradation

@cabljac cabljac mentioned this pull request Sep 2, 2026
62 tasks

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request reinstates the Cloud Tasks write buffer for failed inline BigQuery writes via a new syncBigQuery task queue, and restores explicit function placement derived from DATABASE_REGION. Feedback on the changes highlights a critical parameter mismatch bug in logFailedEventAction where a custom message shifts the subsequent arguments, leading to corrupted error logs. Additionally, it is recommended to calculate the random jitter inside the retry backoff loop in tasks.ts rather than keeping it constant across all attempts.

Comment thread kits/firestore-bigquery-export/src/logs.ts
Comment thread kits/firestore-bigquery-export/src/tasks.ts
Comment thread kits/firestore-bigquery-export/src/tasks.ts
@cabljac
cabljac force-pushed the feat/kits-fbe-cloud-tasks-buffer branch from 367bdc7 to 451332e Compare September 2, 2026 19:38

@cabljac cabljac left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review before this goes out for review. Seven items: five are code changes, two are doc corrections. The tracker one is unverified and needs settling before merge.

This started as an AI-assisted review; I've verified the findings below against the source myself, except the two I've explicitly marked unverified.

Comment thread kits/firestore-bigquery-export/src/handlers.ts Outdated
Comment thread kits/firestore-bigquery-export/src/handlers.ts
Comment thread kits/firestore-bigquery-export/src/tasks.ts Outdated
Comment thread kits/firestore-bigquery-export/src/tasks.ts Outdated
Comment thread kits/firestore-bigquery-export/src/config.ts Outdated
Comment thread kits/firestore-bigquery-export/README.md Outdated
Comment thread kits/firestore-bigquery-export/README.md Outdated
Port the extension's syncBigQuery queue into the kit (option D, #3031):
a failed inline write enqueues the serialized change onto a task queue
(5 attempts, 60s min backoff, MAX_DISPATCHES_PER_SECOND throttle) instead
of replaying through Eventarc for 24h. The task handler self-heals
(ensureInitialized) before re-attempting and rethrows so Cloud Tasks
retries; tracker 2.1.0 backs the row up to BACKUP_COLLECTION on every
terminal insert failure. The trigger keeps retry: true so a failed
enqueue - logged at error level and rethrown, unlike the extension's
silent swallow - is redelivered rather than dropped.

Enqueue targets the bare function name on firebase-admin ^14.2.0, which
resolves the kit-<instance>- prefix from FIREBASE_KIT_INSTANCE_ID
(Firebase CLI 15.28.0+); queue region derives from DATABASE_REGION with
FUNCTION_REGION as fallback. MAX_DISPATCHES_PER_SECOND (default 100) and
MAX_ENQUEUE_ATTEMPTS (default 3) keep their extension names so migrated
.env values carry over.
…pin buffer safety properties

A library consumer passing maxEnqueueAttempts <= 0 made enqueueSyncTask
resolve without enqueueing, so the trigger logged success for an event
buffered nowhere; the budget is now clamped to at least one attempt.
New tests pin the backup wiring (toTrackerConfig.backupTableId carries
backupCollectionId - the property that keeps queue exhaustion durable)
and that the syncBigQuery task never re-enqueues (the trigger-queue loop
seed).
…ry failure path

- Attempt the buffered write even when provisioning fails. The tracker only
  parks a row in BACKUP_COLLECTION from its insert failure path, so throwing
  before the write dropped the row instead of backing it up.
- Publish the success event outside the insert try. Rethrowing after the row
  had landed made Cloud Tasks retry past the insertId dedupe window and
  duplicate it.
- Enqueue with a task id derived from the event id and treat
  task-already-exists as success, so an Eventarc redelivery after a failed
  enqueue cannot buffer the same event twice.
- Drop the FUNCTION_REGION fallback. It is a gen1 variable and is not set on
  gen2 runtimes; resolve the region from DATABASE_REGION only.
- Route MAX_ENQUEUE_ATTEMPTS and MAX_DISPATCHES_PER_SECOND through optionalInt
  so an unset param reaches resolveExportConfig as undefined and gets the
  documented default rather than 0.
@cabljac
cabljac force-pushed the feat/kits-fbe-cloud-tasks-buffer branch from 8f0b2e1 to 0f30873 Compare September 7, 2026 08:38
…s code

firebase-admin reports the duplicate-task error as
functions/task-already-exists, so the bare comparison never matched and a
redelivered enqueue rethrew instead of resolving. The test mock now carries
the prefixed code as the SDK does.
…N_REGION first

The Firebase CLI sets FUNCTION_REGION on every deployed gen2 function
(cloudfunctionsv2.js sets it from endpoint.region), so it is the region the
queue actually lives in. Dropping it in 0f30873 was based on a wrong claim
that the variable is never set. With DATABASE_REGION empty, which the README
documents as supported, every failed inline write threw before the first
enqueue attempt and went back to Eventarc for redelivery; on a first
interactive deploy the queue path named the DATABASE_REGION target while the
functions were in us-central1.

Also clamp a NaN or non-integer attempt budget to one attempt: Math.max(1,
NaN) is NaN and skipped the enqueue loop entirely.
… on the trigger

The extension declares no retry policy on fsexportbigquery and, when the
enqueue itself fails, logs at error level, publishes onError, and drops the
event. The kit kept retry: true from before the buffer landed and rethrew on
enqueue exhaustion, which redelivered the event through Eventarc for up to
24 hours; it also redelivered on every failure before the write was
attempted (serialization, onStart publish), which the PR text did not say.
Drop retry: true and stop rethrowing on enqueue exhaustion so the failure
behaviour and cost profile match the extension.
…ilures

Transform-function failures throw before the insert and are never backed up,
on the kit and the extension alike.
…ler's hot path

Drop provisioning from handleSyncBigQueryTask. The extension's queue handler
goes straight to the write with skipInit on the tracker; the kit ran
tracker.initialize() before every buffered write on a cold instance, which
under a recovery burst fanned six to eight BigQuery metadata calls, and with
TABLE_PARTITIONING set on an unpartitioned table a table.setMetadata, across
up to 500 concurrent instances. Provisioning stays in the lifecycle tasks.

Give syncBigQuery maxInstances equal to its maxConcurrentDispatches. The
extension's handler is gen1 with no instance cap; gen2 defaults to 100, so
dispatches beyond that would 429 at the cap and consume queue attempts.
@cabljac
cabljac marked this pull request as ready for review September 7, 2026 14:15
@cabljac
cabljac requested a review from a team as a code owner September 7, 2026 14:15
@cabljac

cabljac commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favour of the same change split into a reviewable stack (stack #3132), bottom to top:

The top of the stack is identical to this branch on kits/firestore-bigquery-export, rebased onto current kits.

@cabljac cabljac closed this Sep 7, 2026
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