Beacon Pulse runs on Cloudflare Workers (pulse-ingest + pulse-public), Cloudflare Queues, R2, and D1. This document describes the platform’s performance characteristics, known bottlenecks, and the strategies applied to address them.


System Overview

End-to-End Flow

POST /upload/multipart/complete
  → R2 PUT (raw export)
  → D1 INSERT into exports (status='queued')
  → Queue.send() to beacon-pulse-uploads
       ↓
Queue consumer
  → parseWhatsAppZipExportFromR2()         R2 range reads
  → deduplicateMessages()                  D1 batch INSERTs into message_hashes
  → saveMediaArtifacts()                   R2 PUT per artifact + D1 per artifact
  → enqueueMediaAnalysisJobs()             Queue.send() per artifact
  → runDailyDigestWorkItems()              AI ×2 per day + D1 INSERT
  → regenerateWeeklySummaryForWeek()       AI ×2 per week + D1 ×6–8
       ↓
Public API: GET /pulse/history.json
  → D1 SELECT weekly_summaries_public
  → HTTP response (Cloudflare Cache API, 5-min TTL)

Storage Layer

LayerUsed ForNotes
R2Raw exports, media artifactsRange-read ZIP parsing; 72h/168h cleanup
D1All state: exports, hashes, digests, quotasPrimary bottleneck; targeted by caching and batching improvements
QueuesUpload jobs, media jobs, regen jobsSingle queue; 6 message types
KVQuota bypass flagOptional; 60s TTL; falls back to D1
Cloudflare Cache APIPublic API responses5-min TTL on /pulse.json, /pulse/history.json, /pulse/concepts.json

Bottlenecks Identified

N+1 Query Patterns

PatternLocationQueries/Operation
Media content-hash dedupsaveMediaArtifacts()1 SELECT per artifact → replaced with single batched IN query
/pulse/history.json media countspublic/index.ts1 correlated subquery per digest row → replaced with aggregate pre-fetch
Daily digest media summary contextPipeline1 JOIN per day

Sequential AI Calls

PatternImpact
Image Stage 1: classifier then objectDetector2× slower than necessary
Video frames: extract → classify → detect → caption, frame-by-frame~9 serial ops per video
regenerateWeeklySummariesForCommunity(): sources sequential100+ sequential operations for large communities

Missing Caching (Before Improvements)

WhatWhereCost
Quota config / bypass flagFresh D1 SELECT per AI call~50–100ms per AI call
Public API responsesNo Cache APIFull D1 read on every request
Quota status in cronRe-read per tick3–5 redundant queries per tick

Missing Indexes (Before Improvements)

TableMissing IndexNeeded For
export_media(export_id, community_id) compositeJOIN in /pulse/history.json
exports(status, community_id)Cron stuck-export queries

Improvements Applied

Parallelization

Image Stage 1 — classifier and objectDetector run in parallel. Both are mandatory Stage 1 calls with no dependency on each other. Running them with Promise.all halves per-image AI turnaround time.

Video frame analysis — two-phase parallel execution. Phase 1: all frame extraction and Stage 1 classification run in parallel (independent per frame). Phase 2: Stage 2 caption decisions run sequentially to preserve the maxVideoFrameStage2PerExport cap without a concurrent counter race.

Weekly regeneration — sources run in parallel (SOURCE_CONCURRENCY=3). Sources within a community have no cross-source data dependencies. Up to 3 sources are regenerated concurrently. Weeks within each source remain sequential because each week may compare against the previous week’s summary.

Cron sub-tasks — all tasks run in parallel per schedule. */5 * * * * (high-priority): 5 tasks via Promise.allfinalizeCompletedWeeklyExports, retryRecoverableFailedExports, retryRecoverableFailedMedia, resetStuckDeferredProcessingMedia, requeueStalePendingMediaJobs. */15 * * * * (low-priority): 2 tasks via Promise.allenqueueDeferredMediaBatches, cleanupRetainedRawExports. Each task reads/writes separate row sets; no cross-task D1 conflicts.

Deferred media batch — parallel candidate loading and R2 PUTs. getDeferredMediaReadiness and processDeferredMediaBatch load image/audio/video candidates in parallel. Within each batch, R2 PUTs and D1 status updates are collected and fired in parallel via Promise.all + db.batch() rather than in sequential loops.

Batching

Media content-hash dedup — single IN query per export. Instead of one SELECT per artifact, all content hashes are collected before the loop and fetched in a single SELECT ... WHERE content_hash IN (?) (chunked at MEDIA_DEDUP_CHUNK_SIZE=24 to stay within D1 variable limits). For a 50-artifact export this reduces 50 queries to 3.

Deferred media status updates — db.batch() instead of sequential UPDATEs. Status transitions (mark as deferred_processing, reset on failure, mark as pending_analysis) are batched into single db.batch() calls rather than one UPDATE per row.

Deferred batch limit increased. enqueueDeferredMediaBatches was capped at 3 exports per cron tick. Increased to 12 to drain backlogs faster during high-ingestion periods.

Caching

Quota bypass flag — in-memory cache with 30s TTL. The module-level _mediaConfigCache variable caches quota settings within a Worker isolate. setMediaQuotaBypass() calls invalidateMediaConfigCache() immediately on write so the new state is reflected on the next read within the same isolate without waiting for TTL expiry.

Quota bypass flag — optional KV cross-invocation cache. When beacon_pulse_cache KV namespace is provisioned, bypass flag is also written to KV with 60s TTL. This reduces D1 reads across separate Worker invocations. Falls back gracefully to D1 when KV is absent.

Public API responses — Cloudflare Cache API. /pulse.json, /pulse/history.json, /pulse/concepts.json, and /pulse/daily.json are cached via caches.default with a 5-minute TTL. The cache key is the full request URL. Cache is written on first miss; subsequent requests within the TTL window skip D1 entirely.

Pagination

/pulse/history.json — cursor-based pagination. The legacy LIMIT ? OFFSET ? pattern degrades as offset grows (D1 scans all prior rows). Cursor mode uses WHERE week_start < ? ORDER BY week_start DESC LIMIT ? — O(1) per page regardless of history depth. next_cursor is returned in the response. Legacy offset mode is retained for backward compatibility. See api-contracts for full pagination spec.

Async Pillar Assignment

Concept graph pillar assignment is deferred. Weekly summary generation previously ran AI pillar assignment synchronously as part of the pipeline, adding one AI call per week. Now, the concept graph is saved immediately using keyword-only pillar assignment (assignPillarsKeywordOnly), then a backfill_concept_pillars queue message is enqueued for async AI-quality refinement. The public API handles null pillar fields gracefully during the brief window before backfill runs.

Dual Cron Schedules

Cron split into two schedules. All 7 self-healing tasks previously ran in a single */5 * * * * tick. Slow cleanup operations could delay stuck-export detection. Now:

  • */5 * * * * — high-priority (stuck detection + 5 retry tasks)
  • */15 * * * * — low-priority (deferred media drain + raw export cleanup)

See queue-architecture for the full task schedule.

Indexes Added

MigrationIndexBenefit
fix_daily_digests_weekly_summaries_pk.sqlFull PK on daily_digests (community_id, source_id, day_date)Prevents cross-source digest collision
add_export_media_community_index.sql(export_id, community_id) on export_mediaFaster JOIN in /pulse/history.json
add_exports_status_index.sql(status, community_id) on exportsFaster cron stuck-export detection

Parallelization Safety

OperationSafe to Parallelize?Notes
Image Stage 1 classifier + detectorYesIndependent; same input bytes
Video frame extractionYesDifferent timestamps; no shared state
Video frame Stage 1 per frameYesIndependent; quota cap still enforced
Weekly regen across sourcesYesDifferent source_id rows; no cross-source dependency
Cron sub-tasksYesEach reads/writes separate row sets
Deferred media readiness checksYesRead-only; independent per export
Weekly regen within one sourceNoEach week may compare against previous week
Daily digest generation within exportConfigurableParallelizable with quota stagger
Video frame Stage 2 caption decisionsNoSequential to protect stage2Used counter

Caching Reference

WhatLayerTTLInvalidation
Quota bypass flag (in-process)Module-level variable30sOn every setMediaQuotaBypass() call
Quota bypass flag (cross-invocation)KV (optional)60sOn every /quota/bypass POST
Public API responsesCloudflare Cache API5 minBy URL key; stale on write within TTL
Quota config (model rates, limits)In-memory per invocationInvocation lifetimeRe-read next invocation
Community/source listsCloudflare Cache API30 minOn community/source mutation

Known Limits and Risks

AreaRiskMitigation
Image Stage 1 parallelDoubled concurrent AI calls per itemQuota is per-day, not per-second; acceptable
Video frame parallelHigher burst AI usage during video processingmaxVideoFrameStage2PerExport cap still applies
Parallel weekly regenD1 write contention on ON CONFLICT upsertsSafe — upsert uses full PK including source_id; no cross-source conflicts
Cache API stale readsUp to 5 minutes after digest writeAcceptable for community health product; TTL is documented
KV bypass staleUp to 60s stale bypass stateAdmin-only feature; acceptable
Cache API in test runtimecaches.default behaves differently in workerd test modeSpy on cache calls; validate in staging
D1 variable limitLarge IN queries (media dedup, batch inserts)Chunked at MEDIA_DEDUP_CHUNK_SIZE=24 and DAY_BATCH_SIZE=50