[{"data":1,"prerenderedAt":431},["ShallowReactive",2],{"navigation":3,"/blog/postmortem-neon-http-transactions":130,"surround-postmortem-neon-http-transactions":420},[4],{"title":5,"path":6,"stem":7,"children":8,"page":129},"Blog","/blog","blog",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":10,"path":11,"stem":12},"How to Increase Free Tier Memory on AWS EC2","/blog/aws-ec2-ram","blog/aws-ec2-ram",{"title":14,"path":15,"stem":16},"Blooper: The Disappearing Cloudflare Variables","/blog/blooper-cloudflare-env-vars","blog/blooper-cloudflare-env-vars",{"title":18,"path":19,"stem":20},"Blooper: Hardcoded Content Links cause 404s","/blog/blooper-hardcoded-nuxt-content-links","blog/blooper-hardcoded-nuxt-content-links",{"title":22,"path":23,"stem":24},"Blooper: The Invisible SEO","/blog/blooper-nuxt-seo-env","blog/blooper-nuxt-seo-env",{"title":26,"path":27,"stem":28},"Blooper: The SSR Crash of Doom","/blog/blooper-ssr-feedback-form","blog/blooper-ssr-feedback-form",{"title":30,"path":31,"stem":32},"Blooper: The YouTube Crawl Budget Killer","/blog/blooper-video-crawl-budget","blog/blooper-video-crawl-budget",{"title":34,"path":35,"stem":36},"Blooper: PWA Redirecting SPA Routes","/blog/blooper-vite-pwa-ssr-redirects","blog/blooper-vite-pwa-ssr-redirects",{"title":38,"path":39,"stem":40},"Disk Management in Linux","/blog/disk-management-in-linux","blog/disk-management-in-linux",{"title":42,"path":43,"stem":44},"Docker Devtools Stage | Powerlevel, OhMyZSH, tmux, fuzzy finder, autocomplete etc","/blog/docker-devtools","blog/docker-devtools",{"title":46,"path":47,"stem":48},"Docker container | Resolving Permission Denied Woes: A Decade-Long Struggle Unraveled","/blog/docker-non-root-user-perms","blog/docker-non-root-user-perms",{"title":50,"path":51,"stem":52},"Git commands | underrated","/blog/git-commands-underrated","blog/git-commands-underrated",{"title":54,"path":55,"stem":56},"Journey: Taming Edge Caching and KV Storage","/blog/journey-github-streak-caching","blog/journey-github-streak-caching",{"title":58,"path":59,"stem":60},"Kubernetes Cluster Setup Guide","/blog/k8s-setup","blog/k8s-setup",{"title":62,"path":63,"stem":64},"Linux WiFi Drivers","/blog/linux-wifi-drivers","blog/linux-wifi-drivers",{"title":66,"path":67,"stem":68},"pfSense basic firewall setup","/blog/pfsense-basic-firewall-setup","blog/pfsense-basic-firewall-setup",{"title":70,"path":71,"stem":72},"pfSense Quickstart","/blog/pfsense-quickstart","blog/pfsense-quickstart",{"title":74,"path":75,"stem":76},"Postmortem: Cloudflare Worker Missing Dependency on Deploy","/blog/postmortem-cloudflare-worker-dependency-deploy","blog/postmortem-cloudflare-worker-dependency-deploy",{"title":78,"path":79,"stem":80},"Postmortem: Serverless HTTP Driver Transaction Failures","/blog/postmortem-neon-http-transactions","blog/postmortem-neon-http-transactions",{"title":82,"path":83,"stem":84},"Postmortem: Nuxt Content 404s on Staging Deployments","/blog/postmortem-nuxt-content-404-staging","blog/postmortem-nuxt-content-404-staging",{"title":86,"path":87,"stem":88},"Postmortem: 500 Internal Server Error on ISR Routes","/blog/postmortem-nuxt-content-payload-extraction","blog/postmortem-nuxt-content-payload-extraction",{"title":90,"path":91,"stem":92},"Post-Mortem: Intrusive Database Driver Refactoring","/blog/postmortem-proxy-re-architecting","blog/postmortem-proxy-re-architecting",{"title":94,"path":95,"stem":96},"Post-Mortem: Cloudflare Worker Connection Hang & Resilient DB Fallbacks","/blog/postmortem-worker-db-connection-hang","blog/postmortem-worker-db-connection-hang",{"title":98,"path":99,"stem":100},"Pro Tip: Sandboxing Cloudflare Workers for AI Agents","/blog/pro-tip-wrangler-local-dev-trick","blog/pro-tip-wrangler-local-dev-trick",{"title":102,"path":103,"stem":104},"Proxmox DNS Management","/blog/proxmox-dns","blog/proxmox-dns",{"title":106,"path":107,"stem":108},"Proxmox Recovery","/blog/proxmox-recovery","blog/proxmox-recovery",{"title":110,"path":111,"stem":112},"Proxmox WiFi Connection Story","/blog/proxmox-wifi","blog/proxmox-wifi",{"title":114,"path":115,"stem":116},"roadauth-rails api jwt cors","/blog/rodauth-jwt","blog/rodauth-jwt",{"title":118,"path":119,"stem":120},"SOCKS5 Proxy Quickstart","/blog/sock5-proxy","blog/sock5-proxy",{"title":122,"path":123,"stem":124},"tmux status bar toggle","/blog/tmux-status","blog/tmux-status",{"title":126,"path":127,"stem":128},"UFW - Cheatsheet","/blog/ufw-cheatsheet","blog/ufw-cheatsheet",false,{"id":131,"title":78,"authors":132,"badge":138,"body":140,"date":409,"description":410,"extension":411,"image":412,"imageComponent":413,"meta":414,"navigation":415,"path":79,"pinned":129,"seo":416,"seoImage":417,"stem":80,"__hash__":419},"posts/blog/postmortem-neon-http-transactions.md",[133],{"name":134,"to":135,"avatar":136},"Rahul Dhole","/",{"src":137},"/profile.png",{"label":139},"Postmortem",{"type":141,"value":142,"toc":400},"minimark",[143,148,168,172,175,190,194,203,217,282,286,314,318,326,334,339,395],[144,145,147],"h2",{"id":146},"incident-summary","Incident Summary",[149,150,151,155,156,160,161,164,165],"p",{},[152,153,154],"strong",{},"Issue:"," The application encountered fatal ",[157,158,159],"code",{},"500 Internal Server Error"," responses when performing core write operations like generating documents or saving records.\n",[152,162,163],{},"Error Message:"," ",[157,166,167],{},"Error: No transactions support in http driver",[144,169,171],{"id":170},"root-cause","Root Cause",[149,173,174],{},"The application uses Drizzle ORM connected to a Serverless Postgres database. For Cloudflare Workers compatibility (to avoid WebSocket I/O limitations), the database connection dynamically uses a stateless HTTP driver.",[149,176,177,178,181,182,185,186,189],{},"However, several core data models and webhook handlers were wrapping database queries inside native ",[157,179,180],{},"db.transaction(async tx => { ... })"," blocks. Because HTTP connections are inherently stateless, they cannot maintain an open connection session to execute a ",[157,183,184],{},"BEGIN",", run arbitrary JavaScript logic (like waiting for Cloudflare R2 uploads), and then issue a ",[157,187,188],{},"COMMIT",". This fundamental incompatibility caused the driver to throw an exception immediately.",[144,191,193],{"id":192},"resolution","Resolution",[149,195,196,197,202],{},"To resolve the issue while preserving data consistency across distributed systems (Postgres + Cloudflare R2 + Usage Ledgers), we implemented a modified Saga/compensating-transaction pattern (",[198,199,201],"a",{"href":200},"#fig-1","Figure 1","):",[149,204,205,208,213],{},[198,206],{"id":207},"fig-1",[209,210],"mermaid",{":config":211,"code":212},"config","sequenceDiagram%0A%20%20%20%20participant%20App%20as%20Application%0A%20%20%20%20participant%20R2%20as%20Cloudflare%20R2%0A%20%20%20%20participant%20DB%20as%20Postgres%20(HTTP)%0A%0A%20%20%20%20App-%3E%3EApp%3A%20Generate%20UUID%0A%20%20%20%20App-%3E%3ER2%3A%20Upload%20File%0A%20%20%20%20alt%20Upload%20Fails%0A%20%20%20%20%20%20%20%20R2--%3E%3EApp%3A%20Error%0A%20%20%20%20%20%20%20%20App-%3E%3EApp%3A%20Abort%20Operation%0A%20%20%20%20else%20Upload%20Succeeds%0A%20%20%20%20%20%20%20%20R2--%3E%3EApp%3A%20Success%0A%20%20%20%20%20%20%20%20App-%3E%3EDB%3A%20Insert%20Record%0A%20%20%20%20%20%20%20%20alt%20Insert%20Fails%0A%20%20%20%20%20%20%20%20%20%20%20%20DB--%3E%3EApp%3A%20Error%0A%20%20%20%20%20%20%20%20%20%20%20%20App-%3E%3EApp%3A%20File%20orphaned%20in%20R2%20(Harmless)%0A%20%20%20%20%20%20%20%20else%20Insert%20Succeeds%0A%20%20%20%20%20%20%20%20%20%20%20%20DB--%3E%3EApp%3A%20Success%0A%20%20%20%20%20%20%20%20%20%20%20%20App-%3E%3EApp%3A%20Complete%20Operation%0A%20%20%20%20%20%20%20%20end%0A%20%20%20%20end",[214,215,216],"em",{},"Figure 1: Compensating transaction flow handling external storage and database",[218,219,220,231,245,268],"ol",{},[221,222,223,226,227,230],"li",{},[152,224,225],{},"Removed Native Transactions:"," Completely removed all ",[157,228,229],{},"db.transaction()"," wrappers across the application models.",[221,232,233,236,237,240,241,244],{},[152,234,235],{},"Pre-Generated UUIDs:"," Migrated from relying on Postgres ",[157,238,239],{},"RETURNING id"," to eagerly generating IDs in the Node/Worker runtime using ",[157,242,243],{},"globalThis.crypto.randomUUID()",".",[221,246,247,250,251,254,255],{},[152,248,249],{},"Reordered Network Operations:"," Shifted all external side-effects (specifically Cloudflare R2 object uploads) to occur ",[214,252,253],{},"first"," in the function logic.\n",[256,257,258],"ul",{},[221,259,260,263,264,267],{},[214,261,262],{},"Impact:"," If an R2 upload fails, the operation aborts cleanly before any database state is mutated. If a database query fails ",[214,265,266],{},"after"," a successful R2 upload, the system simply leaves an orphaned (but inaccessible and harmless) file in the bucket rather than corrupting user data or ledger quotas.",[221,269,270,273,274,277,278,281],{},[152,271,272],{},"Sequential Execution:"," Executed dependent database queries sequentially using ",[157,275,276],{},"await",". Because side-effects were resolved upfront, the chance of partial failure between sequential database inserts is practically zero, and the database schema's foreign key cascades (",[157,279,280],{},"onDelete: cascade",") ensure structural integrity.",[144,283,285],{"id":284},"lessons-learned","Lessons Learned",[218,287,288,298,304],{},[221,289,290,293,294,297],{},[152,291,292],{},"Serverless Limitations:"," Stateful database concepts like ",[157,295,296],{},"BEGIN/COMMIT"," transactions cannot be safely relied upon in serverless edge runtimes that mandate HTTP data-fetching.",[221,299,300,303],{},[152,301,302],{},"Side-Effect Management:"," Always execute non-transactional side-effects (like interacting with blob storage or third-party APIs) as early as possible in a pipeline to minimize the blast radius of partial failures.",[221,305,306,309,310,313],{},[152,307,308],{},"TypeScript Limitations:"," Driver-specific constraints (like ",[157,311,312],{},".transaction()"," missing from the HTTP driver) were masked by union types that incorrectly suggested full feature parity across environments.",[144,315,317],{"id":316},"action-items","Action Items",[256,319,320,323],{},[221,321,322],{},"Monitor the R2 bucket growth over time to evaluate if an orphaned-file cleanup cron job is necessary.",[221,324,325],{},"Ensure all future database models adhere to the \"Network First, DB Second\" pattern.",[149,327,328,329,333],{},"See ",[198,330,332],{"href":331},"#table-1","Table 1"," for a breakdown of these phases.",[149,335,336],{},[198,337],{"id":338},"table-1",[340,341,342,358],"table",{},[343,344,345],"thead",{},[346,347,348,352,355],"tr",{},[349,350,351],"th",{},"Phase",[349,353,354],{},"Action",[349,356,357],{},"Failure Consequence",[359,360,361,373,384],"tbody",{},[346,362,363,367,370],{},[364,365,366],"td",{},"1",[364,368,369],{},"Generate UUIDs",[364,371,372],{},"None (Local Execution)",[346,374,375,378,381],{},[364,376,377],{},"2",[364,379,380],{},"Network/R2 Uploads",[364,382,383],{},"Request Aborted cleanly",[346,385,386,389,392],{},[364,387,388],{},"3",[364,390,391],{},"Database Inserts",[364,393,394],{},"Orphaned File in R2 (Safe)",[149,396,397],{},[214,398,399],{},"Table 1: Action items and failure consequences",{"title":401,"searchDepth":402,"depth":402,"links":403},"",2,[404,405,406,407,408],{"id":146,"depth":402,"text":147},{"id":170,"depth":402,"text":171},{"id":192,"depth":402,"text":193},{"id":284,"depth":402,"text":285},{"id":316,"depth":402,"text":317},"2026-06-05","Why database transactions fail in serverless HTTP drivers and how to architect around them using compensating transactions.","md",null,{"type":139},{},true,{"title":78,"description":410},{"src":418},"https://placehold.co/800x400/0f172a/3b82f6?text=Serverless+HTTP+Transactions","XNwwcrX-l1_ixsc9mIPpnNCkMCpzN23FUDHRh7olzbw",[421,426],{"title":74,"path":75,"stem":76,"description":422,"imageComponent":423,"seoImage":424,"children":-1},"How to fix missing dependencies when deploying Cloudflare Workers from within a monorepo workspace.",{"type":139},{"src":425},"https://placehold.co/800x400/0f172a/3b82f6?text=Cloudflare+Worker+Dependencies",{"title":82,"path":83,"stem":84,"description":427,"imageComponent":428,"seoImage":429,"children":-1},"Debugging missing content and 404 errors when deploying Nuxt Content to Cloudflare D1.",{"type":139},{"src":430},"https://placehold.co/800x400/0f172a/3b82f6?text=Nuxt+Content+404s",1784464469173]