postgres(minor): async query support via dblink + MongoDB job tracking#1007
postgres(minor): async query support via dblink + MongoDB job tracking#1007salatv-ai wants to merge 9 commits into
Conversation
- Add AsyncJobModel.js: MongoDB model for pgAsyncJobs collection with TTL cleanup - Add connections.js: process-global pg.Client management for dblink sessions (same pattern as Kafka connector, survives require() cache clears) - Add jobs.js: polling job (every 5s) + cleanup job (every 10min), both with distributed locks to prevent duplicate processing across cluster nodes - Add config.js: configurable schedules and thresholds - Add plugin.js: plugin entry point registering background jobs - Update Query.js: async mode path (startAsyncQuery + deliverAsyncResult), sync path unchanged; results delivered via triggerComponent from jobs.js - Update Query component.json: asyncMode toggle input (index 3) - Bump bundle.json to 2.2.0 with changelog Flow: asyncMode=true → insertOne(pgAsyncJobs) → dblink_send_query → return jobs.js polls → dblink_is_busy → done → updateOne(result) → triggerComponent receive(_asyncJobId) → findOne(pgAsyncJobs) → sendJson(out) Closes: part of appmixer-connectors#977
- test-flow-crud.json: full CRUD lifecycle (CreateRow/Query/UpdateRow/DeleteRow) - test-flow-query.json: Query outputTypes - rows, row, file - test-flow-schema.json: ListTables + ListColumns - test-flow-async-query.json: asyncMode (dblink) end-to-end (AfterAll timeout 120s) Part of Appmixer-ai#977
- Replace double quotes with single quotes - Rename _row to jsonRow to avoid no-underscore-dangle - Use named catch variable instead of _
Code Review — Postgres Async Query (PR #1007)✅ What looks good
|
…ty result test - Added sync query step (regression check that sync mode still works) - Added async empty result test (emptyResult port) - All 3 assert branches connected to AfterAll - Timeout increased to 120s for async polling delay
AsyncJobModel was extending context.db.Model which doesn't exist in the Appmixer component context. Since all CRUD operations already use context.db.collection() directly, the Model base class was unnecessary. Simplified AsyncJobModel to a plain object exporting only the collection name constant. Fixes TypeError: Cannot read properties of undefined (reading 'Model')
Component context doesn't have context.db — only service-level (plugin/jobs/routes) does. Refactored async query architecture: - Query.js now registers jobs via context.service.stateAddToSet() instead of writing to MongoDB directly - jobs.js syncs pending jobs from service state to MongoDB on each poll - Result rows are delivered inline via triggerComponent payload (_asyncRows) instead of component reading from MongoDB This fixes TypeError: Cannot read properties of undefined (reading 'collection') in component context.
triggerComponent delivers data through context.messages.webhook, not context.messages.in. The component was crashing because context.messages.in was undefined when called back by jobs.js. Check for webhook message first before accessing input port.
… errors - Check both webhook.content.data, webhook.content and in.content for async callback data from triggerComponent (covers all Appmixer versions) - Also check in port for async data in case triggerComponent routes there - Rename _asyncJobId/_asyncRows/_asyncError to drop leading underscore (eslint no-underscore-dangle rule)
…ping The user query gets wrapped in a subquery for dblink: SELECT row_to_json(t)::text FROM (<query>) t A trailing semicolon in the user query causes a syntax error inside the subquery parentheses.
Async Query Support via
dblink+ MongoDB Job TrackingOverview
Adds a non-blocking async execution path to the PostgreSQL
Querycomponent. Long-running queries are dispatched viadblink_send_queryand tracked in MongoDB — allowing the Appmixer flow to return immediately and deliver results later without occupying a worker thread.Architecture
Async Execution Flow
Key Components
AsyncJobModel.jspgAsyncJobscollection; TTL index for automatic cleanupconnections.jspg.Clientpool for persistentdblinksessions; survivesrequire()cache clears (same pattern as Kafka connector)jobs.jsconfig.jsplugin.jsQuery.jsasyncModetoggleWhy
dblink?PostgreSQL's
dblinkextension allows one connection to fire a query and another to poll its status — decoupling execution from result delivery. This avoids long-held connections and worker starvation on slow queries.Why MongoDB for job state?
Appmixer already uses MongoDB as its operational store. Storing async job state there (vs. Redis or in-memory) gives us:
Sync Path
Unchanged.
asyncMode=false(default) goes through the existing execution path with zero overhead.