WIP perf(graphile): harden tenant density without SQL rewrite - #1652
WIP perf(graphile): harden tenant density without SQL rewrite#1652yyyyaaa wants to merge 10 commits into
Conversation
|
@pyramation @Zetazzz Conceptually, the algorithm changes are: old PRs: reduce memory by making structurally similar tenants share one canonical Graphile instance: normalize schema → fingerprint → shared blueprint
request → generate canonical SQL → rewrite schema names → executeThat scales with the number of unique blueprints, but isolation depends on every generated, plugin, raw-SQL, and prepared-statement path being rewritten correctly. The new algorithm keeps an exact instance per tenant/API contract and instead makes each instance much smaller: exact tenant contract → scoped catalog query → small dedicated Graphile build
request → already-physical SQL → sanitized pool + complete GUCs → executeSo the old approach reduced the number of instances; the new one reduces the memory cost of each instance. |
08e1848 to
666e2a0
Compare
|
This draft has been superseded by the smaller, bottom-up draft stack created for review:
The replacement stack preserves the current approach and its evidence while making each security/performance concern reviewable against its direct parent. All six replacement PRs are drafts and remain a production no-go until the blockers documented in their descriptions are closed. The old #1330–#1335 SQL-rewrite/blueprint-pooling direction is deprecated. Valid plugin fixes, cache-governor ideas, and harness concepts were reimplemented or hardened, but runtime SQL rewriting and cross-tenant blueprint sharing are not part of the replacement. The dedicated-instance candidate gets its measured memory reduction from parameterized, dependency-scoped introspection and released build-only state while retaining exact physical tenant/API pool and build identities. Closing #1652 as superseded; its branch is intentionally retained for provenance and recovery. |
Caution
This is a research draft, not a production-ready change. The measured data-plane design is promising, but production remains blocked on an atomic/versioned tenant security contract, WebSocket revocation, production policy proof, upstream Graphile review, and final workload qualification.
Summary
This draft supersedes the old Graphile scale stack #1330–#1335 and rebuilds its useful work on
origin/mainat2331ba016, while rejecting the part that made tenant isolation depend on runtime SQL rewriting and shared tenant blueprints.The objective is complete resident customer tenants per GiB of memory. A 1 GiB heap is one normalization point in the planned capacity matrix, not a product constraint.
Zhi's SQL-rewrite concern is sound: RLS filters rows after PostgreSQL resolves an object, so it cannot prove that a rewritten query reached the correct schema, function, sequence, index, metadata object, or privileged code path. Dan's plugin, BM25, and GUC concerns are also valid review requirements. This candidate addresses them by removing runtime rewrite/pooling entirely:
scoped-requiredfails closed and never silently falls back.Why this approach is better than the old stack
regclass-like valuesDISCARD ALL, prepared-cache clearing, and client destruction on reset failureSET LOCALrestoration is treated as expected PostgreSQL behavior, while every reused checkout starts from a sanitized state.This gives up the old stack's most aggressive same-blueprint sharing claim. That is deliberate: density is only useful when tenant isolation remains at least as strong as today's exact physical boundary.
Progress
origin/mainsnapshot2331ba016.503codes, and observability.TenantSecurityContractV1with one atomic, monotonic revision across routing and all security metadata.authRequiredan authoritative routed API contract.mainadvanced tod66ae8e18after this branch was pushed, and GitHub currently reports merge conflicts. Per this draft's scope, parity is deferred rather than hidden.Commit-by-commit review guide
The branch is split into ten dependency-ordered commits. Each exact commit passes
CI=1 pnpm install --frozen-lockfile --ignore-scripts, and the final tree is byte-identical to the previously validated three-commit draft.ab50c1b42— plugin SQL quoting and metadata/build-cache isolation across bulk, i18n, LLM/RAG, search, ltree, PostGIS, and function bindings.2899a8724— exact PostgreSQL pool identities, checkout sanitation, notification roles, and request query context.86a80e85a— tenant-isolated storage module discovery, physical bucket coordinates, credentials, signing, and RLS-aware access.a76d05882— request-scoped tenant context and fail-closed security/configuration loaders.6aad36dea— Graphile instance cache governance plus realtime resource and generation lifecycle management.817fdc4ad— exact Graphile build contracts and fail-closed schema-scoped introspection, including the isolated upstream patch candidate.1ed6d5868— server integration and tenant request admission: runtime-role checks, internal headers, CAPTCHA, WebSockets, routing, and observability.3f40abd92— attested cperf harness, workload scoring, process isolation, and report generation.93da3a662— hostile complete-tenant and physical-database density fixtures.666e2a038— security audit, performance evidence, original-stack disposition, and upstream/qualification notes.What this draft includes
GraphileBuildContractV1identities over the complete tenant/API execution contract, never hostname alone.DISCARD ALL, node-postgres/Graphile prepared-statement bookkeeping reset, and client destruction on failure.BYPASSRLS,CREATEROLE, ownership, schemaCREATE, unsafe inheritance/SET ROLE, unsafeSECURITY DEFINER, and cross-schema dependency capabilities.Detailed notes live in:
research/graphile-density/REPORT.mdresearch/graphile-density/SECURITY-AUDIT.mdresearch/graphile-density/PLUGIN-SQL-AUDIT.mdresearch/graphile-density/UPSTREAM-REVIEW.mdConfirmed performance evidence
The clean benchmark used a production-shaped catalog with 62,298
pg_classrows, 346,369 attributes, 8,496 procedures, 23,709 types, and 4,036 namespaces. Each arm ran in three fresh Node processes with a 4 GiB V8 heap and warmed 100 distinct Grafast operations plus three exact replay passes.Both arms emitted the same 17,976-byte GraphQL SDL with SHA-256
5fb82f96153815b23820a9ccf10322a20c864e49605ef5781cd33422b3b31020, and the recorded operations had zero errors, mismatches, or cross-tenant tokens.The raw clean-run summaries remain local/gitignored at:
research/graphile-density/artifacts/production-shaped-20260801-v6-clean/stock-release-destroy-clean-h4096-r3/summary.jsonresearch/graphile-density/artifacts/production-shaped-20260801-v6-clean/scoped-dependency-release-destroy-clean-h4096-r3/summary.jsonThese numbers measure one dedicated Graphile surface, not complete customers per GiB. They predate the final security-hardening pass, so they do not establish final-code throughput, p99, cache capacity, or production readiness. Security checks must not be removed to recover benchmark performance.
Confirmed isolation evidence and its limit
Before the final
mainrefresh, a disposable PostgreSQL 18 A/B/C fixture used three distinctLOGIN NOINHERITruntime roles, forced-RLS tenant tables, exact physical pools, scoped-required introspection, BM25/vector/PostGIS/ltree, and realtime-resident instances. It passed 56/56 hostile checks in 3.9 seconds withcrossTenantTokens: 0.The checks covered generated and plugin SQL, cross-schema identifiers, metadata, functions, sequences, search extensions, owner/BYPASS rejection, poisoned GUCs, rollback/savepoints, same-backend prepared-statement reuse, schema drift, cache invalidation, concurrent cold builds, realtime instances, and alternating connection reuse. Separate live PostgreSQL sanitizer and runtime-role suites passed 2/2 each.
The generated hostile-run artifact remains local/gitignored at
research/graphile-density/complete-tenant-fixture/generated/hostile-validation.json. This evidence validates the fixture and exact physical data-plane mechanism, but it does not prove every deployed schema, policy, role grant, dependency object, provider integration, or control-plane transition. The hostile run must be repeated after the rebase and after the versioned security contract lands.Why this is not production-ready
Production is currently a no-go. The unresolved work is part of the security contract, not cleanup.
Routing and security metadata do not share one atomic revision. Routing, RLS, auth, feature, CORS, public-key, and WebAuthn data are loaded independently. A domain handover or revocation can leave an in-flight HTTP request using a captured old authorization snapshot. Exact identities confine it to the captured physical tenant, but stale authorization is still incorrect.
Long-lived transports do not yet revalidate against authoritative current state. A WebSocket operation compares against the connection's captured route/generation. A handover or revocation can remain effective only after the socket closes or its generation retires. The new revision must drive admission, cache retirement, active subscription termination, and every operation check.
The production database policy manifest is unproven. The fixture proves the mechanism, while production still needs verification of every runtime/request role, exposed/dependency object, shared-row RLS policy, and intended
FORCE ROW LEVEL SECURITYrule.Authentication requirements are not yet authoritative per API.
strictAuth=falsedeliberately supports public APIs and permits a missing RLS module. APIs that require authentication need a routedauthRequiredcontract so missing configuration fails closed.Scoped introspection depends on local source patches. It must remain off by default until Graphile maintainers review the isolated API and the patches are replaced with supported upstream behavior.
Operational trust boundaries still need deployment proof. The internal routing credential must be stripped at public ingress and accepted only over authenticated encrypted service hops.
X-Meta-Schemais a disabled-by-default cross-tenant administrative capability and requires a separate private ingress if enabled. Runtime credential resolution and explicitly admitted plugins remain trusted code.Release qualification is incomplete. Full parity with current
main, external LLM/MinIO paths, multipart byte roundtrips, repeated complete-customer density runs, mixed workload latency/error gates, and the two-hour maximum-density soak remain open. The 6.55 MiB result is not yet a safe governor capacity setting.The old PR stack is deprecated
This draft is the only continuation path. #1330–#1335 are retained as historical provenance and should not be merged independently.
The old approximately 14.7 MiB retained-heap, 417 ms cold-build, 87x reduction, and same-blueprint density claims came from the rejected rewrite/pooling system. They are historical context, not current evidence. The current dedicated-instance evidence is 6.55 MiB and 130.34 ms for one production-shaped surface; complete-customer density is still pending.
Validation status
Post-rebase focused checks completed for this draft:
git diff --checkare clean.The full root build and broader package/security suites passed on the pre-rebase tip, but they are deliberately left unchecked above until rerun for current-main parity. The MinIO integration suite could not connect to a local MinIO service; live provider/storage qualification remains an explicit gate rather than a skipped success.
Scope
This PR changes only
constructive. It contains noconstructive-dbchanges, deployment, production enablement, SQL rewrite, or tenant blueprint pooling. Stock introspection remains the default, and no upstream maintainer has been contacted.Suggested review order: security audit and routing/runtime boundary first, Graphile source patches second, plugin trust/raw SQL paths third, then cache governor and benchmark harness.