Skip to content

PostgresJsInstrumentation Proxy crashes drizzle-orm mapFromDriverValue under load #20550

@coreydylan

Description

@coreydylan

Summary

After raising tracesSampleRate from 0.1 to 0.5 in a Next.js 15 / Vercel serverless app, a route running a simple drizzle-orm select started crashing inside drizzle's PgArray.mapFromDriverValue because one column value in the row tuple is undefined. The crash disappears immediately when Sentry's PostgresJs integration is filtered out. The interaction is between PostgresJsInstrumentation patching per-Query resolve/reject via Proxy and how postgres-js (porsager/postgres) feeds row data to drizzle's column decoders.

I do not have a minimal isolated repro — this only manifests under serverless concurrency with non-trivial sampling — but the root cause site is narrow enough that I think it is worth flagging.

Affected versions

  • @sentry/nextjs 9.47.1 (pulls in @sentry/node 9.47.1, @sentry/opentelemetry 9.47.1)
  • postgres 3.4.7 (prepare: false, fetch_types: false)
  • drizzle-orm 0.44.6 (postgres-js adapter)
  • Next.js 15 App Router, Node 20.20.2, Vercel serverless

Reproduction context

  • Route: server component running db.select().from(contests).orderBy(desc(contests.endDate)) (24 rows, 60 cols, ~237 KB, includes text[] and jsonb columns).
  • Trigger: tracesSampleRate >= ~0.3 plus warm serverless concurrency.
  • The same query against the same Supabase pooler from a local Node process (no auto-instrumentation) is clean. Production with Sentry auto-instrumentation crashes intermittently, then reliably as sampling rises.
  • I have not reduced this to a standalone script — it appears to depend on parallel Query execution against an instance whose Query.prototype.handle has been Proxied.

Symptom

TypeError: Cannot read properties of undefined (reading 'map')
    at r.mapFromDriverValue (.next/server/chunks/9800.js:1:37984)
    at <unknown> (.next/server/chunks/9800.js:1:40332)
    at Array.reduce (<anonymous>)
    at j (.next/server/chunks/9800.js:1:40118)
    ...
    at Object.startActiveSpan (.next/server/chunks/9800.js:28:41819)

drizzle's decoder (node_modules/drizzle-orm/pg-core/columns/common.js:180):

mapFromDriverValue(value) {
  if (typeof value === "string") {
    value = parsePgArray(value);
  }
  return value.map((v) => this.baseColumn.mapFromDriverValue(v));
}

value is undefined for one column position when iterating columns by index for one row. No nullable issues in the data — every text[]/jsonb column contains at least '{}'.

Root cause hypothesis

PostgresJsInstrumentation (in node_modules/@sentry/node/build/cjs/integrations/tracing/postgresjs.js, lines ~127–201) Proxies Query.prototype.handle and replaces the per-query callbacks:

handleThisArg.resolve = this._patchResolve(handleThisArg.resolve, span);
handleThisArg.reject  = this._patchReject(handleThisArg.reject, span);

_patchResolve (lines ~108–121) wraps resolve in a Proxy whose apply trap reads resolveArgs?.[0]?.command, sets a span attribute, and ends the span. Because postgres-js identifies the in-flight Query by reference identity on its handler and resolver, swapping resolve with a Proxy on a hot per-query object appears to disturb how postgres-js assembles row arrays from the streaming binary protocol — yielding tuples where some column position is undefined instead of the expected text/binary value, which then explodes inside drizzle's decoder.

The hypothesis is consistent with: (a) the failure being load-dependent, (b) it disappearing when only this integration is disabled, (c) the failing column index varying between requests.

What we tried

  • Verified schema vs. information_schema.columns — no mismatch.
  • Confirmed no NULL values in the affected text[] / jsonb columns.
  • Reproduced locally without Sentry against the same pooler — clean.
  • Lowered tracesSampleRate back to 0.1 — crash rate drops sharply.
  • Disabled only PostgresJs integration — crash gone immediately.

Workaround

Sentry.init({
  // ...
  integrations: (defaults) =>
    defaults.filter((i) => i.name !== "PostgresJs"),
});

Asks

  1. Is this a known interaction between PostgresJsInstrumentation and postgres-js / drizzle-orm? I searched issues and did not find it.
  2. If not, is the per-query resolve/reject Proxy strictly necessary, or could a non-mutating wrapper (e.g. attaching span end via .then/.finally on the returned promise, or hooking Query.prototype.handle's return path) achieve the same instrumentation without swapping callbacks on a live Query?
  3. Should porsager/postgres maintainers be looped in here, or is this best fixed entirely on the Sentry side?

Happy to test a patch build against our production workload if useful.

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

Status

Waiting for: Community

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions