@opengeni/db 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OGCE6O2X.js → chunk-7LDU7F5P.js} +31 -3
- package/dist/chunk-7LDU7F5P.js.map +1 -0
- package/dist/chunk-B22X3IEZ.js +2634 -0
- package/dist/chunk-B22X3IEZ.js.map +1 -0
- package/dist/{chunk-57MLICFR.js → chunk-YFQ7SGE4.js} +18 -6
- package/dist/chunk-YFQ7SGE4.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16571 -5018
- package/dist/index.js.map +1 -1
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +2121 -248
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BUbuMteO.d.ts → schema-BN5mB9xZ.d.ts} +8716 -3432
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +43 -5
- package/drizzle/0044_reap_dead_turn_holders.sql +104 -0
- package/drizzle/0045_workspace_captures.sql +83 -0
- package/drizzle/0045_workspace_memory_v1.sql +71 -0
- package/drizzle/0046_variable_sets_rename.sql +56 -0
- package/drizzle/0047_rigs.sql +151 -0
- package/drizzle/0048_rig_runtime.sql +9 -0
- package/drizzle/0049_enrollment_went_offline.sql +28 -0
- package/drizzle/0050_enrollment_op_stream.sql +1 -0
- package/drizzle/0051_codex_pin_source.sql +48 -0
- package/drizzle/0052_file_upload_cleanup.sql +91 -0
- package/drizzle/0053_codex_credential_leases.sql +230 -0
- package/drizzle/0054_session_pins.sql +85 -0
- package/drizzle/0055_session_list_snapshots.sql +73 -0
- package/drizzle/0056_workspace_model_policies.sql +48 -0
- package/drizzle/0057_durable_queue_control.sql +536 -0
- package/drizzle/0058_turn_admission_usage_enrollment.sql +158 -0
- package/drizzle/0059_workspace_pause_control_kind.sql +12 -0
- package/drizzle/0060_session_system_update_deferral.sql +10 -0
- package/drizzle/0061_session_workflow_wake_outbox.sql +157 -0
- package/drizzle/0062_session_list_snapshot_reaper.sql +48 -0
- package/drizzle/0063_session_control_mega_foundation.sql +1324 -0
- package/package.json +13 -13
- package/src/codex-token-resolver.ts +58 -23
- package/src/connection-token-resolver.ts +146 -57
- package/src/environment-crypto.ts +5 -1
- package/src/event-payload-sanitizer.ts +29 -1
- package/src/index.ts +20990 -6465
- package/src/memory-domain.ts +218 -0
- package/src/migrate.ts +58 -3
- package/src/provision-roles.ts +46 -17
- package/src/schema.ts +2888 -1121
- package/src/session-control.ts +1759 -0
- package/src/session-queue-commands.ts +1753 -0
- package/src/session-tool-call-settlement.ts +269 -0
- package/dist/chunk-57MLICFR.js.map +0 -1
- package/dist/chunk-OGCE6O2X.js.map +0 -1
- package/dist/chunk-ZIUCA2IO.js +0 -1268
- package/dist/chunk-ZIUCA2IO.js.map +0 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
-- One-way production remediation after the 0057 session-control cutover.
|
|
2
|
+
--
|
|
3
|
+
-- 1. Provider response usage has exactly one authoritative current event. SDK
|
|
4
|
+
-- wrapper duplicates stay in the audit log with an explicit association.
|
|
5
|
+
-- 2. Workflow repair enrolls every durable state that needs a session workflow,
|
|
6
|
+
-- including approval/capacity waits and pending control settlement.
|
|
7
|
+
|
|
8
|
+
ALTER TABLE "session_events"
|
|
9
|
+
DROP CONSTRAINT "session_events_turn_association_check";
|
|
10
|
+
|
|
11
|
+
ALTER TABLE "session_events"
|
|
12
|
+
ADD COLUMN "duplicate_of_event_id" uuid,
|
|
13
|
+
ADD COLUMN "duplicate_reason" text;
|
|
14
|
+
|
|
15
|
+
ALTER TABLE "session_events"
|
|
16
|
+
ADD CONSTRAINT "session_events_duplicate_of_event_fk"
|
|
17
|
+
FOREIGN KEY ("duplicate_of_event_id")
|
|
18
|
+
REFERENCES "session_events"("id") ON DELETE RESTRICT;
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "session_events"
|
|
21
|
+
ADD CONSTRAINT "session_events_turn_association_check"
|
|
22
|
+
CHECK (
|
|
23
|
+
"turn_association" IS NULL
|
|
24
|
+
OR "turn_association" IN ('current', 'late_rejected', 'duplicate')
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- Keep the earliest event as the authoritative observation. Every later event
|
|
28
|
+
-- for the same turn/provider-response source remains queryable and points to
|
|
29
|
+
-- that canonical row; nothing is deleted or disguised as a late attempt write.
|
|
30
|
+
WITH ranked AS (
|
|
31
|
+
SELECT
|
|
32
|
+
e."id",
|
|
33
|
+
first_value(e."id") OVER (
|
|
34
|
+
PARTITION BY
|
|
35
|
+
e."workspace_id",
|
|
36
|
+
e."session_id",
|
|
37
|
+
e."turn_id",
|
|
38
|
+
e."payload" ->> 'sourceKey'
|
|
39
|
+
ORDER BY e."sequence", e."id"
|
|
40
|
+
) AS "canonical_id",
|
|
41
|
+
row_number() OVER (
|
|
42
|
+
PARTITION BY
|
|
43
|
+
e."workspace_id",
|
|
44
|
+
e."session_id",
|
|
45
|
+
e."turn_id",
|
|
46
|
+
e."payload" ->> 'sourceKey'
|
|
47
|
+
ORDER BY e."sequence", e."id"
|
|
48
|
+
) AS "ordinal"
|
|
49
|
+
FROM "session_events" e
|
|
50
|
+
WHERE e."type" = 'agent.model.usage'
|
|
51
|
+
AND e."turn_association" = 'current'
|
|
52
|
+
AND e."turn_id" IS NOT NULL
|
|
53
|
+
AND nullif(e."payload" ->> 'sourceKey', '') IS NOT NULL
|
|
54
|
+
)
|
|
55
|
+
UPDATE "session_events" e
|
|
56
|
+
SET "turn_association" = 'duplicate',
|
|
57
|
+
"duplicate_of_event_id" = ranked."canonical_id",
|
|
58
|
+
"duplicate_reason" = 'duplicate_provider_response_usage'
|
|
59
|
+
FROM ranked
|
|
60
|
+
WHERE e."id" = ranked."id"
|
|
61
|
+
AND ranked."ordinal" > 1;
|
|
62
|
+
|
|
63
|
+
ALTER TABLE "session_events"
|
|
64
|
+
ADD CONSTRAINT "session_events_duplicate_classification_check"
|
|
65
|
+
CHECK (
|
|
66
|
+
(
|
|
67
|
+
"turn_association" = 'duplicate'
|
|
68
|
+
AND "type" = 'agent.model.usage'
|
|
69
|
+
AND "duplicate_of_event_id" IS NOT NULL
|
|
70
|
+
AND "duplicate_of_event_id" <> "id"
|
|
71
|
+
AND nullif("duplicate_reason", '') IS NOT NULL
|
|
72
|
+
)
|
|
73
|
+
OR (
|
|
74
|
+
"turn_association" IS DISTINCT FROM 'duplicate'
|
|
75
|
+
AND "duplicate_of_event_id" IS NULL
|
|
76
|
+
AND "duplicate_reason" IS NULL
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE UNIQUE INDEX "session_events_current_model_usage_source_uq"
|
|
81
|
+
ON "session_events" (
|
|
82
|
+
"workspace_id",
|
|
83
|
+
"session_id",
|
|
84
|
+
"turn_id",
|
|
85
|
+
(("payload" ->> 'sourceKey'))
|
|
86
|
+
)
|
|
87
|
+
WHERE "type" = 'agent.model.usage'
|
|
88
|
+
AND "turn_association" = 'current'
|
|
89
|
+
AND "turn_id" IS NOT NULL
|
|
90
|
+
AND nullif("payload" ->> 'sourceKey', '') IS NOT NULL;
|
|
91
|
+
|
|
92
|
+
-- The old name described database claimability, not the actual operational
|
|
93
|
+
-- contract. A workflow must also be enrolled while it waits for approval or
|
|
94
|
+
-- capacity, and any pending Pause/Steer control needs a workflow to settle it.
|
|
95
|
+
DROP FUNCTION opengeni_private.list_claimable_sessions(integer);
|
|
96
|
+
|
|
97
|
+
CREATE FUNCTION opengeni_private.list_enrollable_sessions(p_limit integer)
|
|
98
|
+
RETURNS TABLE (
|
|
99
|
+
account_id uuid,
|
|
100
|
+
workspace_id uuid,
|
|
101
|
+
session_id uuid,
|
|
102
|
+
temporal_workflow_id text
|
|
103
|
+
)
|
|
104
|
+
LANGUAGE sql
|
|
105
|
+
SECURITY DEFINER
|
|
106
|
+
AS $$
|
|
107
|
+
SELECT DISTINCT
|
|
108
|
+
s.account_id,
|
|
109
|
+
s.workspace_id,
|
|
110
|
+
s.id,
|
|
111
|
+
coalesce(s.temporal_workflow_id, 'session-' || s.id::text)
|
|
112
|
+
FROM sessions s
|
|
113
|
+
JOIN workspaces w ON w.id = s.workspace_id
|
|
114
|
+
WHERE
|
|
115
|
+
-- Controls are durable intent. Even a closed inference gate must enroll a
|
|
116
|
+
-- workflow long enough to settle the pending Pause/Steer fence.
|
|
117
|
+
s.pending_control_event_id IS NOT NULL
|
|
118
|
+
OR (
|
|
119
|
+
s.control_state = 'active'
|
|
120
|
+
AND (
|
|
121
|
+
w.inference_state = 'active'
|
|
122
|
+
OR s.workspace_run_exception_generation = w.inference_generation
|
|
123
|
+
)
|
|
124
|
+
AND (
|
|
125
|
+
EXISTS (
|
|
126
|
+
SELECT 1 FROM session_turns t
|
|
127
|
+
WHERE t.workspace_id = s.workspace_id
|
|
128
|
+
AND t.session_id = s.id
|
|
129
|
+
AND t.status IN ('queued', 'recovering', 'waiting_capacity', 'requires_action')
|
|
130
|
+
)
|
|
131
|
+
OR EXISTS (
|
|
132
|
+
SELECT 1 FROM session_system_updates u
|
|
133
|
+
WHERE u.workspace_id = s.workspace_id
|
|
134
|
+
AND u.session_id = s.id
|
|
135
|
+
AND u.state = 'pending'
|
|
136
|
+
)
|
|
137
|
+
OR EXISTS (
|
|
138
|
+
SELECT 1 FROM session_goals g
|
|
139
|
+
WHERE g.workspace_id = s.workspace_id
|
|
140
|
+
AND g.session_id = s.id
|
|
141
|
+
AND g.status = 'active'
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
ORDER BY s.id
|
|
146
|
+
LIMIT greatest(1, least(coalesce(p_limit, 1000), 10000));
|
|
147
|
+
$$;
|
|
148
|
+
|
|
149
|
+
REVOKE ALL ON FUNCTION opengeni_private.list_enrollable_sessions(integer) FROM PUBLIC;
|
|
150
|
+
|
|
151
|
+
DO $$
|
|
152
|
+
DECLARE target_schema text := current_schema();
|
|
153
|
+
BEGIN
|
|
154
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
155
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.list_enrollable_sessions(integer)
|
|
156
|
+
TO opengeni_app;
|
|
157
|
+
END IF;
|
|
158
|
+
END $$;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Workspace Pause is a first-class durable control kind. The 0057 constraint
|
|
2
|
+
-- predates that control path and must match the canonical runtime state machine.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE "sessions"
|
|
5
|
+
DROP CONSTRAINT "sessions_pending_control_kind_check";
|
|
6
|
+
|
|
7
|
+
ALTER TABLE "sessions"
|
|
8
|
+
ADD CONSTRAINT "sessions_pending_control_kind_check"
|
|
9
|
+
CHECK (
|
|
10
|
+
"pending_control_kind" IS NULL
|
|
11
|
+
OR "pending_control_kind" IN ('pause', 'workspace_pause', 'steer')
|
|
12
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- A failed internal-only inference preserves ordinary internal updates without
|
|
2
|
+
-- making them independently runnable again. They become eligible when a real
|
|
3
|
+
-- prompt or a genuinely new pending internal update starts the next inference.
|
|
4
|
+
|
|
5
|
+
ALTER TABLE "session_system_updates"
|
|
6
|
+
DROP CONSTRAINT "system_updates_state_check";
|
|
7
|
+
|
|
8
|
+
ALTER TABLE "session_system_updates"
|
|
9
|
+
ADD CONSTRAINT "system_updates_state_check"
|
|
10
|
+
CHECK ("state" IN ('pending', 'deferred', 'delivered', 'cancelled', 'failed'));
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
-- OPE-50: replace the blind periodic scan of "enrollable" sessions with a
|
|
2
|
+
-- transactional, revisioned, coalescing delivery ledger. Postgres is the
|
|
3
|
+
-- durable work source; a Temporal signal is an idempotent nudge.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE "session_workflow_wake_outbox" (
|
|
6
|
+
"session_id" uuid PRIMARY KEY,
|
|
7
|
+
"account_id" uuid NOT NULL,
|
|
8
|
+
"workspace_id" uuid NOT NULL,
|
|
9
|
+
"temporal_workflow_id" text NOT NULL,
|
|
10
|
+
"wake_revision" bigint NOT NULL DEFAULT 1,
|
|
11
|
+
"delivered_revision" bigint NOT NULL DEFAULT 0,
|
|
12
|
+
"reason" text NOT NULL,
|
|
13
|
+
"attempts" integer NOT NULL DEFAULT 0,
|
|
14
|
+
"next_attempt_at" timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
"last_error" text,
|
|
16
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
18
|
+
CONSTRAINT "session_workflow_wake_outbox_revision_check"
|
|
19
|
+
CHECK ("wake_revision" > 0 AND "delivered_revision" >= 0
|
|
20
|
+
AND "delivered_revision" <= "wake_revision"),
|
|
21
|
+
CONSTRAINT "session_workflow_wake_outbox_workspace_account_fk"
|
|
22
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
23
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
24
|
+
CONSTRAINT "session_workflow_wake_outbox_workspace_session_fk"
|
|
25
|
+
FOREIGN KEY ("workspace_id", "session_id")
|
|
26
|
+
REFERENCES "sessions"("workspace_id", "id") ON DELETE CASCADE
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE UNIQUE INDEX "session_workflow_wake_outbox_workspace_session_uq"
|
|
30
|
+
ON "session_workflow_wake_outbox" ("workspace_id", "session_id");
|
|
31
|
+
CREATE INDEX "session_workflow_wake_outbox_pending_idx"
|
|
32
|
+
ON "session_workflow_wake_outbox" ("next_attempt_at", "updated_at", "session_id")
|
|
33
|
+
WHERE "wake_revision" > "delivered_revision";
|
|
34
|
+
|
|
35
|
+
ALTER TABLE "session_workflow_wake_outbox" ENABLE ROW LEVEL SECURITY;
|
|
36
|
+
ALTER TABLE "session_workflow_wake_outbox" FORCE ROW LEVEL SECURITY;
|
|
37
|
+
CREATE POLICY workspace_isolation ON "session_workflow_wake_outbox"
|
|
38
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
39
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
40
|
+
|
|
41
|
+
-- Workspace control retries now persist the exact control deliveries they own.
|
|
42
|
+
-- Canonicalize pre-cutover receipts once so runtime code has one result shape;
|
|
43
|
+
-- any still-pending historical control is independently seeded below.
|
|
44
|
+
UPDATE "runtime_control_operations"
|
|
45
|
+
SET "result" = jsonb_set("result", '{controls}', '[]'::jsonb, true)
|
|
46
|
+
WHERE "scope" = 'workspace'
|
|
47
|
+
AND jsonb_typeof("result") = 'object'
|
|
48
|
+
AND NOT ("result" ? 'controls');
|
|
49
|
+
|
|
50
|
+
-- Claim only committed revisions that still need delivery. The due timestamp
|
|
51
|
+
-- is advanced before delivery, so a process death becomes retryable after the
|
|
52
|
+
-- bounded backoff. Acknowledgements are revision-scoped: an old delivery can
|
|
53
|
+
-- never hide a newer committed wake.
|
|
54
|
+
DO $migration$
|
|
55
|
+
DECLARE target_schema text := current_schema();
|
|
56
|
+
BEGIN
|
|
57
|
+
EXECUTE format($create$
|
|
58
|
+
CREATE FUNCTION opengeni_private.claim_session_workflow_wakes(p_limit integer)
|
|
59
|
+
RETURNS TABLE (
|
|
60
|
+
account_id uuid,
|
|
61
|
+
workspace_id uuid,
|
|
62
|
+
session_id uuid,
|
|
63
|
+
temporal_workflow_id text,
|
|
64
|
+
wake_revision bigint,
|
|
65
|
+
control_event_id uuid
|
|
66
|
+
)
|
|
67
|
+
LANGUAGE plpgsql
|
|
68
|
+
SECURITY DEFINER
|
|
69
|
+
SET search_path = pg_catalog
|
|
70
|
+
AS $function$
|
|
71
|
+
BEGIN
|
|
72
|
+
RETURN QUERY
|
|
73
|
+
WITH due AS (
|
|
74
|
+
SELECT o.session_id
|
|
75
|
+
FROM %1$I.session_workflow_wake_outbox o
|
|
76
|
+
WHERE o.wake_revision > o.delivered_revision
|
|
77
|
+
AND o.next_attempt_at <= now()
|
|
78
|
+
ORDER BY o.next_attempt_at, o.updated_at, o.session_id
|
|
79
|
+
FOR UPDATE SKIP LOCKED
|
|
80
|
+
LIMIT greatest(1, least(coalesce(p_limit, 100), 1000))
|
|
81
|
+
)
|
|
82
|
+
UPDATE %1$I.session_workflow_wake_outbox o
|
|
83
|
+
SET attempts = o.attempts + 1,
|
|
84
|
+
next_attempt_at = now() + make_interval(
|
|
85
|
+
secs => least(300, greatest(1, power(2, least(o.attempts, 8))::integer))
|
|
86
|
+
),
|
|
87
|
+
updated_at = now()
|
|
88
|
+
FROM due
|
|
89
|
+
WHERE o.session_id = due.session_id
|
|
90
|
+
RETURNING o.account_id, o.workspace_id, o.session_id,
|
|
91
|
+
o.temporal_workflow_id, o.wake_revision,
|
|
92
|
+
(SELECT s.pending_control_event_id FROM %1$I.sessions s WHERE s.id = o.session_id)
|
|
93
|
+
AS control_event_id;
|
|
94
|
+
END $function$;
|
|
95
|
+
$create$, target_schema);
|
|
96
|
+
END $migration$;
|
|
97
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer) FROM PUBLIC;
|
|
98
|
+
|
|
99
|
+
-- One-time cutover seed. This is intentionally the final use of an eligibility
|
|
100
|
+
-- scan: it converts already-committed pre-outbox work into explicit revisions.
|
|
101
|
+
-- Runtime repair after this migration reads only the outbox.
|
|
102
|
+
INSERT INTO "session_workflow_wake_outbox" (
|
|
103
|
+
"session_id", "account_id", "workspace_id", "temporal_workflow_id", "reason"
|
|
104
|
+
)
|
|
105
|
+
SELECT DISTINCT
|
|
106
|
+
s."id",
|
|
107
|
+
s."account_id",
|
|
108
|
+
s."workspace_id",
|
|
109
|
+
coalesce(s."temporal_workflow_id", 'session-' || s."id"::text),
|
|
110
|
+
'cutover_seed'
|
|
111
|
+
FROM "sessions" s
|
|
112
|
+
JOIN "workspaces" w ON w."id" = s."workspace_id"
|
|
113
|
+
WHERE
|
|
114
|
+
s."pending_control_event_id" IS NOT NULL
|
|
115
|
+
OR (
|
|
116
|
+
s."control_state" = 'active'
|
|
117
|
+
AND (
|
|
118
|
+
w."inference_state" = 'active'
|
|
119
|
+
OR s."workspace_run_exception_generation" = w."inference_generation"
|
|
120
|
+
)
|
|
121
|
+
AND (
|
|
122
|
+
EXISTS (
|
|
123
|
+
SELECT 1 FROM "session_turns" t
|
|
124
|
+
WHERE t."workspace_id" = s."workspace_id"
|
|
125
|
+
AND t."session_id" = s."id"
|
|
126
|
+
AND t."status" IN ('queued', 'recovering', 'waiting_capacity', 'requires_action')
|
|
127
|
+
)
|
|
128
|
+
OR EXISTS (
|
|
129
|
+
SELECT 1 FROM "session_system_updates" u
|
|
130
|
+
WHERE u."workspace_id" = s."workspace_id"
|
|
131
|
+
AND u."session_id" = s."id"
|
|
132
|
+
AND u."state" = 'pending'
|
|
133
|
+
)
|
|
134
|
+
OR EXISTS (
|
|
135
|
+
SELECT 1 FROM "session_goals" g
|
|
136
|
+
WHERE g."workspace_id" = s."workspace_id"
|
|
137
|
+
AND g."session_id" = s."id"
|
|
138
|
+
AND g."status" = 'active'
|
|
139
|
+
)
|
|
140
|
+
OR s."compact_requested" = true
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
DROP FUNCTION opengeni_private.list_enrollable_sessions(integer);
|
|
145
|
+
|
|
146
|
+
DO $$
|
|
147
|
+
DECLARE target_schema text := current_schema();
|
|
148
|
+
BEGIN
|
|
149
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
150
|
+
EXECUTE format(
|
|
151
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.session_workflow_wake_outbox TO opengeni_app',
|
|
152
|
+
target_schema
|
|
153
|
+
);
|
|
154
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer)
|
|
155
|
+
TO opengeni_app;
|
|
156
|
+
END IF;
|
|
157
|
+
END $$;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- OPE-44: session list reads are serializable and subject-scoped. Performing a
|
|
2
|
+
-- global expired-row delete inside every read makes concurrent API replicas
|
|
3
|
+
-- conflict on the same rows. Move TTL cleanup to one bounded SKIP LOCKED
|
|
4
|
+
-- reaper operation and keep the request transaction strictly request-local.
|
|
5
|
+
|
|
6
|
+
CREATE INDEX "session_list_snapshots_expiry_reaper_idx"
|
|
7
|
+
ON "session_list_snapshots" ("expires_at", "id");
|
|
8
|
+
|
|
9
|
+
DO $migration$
|
|
10
|
+
DECLARE target_schema text := current_schema();
|
|
11
|
+
BEGIN
|
|
12
|
+
EXECUTE format($create$
|
|
13
|
+
CREATE FUNCTION opengeni_private.reap_expired_session_list_snapshots(p_limit integer)
|
|
14
|
+
RETURNS integer
|
|
15
|
+
LANGUAGE plpgsql
|
|
16
|
+
SECURITY DEFINER
|
|
17
|
+
SET search_path = pg_catalog
|
|
18
|
+
AS $function$
|
|
19
|
+
DECLARE deleted_count integer;
|
|
20
|
+
BEGIN
|
|
21
|
+
WITH victims AS (
|
|
22
|
+
SELECT s.id
|
|
23
|
+
FROM %1$I.session_list_snapshots s
|
|
24
|
+
WHERE s.expires_at < now()
|
|
25
|
+
ORDER BY s.expires_at, s.id
|
|
26
|
+
FOR UPDATE SKIP LOCKED
|
|
27
|
+
LIMIT greatest(1, least(coalesce(p_limit, 500), 5000))
|
|
28
|
+
), deleted AS (
|
|
29
|
+
DELETE FROM %1$I.session_list_snapshots s
|
|
30
|
+
USING victims
|
|
31
|
+
WHERE s.id = victims.id
|
|
32
|
+
RETURNING s.id
|
|
33
|
+
)
|
|
34
|
+
SELECT count(*)::integer INTO deleted_count FROM deleted;
|
|
35
|
+
RETURN deleted_count;
|
|
36
|
+
END $function$;
|
|
37
|
+
$create$, target_schema);
|
|
38
|
+
END $migration$;
|
|
39
|
+
|
|
40
|
+
REVOKE ALL ON FUNCTION opengeni_private.reap_expired_session_list_snapshots(integer) FROM PUBLIC;
|
|
41
|
+
|
|
42
|
+
DO $$
|
|
43
|
+
BEGIN
|
|
44
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
45
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.reap_expired_session_list_snapshots(integer)
|
|
46
|
+
TO opengeni_app;
|
|
47
|
+
END IF;
|
|
48
|
+
END $$;
|