Skip to content

Add upstream-shaped scoped introspection plugin - #1763

Draft
Zetazzz wants to merge 12 commits into
test/performance-harness-corefrom
feat/cnc-scoped-introspection-upstream-copy
Draft

Add upstream-shaped scoped introspection plugin#1763
Zetazzz wants to merge 12 commits into
test/performance-harness-corefrom
feat/cnc-scoped-introspection-upstream-copy

Conversation

@Zetazzz

@Zetazzz Zetazzz commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

This is an upstream-shaped, opt-in alternative to #1719. It copies the exact Crystal release sources pinned by CNC (pg-introspection@1.0.1, graphile-build-pg@5.1.3) and keeps the upstream package boundaries while adding a scoped query and replacement introspection plugin.

Unset/false uses untouched upstream stock introspection. GRAPHILE_SCOPED_INTROSPECTION=true dynamically installs ScopedIntrospectionPreset; GRAPHILE_SCOPED_INTROSPECTION_JIT independently controls the introspection session JIT setting and defaults to false.

Architecture and configuration

  • pg-introspection: parameterized recursive schema/dependency-closure query, all/dependency-closure catalog type policy, and extension capability retention.
  • graphile-build-pg: complete CNC-owned PgScopedIntrospectionPlugin, upstream lifecycle/watch/entity behavior, namespace and missing-type fail-closed guards, and pinned upstream contract sentinel.
  • Native Graphile configuration: scoped options live in preset.gather, keyed by PostgreSQL service name. PG service objects are no longer mutated or extended with CNC-only fields.
  • CNC wiring: the public boolean/env model remains unchanged; the server converts enabled CNC options into gather.pgScopedIntrospection and keeps JIT in pgSettingsForIntrospection.
  • Stock startup does not load the scoped package, and ConstructivePreset remains unchanged.

Equivalent direct configuration:

const preset = {
  extends: [ScopedIntrospectionPreset],
  pgServices: [
    makePgService({
      name: "main",
      schemas: ["app_public"],
      pgSettingsForIntrospection: { jit: "off" },
    }),
  ],
  gather: {
    pgScopedIntrospection: {
      main: {
        catalogTypes: "dependency-closure",
        allowedDependencySchemas: ["app_private"],
        capabilityExtensions: ["pg_trgm"],
      },
    },
  },
};

Presence of a service entry enables scoped introspection for that service. Services without an entry remain stock, mixed stock/scoped services are supported, and an unknown configured service fails closed.

Benchmark arms differ only by upstream PgIntrospectionPlugin/stock query versus ScopedIntrospectionPreset/scoped query. They do not load ConstructivePreset, retirement/disposers, governor/cache limits, routing, or admission plugins.

Correctness coverage

The real PostgreSQL dependency-closure fixture covers cross-schema enum/domain/composite/range/multirange types, function signatures, ordinary and pg_trgm indexes, extension metadata, a cross-schema foreign key, and inheritance direction. It proves the required dependency parent is retained while a reverse parent-to-child relationship does not pull an unrelated child into the closure. No production query-algorithm change was needed for this configuration migration.

Additional contracts cover mixed stock/scoped services, stock replacement equivalence, persistent gather cache behavior, unknown-service rejection, invalid dependency schemas, missing-type rejection, schema equality, and runtime execution.

Catalog benchmark

The scoped-only benchmark reuses the generic fresh-process runner/protocol/report. It uses a fixed 65-table root surface plus dependency and unrelated noise schemas, scalable catalog noise, identical explicit JIT settings, public gather-hook entity counts, and a separate query-only worker. Every arm used 10 fresh-process samples and all 240 worker PIDs were unique.

Environment: macOS arm64, Node v22.22.0, PostgreSQL 18.4 in a dedicated local container. PostgreSQL catalog cache is labeled shared-server-not-reset; this is not a pristine-cache cold-start claim.

JIT off (primary)

Negative percentages mean scoped reduced the metric.

Size pg_class Build ms stock → scoped p50/p95 Paired Δ Query ms stock → scoped p50/p95 Paired Δ JSON bytes stock → scoped
small 825 322/333 → 329/342 +1.9% 56/60 → 60/66 +6.0% 1,372,051 → 1,060,547
medium 5,631 626/669 → 341/347 -45.8% 270/277 → 64/71 -76.5% 13,684,742 → 1,060,616
large 65,037 4,682/5,066 → 398/430 -91.5% 3,116/3,214 → 111/147 -96.5% 163,949,368 → 1,060,547

At large scale, parsed classes fell from 64,220 to 270 and median heap fell from 839.6 MiB to 46.7 MiB. The small fixture shows why the feature remains opt-in.

JIT diagnostic

With JIT enabled, compilation dominates the recursive query at small/medium scale. The primary comparison therefore uses identical JIT-off sessions, matching CNC's scoped default. The benchmark reports the requested and observed JIT value for every run.

Stock/scoped schema hashes matched at every size and every runtime verification query passed:

  • small: d31e8b1c2d9cab3d1e67dcbc734e5addd237637028089221cf5f014b9ede4aa5
  • medium: 8c4e2e03008c98295978712461b799d23957ae89071326a16421ac4a7d0b1996
  • large: 41c6ed8feb087766680ab1dc4d833e5082dbf69322b5fb2a118515e7c7ff9db0

Reproduction

pnpm --filter @constructive-io/perf-harness build
node packages/perf-harness/dist/scoped-catalog.js prepare --database-url <url> --fixture <unique-cperf-name> --size <small|medium|large>
node packages/perf-harness/dist/scoped-catalog.js run --database-url <url> --fixture <same-name> --size <same-size> --repetitions 10 --seed 20260819 --output-directory packages/perf-harness/artifacts/<name>

Fixture preparation never replaces or drops schemas. The large synthetic result demonstrates scale behavior; it is not a claim about typical production gains.

Validation

Passing:

  • graphile-scoped-introspection: 30 tests / 8 suites, including 6 real PostgreSQL dependency-closure cases
  • scoped graphile-settings wiring/JIT contracts: 3 tests
  • GraphQL server stock/scoped wiring: 5 tests
  • performance harness: 18 tests / 9 suites
  • CJS and ESM builds for scoped introspection, GraphQL types, graphile-settings, GraphQL server, and perf-harness
  • affected ESLint with no broad autofix
  • pnpm install --frozen-lockfile
  • git diff --check and PR-wide format-only diff audit

No database URL or password appears in benchmark JSON output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant