You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
db: index sandboxes by ID suffix and by lowercased name - #1522
Adds two expression indexes on sandboxes for the admin sandbox list's search (#1523). Nothing reads them until that change merges.
idx_sandboxes_id_suffix on reverse(replace(id::text, '-', '')) COLLATE "C". The console labels a sandbox with the last eight hex digits of its UUIDv7, because the leading digits are the creation timestamp. Reversed, the IDs that end in a given suffix form one key range.
idx_sandboxes_name_lower on left(lower(name), 256) COLLATE "C": the names that start with a text, ignoring case, form one key range.
COLLATE "C" makes each range a byte range under any database collation. The statements read it with plain >= and <, which a generic plan can use as index conditions. A LIKE prefix with text_pattern_ops is indexable only when the pattern is a plan-time constant, so a cached generic plan would scan the table.
The name key keeps the first 256 characters, at most 1 kB. The API caps new names at 256 bytes, but the column is unconstrained. A btree entry over about 2.7 kB would fail the build on an older, longer name and leave an INVALID index.
Each index is built CONCURRENTLY in its own single-statement -- no-transaction migration, following db/README.md and 20261102223448_public_operations_org_state_index. They land alone because the migration guard rejects migrations combined with application changes.
scripts/check-postgres-migrations-squawk.sh: no issues in the 2 up migrations (this also runs the partial-index and UUIDv4 checks).
scripts/check-migration-versions.sh and scripts/check-postgres-migrations-isolated.sh pass.
On a fresh local Kind stack deployed from this head (81164dd), the migration job applied both indexes. The stack then served the console checks against an API without the search (#1524). Every database that #1523's cargo nextest run -p pplx-space-api -p pplx-space-cluster-pool -p pplx-space-internal-api-server --all-features creates applies them: 3074 passed at #1523's head 6f5e710.
Build cost: in a disposable Postgres 16 with 300,000 synthetic rows, CREATE INDEX CONCURRENTLY took 0.31 s and 0.28 s. The indexes are 17 MB and 9 MB, beside a 22 MB table. The plans that use them are in #1523.
Severity and risk
Severity: does not apply. This enables the search in #1523.
Risk:
Writes. An insert writes one entry into each index. An update that changes neither id nor name and is otherwise HOT stays HOT. An update that is not HOT anyway also inserts an entry into both indexes; one that changes an indexed column such as lifecycle, for example.
Building. A concurrent build does not block writers. It waits for older transactions, and runs under the session's lock and statement timeouts, because a single-statement file cannot set its own.
A failed build leaves an INVALID index and no history row. To recover, run that migration's down statement by hand and rerun the migration; the comments say how.
Rollback: run the down migrations, which drop each index concurrently.
Rollout
Deploy this before #1523, which reads these indexes; until then they only cost writes. As with any migration, an image that does not bundle these versions refuses a database that has applied them, so rolling back below this release means running the down migrations first.
The admin sandbox list is to search sandboxes by the label the console
shows, which is the end of the sandbox's UUIDv7, and by name ignoring
case. No index orders IDs by their end or holds the lowercased name, so
either search would read every sandbox row.
Add two expression btrees, each built CONCURRENTLY in its own
single-statement no-transaction migration: the ID's undashed hex
reversed, and the first 256 characters of the lowercased name, both
COLLATE "C" so the statements can read a prefix as a plain byte range
in custom and generic plans. The name key is cut at 256 characters so a
row stored before the name cap cannot fail the build.
Automatic approval is disabled by the configured approval policy for this pull request. Review comments and requests for changes continue normally.
Commit: 81164ddc21c4.
No actionable comments were generated in the recent review.
Walkthrough
The migrations add concurrent B-tree indexes for sandbox ID suffix searches and sandbox name prefix searches. The ID index uses hyphen-free, reversed ID text with C collation. The name index uses the first 256 characters of the lowercased name with C collation. Each index has a rollback migration that drops it concurrently if it exists. The migration comments describe transaction requirements and failure recovery.
Merge Risk: Minimal · up to 81164
No actionable merge-blocking issue is identified for these index migrations; they are mergeable after normal migration checks.
Security architecture risk: Low · up to 81164
The change adds search indexes without changing access controls or sandbox records. Concurrent builds and bounded index keys limit disruption, but an interrupted build can require manual cleanup before deployment can proceed.
Retained concerns
No architecture-level concerns identified.
Security review details
Security Blast Radius
inferred — Deployment work affects the shared sandbox table, rather than one tenant's records. The relevant exposure is database build and maintenance load or delayed migration completion, not a newly reachable data-disclosure path. Production impact magnitude is not established by the reviewed source.
Trust Boundaries and Controls
inferred — Sandbox identifiers and names become index keys through fixed built-in expressions, not executable SQL or a new privileged interface. The DDL-only comparison introduces no new identity transition, tenant filtering rule, credential access, or permission grant. Authorization behavior of the future search consumer remains outside this assessment.
Resilience and Maintainability Implications
observed — Concurrent creation avoids the ordinary index build's writer-blocking table lock, but can wait on older snapshots. These single-statement migrations inherit session timeouts and cannot set their own bounds. Safe interruption recovery therefore depends on the documented cleanup procedure rather than automatic retry.
Pre-merge checks: 5
Check name
Status
Explanation
Title check
The title "db: index sandboxes by ID suffix and by lowercased name" follows the required format: a lowercase area ("db") followed by a colon, then a summary starting with a lowercase word ("index"). I…
Description Follows The Pr Template
The description contains all required template content: a substantive "## Summary" section; a "## Validation" section with named checks and reported results; a "## Severity and risk" section with seve…
Bug Fixes Name A Regression Test
PASS: The pull request changes only four files under db/postgres and changes no files under crates/, shim/, or ui/. It does not fix an application behavior defect, so the regression-test requirement d…
Weakened Safeguards Are Justified
PASS. The diff adds only four migration files. It does not remove tests, assertions, CI steps, or repository guards. The Squawk suppressions are justified: the up migrations explain that `CREATE INDEX…
Public-Safe Content
PASS. The title, description, commit subjects, and four added SQL files contain no credentials, private keys, internal URLs or hostnames, cloud account IDs, personal absolute paths, private tracker li…
Full details: Title check
Explanation
The title "db: index sandboxes by ID suffix and by lowercased name" follows the required format: a lowercase area ("db") followed by a colon, then a summary starting with a lowercase word ("index"). It clearly names both main changes, the ID suffix index and the lowercased name index, which match the migrations and PR objectives. It has no trailing period and no ticket IDs. Code identifiers keep their case where used, and none are needed here.
Full details: Description Follows The Pr Template
Explanation
The description contains all required template content: a substantive "## Summary" section; a "## Validation" section with named checks and reported results; a "## Severity and risk" section with severity "does not apply" and a stated risk; and an "AI assistance:" line that identifies Claude Code and the drafted content.
Full details: Bug Fixes Name A Regression Test
Explanation
PASS: The pull request changes only four files under db/postgres and changes no files under crates/, shim/, or ui/. It does not fix an application behavior defect, so the regression-test requirement does not apply.
Full details: Weakened Safeguards Are Justified
Explanation
PASS. The diff adds only four migration files. It does not remove tests, assertions, CI steps, or repository guards. The Squawk suppressions are justified: the up migrations explain that CREATE INDEX CONCURRENTLY requires a single -- no-transaction statement, cannot set its own timeouts, and intentionally omits IF NOT EXISTS so failed builds remain visible (lines 18-36 of both up files). The pull request description also documents concurrent rollback drops.
Full details: Public-Safe Content
Explanation
PASS. The title, description, commit subjects, and four added SQL files contain no credentials, private keys, internal URLs or hostnames, cloud account IDs, personal absolute paths, private tracker links, real cluster names, incident details, or undisclosed vulnerabilities. The reported measurements are explicitly for synthetic rows in a disposable PostgreSQL instance, not production data.
Recent review info
Automatic approval policy
Commit: 81164ddc21c4.
Setting
Effective value
Source
Automatic approvals
Disabled
Override 4
Merge risk
Low
Override 2
Security risk
Not set
Global configuration
Matching overrides (numbered as in the resolved configuration):
Any matching disabled override withholds approval, regardless of risk. Otherwise, the strictest explicitly set limit wins for each score. Limits omitted by all matching overrides inherit the global configuration.
Not set means no limit was configured after inheritance. Use @coderabbitai configuration to inspect the resolved settings and their sources.
The migration comments said the ID-suffix and lowercased-name indexes
add no work to updates. They keep an otherwise-HOT update HOT while their
source columns are unchanged, but an update that is not HOT, such as one
changing lifecycle, inserts into both.
The reason will be displayed to describe this comment to others. Learn more.
Approving. The index expressions match #1523, and the name-length cap avoids failing the build on older long names. The up/down SQL passed on disposable data. Deploy this before #1523.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two expression indexes on
sandboxesfor the admin sandbox list'ssearch(#1523). Nothing reads them until that change merges.idx_sandboxes_id_suffixonreverse(replace(id::text, '-', '')) COLLATE "C". The console labels a sandbox with the last eight hex digits of its UUIDv7, because the leading digits are the creation timestamp. Reversed, the IDs that end in a given suffix form one key range.idx_sandboxes_name_loweronleft(lower(name), 256) COLLATE "C": the names that start with a text, ignoring case, form one key range.COLLATE "C"makes each range a byte range under any database collation. The statements read it with plain>=and<, which a generic plan can use as index conditions. ALIKEprefix withtext_pattern_opsis indexable only when the pattern is a plan-time constant, so a cached generic plan would scan the table.The name key keeps the first 256 characters, at most 1 kB. The API caps new names at 256 bytes, but the column is unconstrained. A btree entry over about 2.7 kB would fail the build on an older, longer name and leave an INVALID index.
Each index is built
CONCURRENTLYin its own single-statement-- no-transactionmigration, following db/README.md and20261102223448_public_operations_org_state_index. They land alone because the migration guard rejects migrations combined with application changes.Validation
At head 81164dd:
scripts/check-postgres-migrations-squawk.sh: no issues in the 2 up migrations (this also runs the partial-index and UUIDv4 checks).scripts/check-migration-versions.shandscripts/check-postgres-migrations-isolated.shpass.On a fresh local Kind stack deployed from this head (81164dd), the migration job applied both indexes. The stack then served the console checks against an API without the search (#1524). Every database that #1523's
cargo nextest run -p pplx-space-api -p pplx-space-cluster-pool -p pplx-space-internal-api-server --all-featurescreates applies them: 3074 passed at #1523's head 6f5e710.Build cost: in a disposable Postgres 16 with 300,000 synthetic rows,
CREATE INDEX CONCURRENTLYtook 0.31 s and 0.28 s. The indexes are 17 MB and 9 MB, beside a 22 MB table. The plans that use them are in #1523.Severity and risk
Severity: does not apply. This enables the search in #1523.
Risk:
idnornameand is otherwise HOT stays HOT. An update that is not HOT anyway also inserts an entry into both indexes; one that changes an indexed column such aslifecycle, for example.Rollout
Deploy this before #1523, which reads these indexes; until then they only cost writes. As with any migration, an image that does not bundle these versions refuses a database that has applied them, so rolling back below this release means running the down migrations first.
Stack
AI assistance: Claude Code drafted the migrations, their comments and this description; the measurements were run as described.
🤖 Generated with Claude Code