Skip to content

Skip search slots until a database has an index - #103

Merged
matt-welch merged 3 commits into
mainfrom
fix/is222-search-slots-starve-first-build
Oct 27, 2026
Merged

matt-welch merged 3 commits into
mainfrom
fix/is222-search-slots-starve-first-build

Conversation

@matt-welch

@matt-welch matt-welch commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Enrolling a database parked its full set of search slots immediately, before the database had any index. The slots are parallel workers, so they count against max_parallel_workers for the backend that registers them. When the session running the first CREATE INDEX had a max_parallel_workers no larger than the parked slot count, every build worker registration failed and the build silently ran on one thread. A per-role or per-session setting made this easy to hit, because it overrides the server value.

This PR makes two changes, and leaves the way the search and build threads are split between databases alone.

  • A database with no vamana index no longer gets a search claim, so no slots are parked until the first index commits. The launcher computes zero desired threads for it, and the worker parks exactly the published grant. A standby keeps its current behavior, since it does not maintain the index count. Committing the first index, or dropping the last one, now wakes the launcher so slots are parked or released right away instead of after its three minute naptime. A live database granted zero now holds zero slots rather than one; searches still get at least one thread.
  • A build that starts fewer parallel workers than it was granted now emits a WARNING with the started and granted counts, the number of threads the build will use, and the session's max_parallel_workers. The hint does not name a single cause, because the registration failure does not say why it failed. A build granted zero threads stays silent.

The worker also kicks the launcher a second time, after its startup transaction seeds the index count. Without it, a restart leaves the launcher reading a count of zero for a database that has indexes, and its slots stay unparked until the next naptime. The earlier kick stays where it was, because the reconcile it triggers completes memory admission, which the startup residency seed depends on. Moving that kick instead of adding a second one broke the residency accounting test.

Several existing tests expected grants or parked slots in databases with no index, which is no longer true. They now create an index in setup where the test is not about the no-index state. docs/RESOURCE_SIZING.md and docs/USER_GUIDE.md describe the new behavior and the new warning.

The regression CI config also changes: max_parallel_workers in ci/test_server.conf goes from 8 to 10. Under the old value, cpu_governance leaves 8 search threads parked in contrib_regression, so later builds in dynamic and halfvec got 0 or 1 of their 2 granted workers. That was already happening, but silently. With the new warning it showed up in the expected output, and the count varied from run to run. The pool now covers the 8 search threads plus a build's max_parallel_maintenance_workers of 2. The warning is not suppressed anywhere, so any build that comes up short in a regression file still fails the diff. The starved-build case itself is covered deterministically by the new TAP test and by cpu_governance.

The failure artifact in .github/workflows/build-and-test.yml now also uploads log/, where each TAP test writes its server log and regress_log. Until now a TAP failure left only the regression logs, which gave no way to see why a test failed or stalled. The step still runs only on failure.

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Refactor / code cleanup
  • Documentation update
  • Test addition or update
  • Build / CI change

Pre-Merge Checklist

Build

  • make completes without errors or warnings
  • make install completes successfully

Tests

  • If this PR introduces no new behavior: existing regression tests (make installcheck) and TAP tests (test/t/) pass with no failures
  • If this PR introduces new behavior: test cases covering it were added to test/sql/ and/or test/t/
  • If this PR adds a standalone unit-test module under test/modules/: it builds and passes (make -C test/modules/<module> installcheck)

Documentation

  • Relevant docs under docs/ updated if architecture or usage changed

Testing Notes

The full TAP suite passes (72 files, 1529 tests; only 18_build_offset_overflow.pl skips, since it needs enable_slow_tests=yes), as do the hardware-dependent TAP tests (2 files, 55 tests). The SQL regression suite passes (10 of 10), as do the hardware-dependent SQL tests (2 of 2). svs_cpu_budget_test passes with two new cases for a database with no index. The new TAP test 75_search_slots_no_index_build.pl covers the first build, slots appearing within 30 seconds of the first index committing, the warning with and without room in the pool, and slots being released after the last index is dropped.

I checked that the new tests can fail by reversing each change with git apply -R and confirming the matching cases fail: the no-index gate, the raw-grant resize, the commit-time launcher kick, the warning, and the second startup kick (51_worker_crash_scope.pl test 21).

I also ran the original reproduction on a scratch cluster with 16 search and build threads and a 20k by 768 table, smaller than the original 50k. Right after enrolling, the database showed 0 granted and 0 registered slots. The first build with a session limit of 16 got all 16 workers with no warning. Once the index was live and 16 slots were parked, the same limit started 0 of 16 workers and printed the warning, a limit of 24 started 8 of 16 and printed it, and a limit of 32 started 16 and printed nothing.

For the CI config change, I started a scratch server with ci/test_server.conf and ran make installcheck repeatedly with different pool sizes. With 8, dynamic failed with the shortfall warnings. With 9, all 3 runs failed with 2 or 3 warnings. With 10, all 8 runs passed with no warnings, and 12 also passed in 3 of 3 runs.

On CI, the SQL regression step now passes on both legs, and the 22.04 leg has passed on every run since the pool change. The 24.04 leg, which also runs the TAP suite with -j2, passed once (72 files, 1529 tests) and failed on several other runs, in a few different places (two of them ended with pg_ctl start failed for which I have no logs). Test 58 stalled in a 12,000-row rollback in two runs before TAP logs were being uploaded, so I have no log of that and its cause is not established. It passed in the later runs. 73_primary_slot_catchup_scale.pl failed in two runs with logs: once when a 60-row insert hit the test's 1000 ms worker timeout while the worker was saving the index, and once when the test's SIGKILL and immediate restart raced the old worker's exit and the new server refused to start because the shared memory was still in use. Neither mechanism involves search slot parking, and I have not compared the failure rate against main. The log/ upload ran on those failures and the artifacts contained the TAP and server logs.

The first checklist item under Tests is left unchecked because this PR does change behavior, and a number of existing tests were edited to match it, so "no failures with no changes" would not be accurate.

A newly enrolled database parked its full set of search slots before it
had any index to search. Those slots are parallel workers, so they count
against max_parallel_workers, and the first CREATE INDEX in that
database could not register its build workers. The build silently ran
on one thread.

 - The launcher now computes a search claim of zero for a database with
   no vamana index, so no slots are parked until the first index
   commits. A standby keeps its current behavior because it does not
   maintain the index count.
 - The worker parks exactly the published grant, so a live database
   granted zero holds zero slots. Searches still get at least one
   thread.
 - Committing the first index, or dropping the last one, wakes the
   launcher so slots are parked or released promptly instead of after
   the launcher's naptime.
 - The worker kicks the launcher again after its startup transaction
   seeds the index count, so a restart does not leave a database with
   indexes at zero slots until the next naptime. The earlier kick
   stays, because that reconcile completes memory admission, which the
   startup residency seed needs.
 - A build that starts fewer parallel workers than it was granted now
   emits a WARNING with the counts, the thread count it will use, and
   the session's max_parallel_workers.
 - svs_cpu_budget_test takes an optional per-database has-indexes array.
   A new TAP test covers the first build, the slot lifecycle around the
   first and last index, and the warning.
 - Existing tests that expected grants in databases with no index now
   create one in setup. docs/RESOURCE_SIZING.md and docs/USER_GUIDE.md
   describe the new behavior.

Signed-off-by: Matt Welch <matt.welch@intel.com>
@matt-welch
matt-welch requested a review from a team October 26, 2026 17:48
asonje
asonje previously approved these changes Oct 26, 2026

@asonje asonje left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff and tested on a scratch cluster. it matches the stated design; hasIndexes gate on the search claim, prompt launcher kicks on both the commit-time transition and the post-restart seed, and the shortfall WARNING. Sum-and-split apportionment is correctly left untouched.

I also ran it with an end-to-end test and the search slots no longer starve the build threads. confirmed the fix holds under a real workload, not just the TAP suite.

LGTM, approving.

After cpu_governance runs, the regression server keeps 8 search threads
parked in contrib_regression, and ci/test_server.conf capped
max_parallel_workers at 8. Every later CREATE INDEX or REINDEX in that
database (in dynamic and halfvec) therefore got 0 or 1 of its 2 granted
build workers. That was already happening silently, but builds now warn
when they start fewer workers than granted, and because the count
depends on launcher timing the warnings made those two files fail with
output that differs from run to run.

 - Raise max_parallel_workers from 8 to 10 in ci/test_server.conf, so
   the pool covers the 8 parked search threads plus a build's
   max_parallel_maintenance_workers of 2. Under the CI config, 9 failed
   in 3 of 3 full installcheck runs and 10 passed in 8 of 8, with no
   warnings.
 - Leave the warning unfiltered, so a build that comes up short in any
   regression file still fails the diff.
 - Update the comment above the setting to say why the pool needs that
   room.

Signed-off-by: Matt Welch <matt.welch@intel.com>
The failure artifact for build-and-test only kept the regression logs
and the shared server log, so a TAP test that failed or stalled left
nothing to explain why. Each TAP test writes its server log and
regress_log to log/ at the repo root, so add that directory to the
artifact paths. The upload still runs only on failure, and
assert-build.yml already collects log/ the same way.

Signed-off-by: Matt Welch <matt.welch@intel.com>
@asonje
asonje self-requested a review October 27, 2026 21:05

@asonje asonje left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also checked the two follow-up commits (bb25b32, b5cf7d1) — CI-only, no change to the fix itself: one adds TAP log uploads on failure, the other bumps the CI pool to max_parallel_workers = 10 since cpu_governance.sql's own seed index now leaves slots parked into later test files. Makes sense given the earlier change, no concerns.

@matt-welch
matt-welch merged commit cf18ecb into main Oct 27, 2026
11 of 14 checks passed
@matt-welch
matt-welch deleted the fix/is222-search-slots-starve-first-build branch October 27, 2026 21:08
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.

2 participants