feat: add net.http_request callback interface (on_success/on_error) from #62#267
feat: add net.http_request callback interface (on_success/on_error) from #62#267riderx wants to merge 1 commit into
Conversation
|
hey @steve-chavez i would love your imput in this, i have been strugglying with this for 2 years now. |
|
@olirice as well a review would be amazing, i can provide more test or more details if needed |
This comment was marked as outdated.
This comment was marked as outdated.
|
@riderx Discussed this on #62 (comment), I don't think we should complicate the current interface more and instead add a new one. |
b11ec46 to
d8d6269
Compare
Implements the callback-based interface discussed in supabase#62 on top of the existing queue, as an addition to the current API: net.http_request() hands the response to an on_success SQL command ($1 = status_code, $2 = headers, $3 = body) or, for failures without a response, to an on_error SQL command ($1 = error message, $2 = timed_out). Requests made this way never touch net._http_response, so callers that don't read responses (webhook-style notifications, queue wake-ups) stop paying for the response row insert plus its later TTL delete. With no on_success callback the response is discarded without even buffering the body in memory (fire-and-forget). Callbacks are executed by the background worker inside a subtransaction, as the role that enqueued the request, with SET ROLE/SET SESSION AUTHORIZATION blocked, so a callback can't do anything the enqueuing role couldn't. A failing callback is rolled back and reported as a WARNING without aborting the batch. Retry policies stay in user hands: callbacks can re-enqueue via net.http_request() again. The existing http_get/http_post/http_delete functions and their table-based behavior are completely unchanged. The worker tolerates a binary newer than the installed extension version (shared library replaced before ALTER EXTENSION pg_net UPDATE runs): it checks for the new queue columns and falls back to the legacy query when they don't exist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d8d6269 to
637e5d1
Compare
|
@steve-chavez thanks for the direction — I reworked the PR based on your comment in #62 (comment). The select net.http_request(
'POST', 'https://example.com/webhook',
body := '{"key": "value"}'::jsonb,
on_success := 'insert into results values (, , )', -- status, headers, body
on_error := 'insert into failures values (, )' -- error msg, timed_out
);
Full test coverage in Could you take a look and tell me if this matches the idea in #62 better? Happy to adjust the semantics (callback arg shapes, error handling, naming) to whatever you prefer, and to open the use-case issue you asked for if that's still the right process. |
What kind of change does this PR introduce?
Feature: a first implementation of the callback interface proposed by @steve-chavez in #62, added alongside the existing API (which is left completely untouched).
What is the new behavior?
net.http_requestnever touchnet._http_response: the response is handed toon_success, or discarded when no callback is given.on_successempty. The response body is then not even buffered in memory by the worker (a discarding libcurl write callback is used).on_successwith the status code as$1. Only failures without a response (timeout, connection error) go throughon_error, with the error message and atimed_outflag so users can implement their own retry decisions (e.g. re-enqueue vianet.http_requestagain). A failure with noon_errorcallback is reported in the logs atLOGlevel.http_get/http_post/http_deleteand their table-based behavior are unchanged — existing users and observability workflows are unaffected.Callback execution semantics
SPI_execute_with_args, inside a subtransaction, as the role that enqueued the request, withSET ROLE/SET SESSION AUTHORIZATIONblocked (SECURITY_LOCAL_USERID_CHANGE | SECURITY_RESTRICTED_OPERATION). A callback can't do anything the enqueuing role couldn't — tested.WARNINGwithout aborting the batch or crashing the worker — tested.Upgrade safety
The shared library is typically replaced before
ALTER EXTENSION pg_net UPDATEruns. The worker checks (viaget_attnum) whether the new queue columns exist and falls back to the legacy query when they don't, so a newer binary keeps processing requests normally against a not-yet-updated extension. Verified end-to-end locally (v0.20.5 binary → new binary with extension still at 0.20.4 →ALTER EXTENSION pg_net UPDATE).Benchmark
5,000 async GET requests against a local HTTP server returning a 10 KB body, default
pg_net.batch_size=200, measured on the background worker process (PostgreSQL 17.10, Apple M-series, macOS; single run,psCPU-time granularity ~10 ms — treat as directional):net.http_get(stored)net.http_request(fire-and-forget)net._http_responsetuple writes (ins + del)net._http_responsesize after runWall-clock is identical in both modes (dominated by the worker's fixed 1 s sleep between batches). The savings are the response-table inserts, their later TTL deletes, the resulting autovacuum pressure (not captured above), and the response-body buffering.
Additional context
test/test_http_request_callbacks.py: fire-and-forget stores nothing;on_successreceives status/headers/body; non-2xx statuses go throughon_success; POST with body;on_errorreceives the error message; a raising callback doesn't kill the worker or the batch; callbacks run as the enqueuing role; a low-privilege enqueuer can't write where its role can't; legacy functions still store responses; missing-column fallback.net.http_postand never reads responses; the response-row insert + TTL delete churn is pure overhead at that frequency. Happy to also open a dedicated issue elaborating this if still useful.0.21.0with migrationsql/pg_net--0.20.4--0.21.0.sql(columns added tonet.http_request_queue; no existing objects modified).🤖 Generated with Claude Code