Files
ironclaw/migrations/V31__root_filesystem_path_collation.sql
firat.sertgoz 9ac7b476cd [codex] Add hosted single-tenant Postgres profile (#5081)
* Add hosted single-tenant Postgres profile

* Persist hosted extension state under tenant storage

* fix(reborn): bound filesystem event tail reads

* fix(reborn): reduce hosted postgres read amplification

* fix(reborn): batch filesystem thread history reads

* fix(filesystem): avoid postgres prefix scans

* fix(reborn): harden hosted postgres bootstrap

* fix(webui): advance empty projection cursors

* fix(reborn): batch llm secret metadata reads

* perf(webui): back off idle stream polling

* perf(turns): cache fresh filesystem snapshots for reads

* fix(reborn): authorize extension lifecycle catalog mounts

* fix(reborn): time out wedged turn drivers

* fix(reborn): bound stuck heartbeat calls

* refactor: split postgres PR hot path helpers

* reborn: address hosted postgres review feedback

* turns: move test module after production items

* reborn: fix hosted postgres review feedback

* reborn: fix projection ci regressions

* tests: relax reborn harness heartbeat

* tests: widen reborn harness heartbeat

* ci: harden cargo network fetches

* reborn: fix hosted runtime gate diagnostic

* tests: wait for budget gate materialization

* reborn: fix hosted postgres review and ci issues

* reborn: tighten hosted postgres review fixes

* reborn: keep nearai bootstrap outcome local

* reborn: clarify hosted local-runtime seams

* fix(reborn): address CodeRabbit review feedback (#5081)

* test(reborn): stabilize runtime no-gateway failure check

* fix(ci): update test secret metadata expiry plumbing

* fix(threads): make postgres message acceptance atomic

* fix(filesystem): retry postgres migration connect

* fix(reborn): lower default postgres pool size

* fix(reborn): allow postgres pool cap override

* fix(filesystem): extend postgres startup retry window

* fix(railway): disable reborn deployment overlap

* fix(reborn): serve startup health before postgres runtime

* fix(reborn): harden startup and inbound accept paths

* fix(ci): restore reborn bootstrap build

* fix(ci): stabilize reborn harness heartbeat

* fix(reborn): collapse repeated auto-approve lookups

* fix(reborn): suppress noisy debug logs in hosted serve

* fix(reborn): raise hosted-single-tenant postgres pool to 16

The hosted profile shared a single deadpool across every Postgres-backed
subsystem (turns, threads, messages, events, secrets), and every filesystem
op checks out a connection. With pool_max_size=2 a single turn's reads plus
an open transaction monopolize both connections, so the runner heartbeat and
webui block indefinitely on pool.get() (no get() timeout), the 90s lease
expires, and the turn wedges with failure_category=lease_expired.

The cap was lowered to fit a managed session-pool limit during blue-green
deploy overlap; that overlap is resolved, so restore a healthy pool (16, the
prior default). Runtime override via IRONCLAW_REBORN_POSTGRES_POOL_MAX_SIZE
is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(reborn): cache prepared statements + bound postgres pool checkout

Two latency/deadlock fixes for the Postgres-backed root filesystem, which
backs every Reborn subsystem (turns, threads, messages, events, secrets) and
checks out a pooled connection per op.

Caching: every fixed-SQL read/write went through `client.query_opt(sql, ..)`
with a string, so tokio_postgres issued a fresh `Parse` (prepare) on every
call — ~2.77ms RTT to remote Postgres measured from production logs, ~48% of
each read, and a monotonically growing set of server-side prepared statements
(s1625, s1626, ... never reused). Route the deadpool `Object` (not the
deref'd `Client`) through new `cached_query_opt`/`cached_query`/
`cached_query_one`/`cached_execute` helpers that use deadpool's per-connection
`prepare_cached`. The Parse round-trip is now paid once per connection per
distinct statement; the pooled connection is held for ~half as long per op,
which is what relieves the pool contention behind the lease-expiry wedge.
Dynamic SQL (filter `query`, index DDL, create_dir_all's txn) stays uncached
to keep the cache bounded. Helpers return `tokio_postgres::Error` so existing
`db_error` mapping at call sites is unchanged.

Deadlock guard: the pool was built with no checkout timeout, so `Pool::get()`
blocks forever once all connections are busy — an unbounded wait wedges the
runner heartbeat and webui until the 90s lease expires. Add 30s
wait/create/recycle timeouts (well under the lease) so a saturated pool
surfaces a retryable error instead of hanging the process.

Verified: `cargo clippy` clean and `cargo check` pass for ironclaw_filesystem,
ironclaw_reborn_event_store, and ironclaw_reborn_composition with
postgres[,webui-v2-beta]. Live Postgres behavior + latency delta verified via
PR CI and Railway (no local live-pg test / Docker).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(reborn): size hosted postgres pool for the Supabase pooler (10)

The reference hosted deployment fronts Postgres with the Supabase Supavisor
session pooler (default pool size ~15), so the shared app pool must stay under
that cap. 16 could exceed it; 10 covers runtime concurrency (heartbeat, webui,
trigger poller, turn driver reads + open txn) with headroom for migrations and
admin sessions. Runtime override IRONCLAW_REBORN_POSTGRES_POOL_MAX_SIZE still
wins over this file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:07:11 +03:00

47 lines
1.7 KiB
SQL

-- Use bytewise ordering for virtual filesystem paths.
--
-- The Postgres backend uses half-open path ranges (`path >= prefix AND
-- path < next_prefix`) for prefix scans. Those ranges are only stable across
-- locales when the stored path column uses the C collation.
--
-- Rollback plan:
-- 1. Stop writers that depend on bytewise path range semantics.
-- 2. Revert both path columns to the database default collation:
-- ALTER TABLE root_filesystem_entries
-- ALTER COLUMN path TYPE TEXT COLLATE "default";
-- ALTER TABLE root_filesystem_events
-- ALTER COLUMN path TYPE TEXT COLLATE "default";
-- 3. Recreate any dependent path indexes if PostgreSQL reports they were
-- rebuilt or invalidated during the ALTER COLUMN TYPE operation.
DO $$
BEGIN
IF to_regclass('root_filesystem_entries') IS NOT NULL
AND EXISTS (
SELECT 1
FROM pg_attribute
WHERE attrelid = to_regclass('root_filesystem_entries')
AND attname = 'path'
AND NOT attisdropped
AND attcollation <> 'pg_catalog."C"'::regcollation
)
THEN
ALTER TABLE root_filesystem_entries
ALTER COLUMN path TYPE TEXT COLLATE "C";
END IF;
IF to_regclass('root_filesystem_events') IS NOT NULL
AND EXISTS (
SELECT 1
FROM pg_attribute
WHERE attrelid = to_regclass('root_filesystem_events')
AND attname = 'path'
AND NOT attisdropped
AND attcollation <> 'pg_catalog."C"'::regcollation
)
THEN
ALTER TABLE root_filesystem_events
ALTER COLUMN path TYPE TEXT COLLATE "C";
END IF;
END $$;