Skip to content

feat(core): Add Supabase Queues support#15921

Open
onurtemizkan wants to merge 28 commits intodevelopfrom
onur/supabase-queues
Open

feat(core): Add Supabase Queues support#15921
onurtemizkan wants to merge 28 commits intodevelopfrom
onur/supabase-queues

Conversation

@onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented Mar 28, 2025

Resolves: #14611

This PR adds Supabase Queues support to the Supabase

Sample Events: Link

Instrumented Operations

Producer operations (queue.publish):

  • send - Send a single message
  • send_batch - Send multiple messages

Consumer operations (queue.process):

  • pop - Read and delete message
  • read - Read message (with visibility timeout)
  • receive - Alias for read

Span Attributes

Follows OTEL Messaging Semantic Conventions and Sentry Queue Developer Docs:

  • messaging.destination.name - Queue name
  • messaging.message.id - Message ID (from msg_id)
  • messaging.message.retry.count - Retry count (from PGMQ's read_ct)
  • messaging.batch.message_count - Batch size for send_batch

Distributed Tracing

Producer spans inject sentry-trace and baggage into the message payload under _sentry key. Consumer spans create span links to the producer trace context.

Notes

  • Works with both client.rpc() and client.schema('pgmq_public').rpc() patterns
  • Queue operations are detected via RPC function names: send, send_batch, pop, read, receive
  • Empty consumer responses (no messages in queue) still create breadcrumbs and spans with messaging.batch.message_count: 0

@github-actions
Copy link
Contributor

github-actions bot commented Mar 28, 2025

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.43 kB - -
@sentry/browser - with treeshaking flags 23.9 kB - -
@sentry/browser (incl. Tracing) 42.27 kB - -
@sentry/browser (incl. Tracing, Profiling) 46.92 kB - -
@sentry/browser (incl. Tracing, Replay) 80.91 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.52 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 85.61 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 97.79 kB - -
@sentry/browser (incl. Feedback) 42.15 kB - -
@sentry/browser (incl. sendFeedback) 30.11 kB - -
@sentry/browser (incl. FeedbackAsync) 35.13 kB - -
@sentry/browser (incl. Metrics) 26.54 kB - -
@sentry/browser (incl. Logs) 26.69 kB - -
@sentry/browser (incl. Metrics & Logs) 27.36 kB - -
@sentry/react 27.14 kB - -
@sentry/react (incl. Tracing) 44.52 kB - -
@sentry/vue 29.87 kB - -
@sentry/vue (incl. Tracing) 44.09 kB - -
@sentry/svelte 25.44 kB - -
CDN Bundle 27.97 kB - -
CDN Bundle (incl. Tracing) 43.04 kB - -
CDN Bundle (incl. Logs, Metrics) 28.82 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 43.87 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 67.75 kB - -
CDN Bundle (incl. Tracing, Replay) 79.8 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 80.67 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 85.23 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.13 kB - -
CDN Bundle - uncompressed 81.83 kB - -
CDN Bundle (incl. Tracing) - uncompressed 127.54 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 84.66 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 130.37 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 208.04 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.14 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 246.96 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 256.94 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 259.75 kB - -
@sentry/nextjs (client) 46.87 kB - -
@sentry/sveltekit (client) 42.66 kB - -
@sentry/node-core 52.18 kB - -
@sentry/node 166.3 kB - -
@sentry/node - without tracing 93.97 kB - -
@sentry/aws-serverless 109.48 kB -0.01% -1 B 🔽

View base workflow run

@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch from bbadd60 to e7b3370 Compare March 31, 2025 09:44
@onurtemizkan onurtemizkan force-pushed the onur/supabase-integration branch from 1bc2897 to d387514 Compare March 31, 2025 10:26
@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch from e7b3370 to 56a2d84 Compare March 31, 2025 14:12
@onurtemizkan onurtemizkan linked an issue Mar 31, 2025 that may be closed by this pull request
@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch from 56a2d84 to 13d60e0 Compare March 31, 2025 14:25
@onurtemizkan onurtemizkan force-pushed the onur/supabase-integration branch 4 times, most recently from 423397d to 556703c Compare April 14, 2025 15:26
Base automatically changed from onur/supabase-integration to develop April 17, 2025 13:35
@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch from 13d60e0 to 74869e4 Compare April 22, 2025 14:16
@onurtemizkan onurtemizkan marked this pull request as ready for review April 24, 2025 09:32
@onurtemizkan onurtemizkan requested review from lforst and smeubank April 24, 2025 09:33
@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch from e63915a to 719c8b6 Compare April 24, 2025 14:11
},
},
async span => {
return (Reflect.apply(target, thisArg, argumentsList) as Promise<unknown>).then((res: unknown) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably also end the span when it throws/rejects? We can also set the status of the span then.

name: 'supabase.db.rpc',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.supabase',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can add the messaging.system attribute to be 'supabase' as described in https://develop.sentry.dev/sdk/telemetry/traces/modules/queues/

Comment on lines 238 to 239
const isProducerSpan = argumentsList[0] === 'enqueue';
const isConsumerSpan = argumentsList[0] === 'dequeue';
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if this recently changed, but here they show send and pop as rpc args: https://supabase.com/docs/guides/queues/quickstart#enqueueing-and-dequeueing-messages 🤔

@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch 4 times, most recently from fb8eeb1 to a8da234 Compare May 31, 2025 20:58
@onurtemizkan onurtemizkan force-pushed the onur/supabase-queues branch 2 times, most recently from ff2f804 to 5ce5ee9 Compare June 23, 2025 15:58
@onurtemizkan onurtemizkan requested review from AbhiPrasad and Lms24 June 23, 2025 23:39
@AbhiPrasad
Copy link
Contributor

@sentry review

@seer-by-sentry
Copy link

On it! We are reviewing the PR and will provide feedback shortly.

@seer-by-sentry
Copy link

PR Description

This pull request introduces instrumentation for Supabase queue operations using pgmq, enabling Sentry to capture spans and breadcrumbs for queue publishing and processing. This provides visibility into asynchronous task execution within Supabase applications.

Click to see more

Key Technical Changes

The key technical changes include: 1) Instrumenting the rpc method of the Supabase client to detect queue operations (send, send_batch, pop). 2) Creating spans for queue publish and process operations, capturing relevant metadata like queue name and message ID. 3) Adding breadcrumbs for queue operations to provide context in Sentry events. 4) Modifying queue messages to inject Sentry trace context for distributed tracing. 5) Adding e2e tests to verify the instrumentation in a Next.js application.

Architecture Decisions

The primary architectural decision involves using Proxy objects to intercept calls to the Supabase client's rpc method and the schema(...).rpc(...) method. This allows for non-intrusive instrumentation without modifying the Supabase client's core code. The trace context is injected directly into the message body to propagate tracing information across queue operations.

Dependencies and Interactions

This integration depends on the @supabase/supabase-js library and interacts with Sentry's tracing and breadcrumb APIs. It also relies on the pgmq extension being enabled in the Supabase database. The integration injects Sentry trace context into queue messages, which requires consumers to be instrumented to extract and continue the trace.

Risk Considerations

Potential risks include: 1) Performance overhead due to the instrumentation, although proxies are generally performant. 2) Incorrectly identifying queue operations, leading to spurious spans. 3) Failure to propagate trace context if consumers are not properly instrumented. 4) Security implications of modifying message bodies, although the injected data is limited to Sentry trace context. 5) The modification of the arguments list in place could lead to unexpected side effects.

Notable Implementation Details

Notable implementation details include: 1) The use of continueTrace to link consumer spans to producer spans. 2) The handling of both rpc and schema(...).rpc(...) calls. 3) The injection of Sentry trace context into the message body. 4) The vendoring of SQL code from the Supabase repository to enable queue access locally.

// even if the prototype's .then is already instrumented
if (_isInstrumented(originalThen) && !hasOwnThen) {
return;
}
Copy link

Choose a reason for hiding this comment

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

Incorrect guard allows double-wrapping of instrumented instances

Low Severity

The guard condition in _instrumentPostgRESTFilterBuilderInstance has flawed logic. The condition _isInstrumented(originalThen) && !hasOwnThen only returns early when the prototype's .then is instrumented AND there's no own property. However, if an instance has its own .then that is already instrumented (e.g., from a previous call), the condition evaluates to true && false = false, allowing the function to proceed and double-wrap the already-instrumented Proxy. The condition should simply be _isInstrumented(originalThen) to prevent wrapping any already-instrumented .then, regardless of whether it's an own property or inherited.

Fix in Cursor Fix in Web

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Codecov Results 📊


Generated by Codecov Action

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

if (rpcFunctionName && QUEUE_RPC_OPERATIONS.has(_normalizeRpcFunctionName(rpcFunctionName))) {
// Queue RPC calls are instrumented in the dedicated queue instrumentation.
return Reflect.apply(target, thisArg, argumentsList);
}
Copy link

Choose a reason for hiding this comment

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

Non-queue RPC calls get double instrumentation

Low Severity

Non-queue RPC calls receive duplicate span instrumentation. The new _instrumentGenericRpc creates a span with description rpc(functionName), but when the result's .then() is called, the PostgREST instrumentation also runs because it only skips queue operations (QUEUE_RPC_OPERATIONS). This creates a second span describing the same call as insert(...) from(functionName). The skip condition at line 73 checks only for queue RPC names but allows all other RPC calls to be instrumented again by the PostgREST path.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

Support for Supabase Queues

4 participants