Skip to content

perf(schemas): sample a table's columns across instances in one query - #153

Merged
Upd4ting merged 2 commits into
mainfrom
perf/schema-sample-cross-instance
Nov 10, 2026
Merged

Upd4ting merged 2 commits into
mainfrom
perf/schema-sample-cross-instance

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Why

The DMS database console should stay fast on schemas with one instance per tenant. The Schemas page, the Overview, the health card, the data browser's table list and the row references all read listSchemaSummaries(). It infers each table's columns from a 5-row sample, cached for 30 s. When a table's default instance held no row, sampleAnyInstance probed the named instances one query at a time until one answered rows. A table empty in every instance paid one round trip per tenant, on every cache miss.

Change

src/service/introspect.ts: when the default instance holds no row, read the 5-row sample once through CROSS_INSTANCE instead of looping over listSchemaInstances(). On MongoDB a cross-instance read adds no $match, so it is one aggregate with $limit: 5.

Workload

Two isolated DMS runs (dms-control up), each with its own mongod: A on origin/main, B on this branch. The playground's links schema has five tables, empty in every instance. Its named instances were raised to 3, 50 and 500 by rows in __antelope_instances. Each sample is one cold GET /api/database/schemas/ after the 30 s caches expired. 7 samples per side, interleaved, with the order alternating each round. mongod's profiler counted the queries on links__* in each request.

Evidence

Confirmed in an end-to-end benchmark.

Named instances Before (median, range) After (median, range) Queries on links
3 39.7 ms (37.0 to 56.5) 44.5 ms (38.1 to 97.9) 25 → 15
50 70.9 ms (59.9 to 128.2) 33.7 ms (29.6 to 45.3) 260 → 15
500 340.5 ms (335.3 to 533.4) 34.9 ms (30.9 to 52.6) 1,241 → 15

Primary number: a cold Schemas request at 500 named instances goes from 340.5 ms to 34.9 ms, 90% faster. At 3 instances the two sides overlap, so normal-size schemas see no measurable difference.

  • Limiter. Node was idle. The cost is serial round trips: 1,226 fewer queries at about 0.25 ms each comes to about 306 ms, and the measured saving is 305 ms.
  • Errors. 0 errors on both sides. The inferred fields of all 19 playground schemas match between A and B, shop included, whose rows live only in its eu and us instances.

Impact

One cold request every 30 s for whoever opens a Schemas, Overview or data-browser page, per schema with named instances. On a 500-tenant schema that is about 0.3 s saved each time, and 1,226 fewer queries against the database. The impact is low, but it scales with the tenant count.

Behaviour note

The sample now spans tenants instead of coming from the first tenant, in creation order, that holds rows. Both are 5-row samples. When tenants store different types in one column, or a column only some tenants fill, the inferred type can differ. One case is a column typed number by tenant A's rows and string by tenant B's. The design note (docs/design/redesign-v2-grill.md) says only that a named instance is sampled when the default one is empty. Keeping the exact old rule would take an ordered cross-instance read, which sorts the whole table on every cold request.

Test

src/test/schemas-page.test.ts adds a tenanted schema whose only row is in a named instance. The test asserts its amount column is typed number. It fails when sampling reads only the default instance:

1) types the columns of a table whose rows are all in a named instance
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:

It passes on this branch, and pnpm run checks passes (Checks: 8 passed, 0 failed.).

flowchart LR
  S[sampleAnyInstance] --> D{default instance has rows?}
  D -- yes --> R[use them]
  D -- no, before --> L[for each named instance: one query] --> R
  D -- no, after --> X[one CROSS_INSTANCE slice 0..5] --> R
Loading

Readability

The function loses its loop and its call to listSchemaInstances. The diff is 5 lines added and 9 removed.

No overlap with #152, which changes src/service/stagedSerialization.ts.

Verify skill

verify-dms-database on ae8afb3, with the features plan lists. The verdict is PASS+NOTES.

Feature Passed Failed
smoke 1 0
extending 3 0
schemas 22 0

The schemas check that a table whose rows live in named instances (shop.order_items) lists its sampled columns still passes. Its wording described the old one-instance rule, so a50a4b0 rewords it.

When a table's default instance holds no row, its columns were inferred by probing each named instance in turn, one query per instance until one answered rows. A schema with one instance per tenant paid one round trip per tenant for every empty table, each time the 30 s fields cache expired. One cross-instance sample answers the same rows in one query.
@Upd4ting
Upd4ting merged commit 53d6b85 into main Nov 10, 2026
10 checks passed
@Upd4ting
Upd4ting deleted the perf/schema-sample-cross-instance branch November 10, 2026 16:25
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