fix(db): add indexes on documents.sender_id and document_comments.author_id (#470) #491

Merged
marcel merged 1 commits from fix/issue-470-fk-indexes into main 2026-05-09 16:34:26 +02:00
Owner

Summary

Closes #470.

  • Flyway migration V62__index_fk_columns.sql adds idx_documents_sender_id on documents(sender_id) and idx_comments_author_id on document_comments(author_id)
  • Both indexes use CREATE INDEX IF NOT EXISTS (idempotent, safe on existing data)
  • Two pg_indexes assertions added to MigrationIntegrationTest — confirmed RED before migration, GREEN after

Why

Both FK columns were missing B-tree indexes. sender_id drives the Korrespondenz-Überblick queries on /persons/[id] and the Briefwechsel timeline. author_id drives admin user-detail and future contributor-profile queries. PostgreSQL can only use the FK constraint for referential integrity — it does not auto-create an index.

Test plan

  • MigrationIntegrationTest#v62_idx_documents_sender_id_exists — RED before V62, GREEN after
  • MigrationIntegrationTest#v62_idx_comments_author_id_exists — RED before V62, GREEN after
  • Full backend suite: 1548 tests, 0 failures
## Summary Closes #470. - Flyway migration `V62__index_fk_columns.sql` adds `idx_documents_sender_id` on `documents(sender_id)` and `idx_comments_author_id` on `document_comments(author_id)` - Both indexes use `CREATE INDEX IF NOT EXISTS` (idempotent, safe on existing data) - Two `pg_indexes` assertions added to `MigrationIntegrationTest` — confirmed RED before migration, GREEN after ## Why Both FK columns were missing B-tree indexes. `sender_id` drives the Korrespondenz-Überblick queries on `/persons/[id]` and the Briefwechsel timeline. `author_id` drives admin user-detail and future contributor-profile queries. PostgreSQL can only use the FK constraint for referential integrity — it does not auto-create an index. ## Test plan - [x] `MigrationIntegrationTest#v62_idx_documents_sender_id_exists` — RED before V62, GREEN after - [x] `MigrationIntegrationTest#v62_idx_comments_author_id_exists` — RED before V62, GREEN after - [x] Full backend suite: 1548 tests, 0 failures
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: Approved

What I checked

  • TDD evidence: PR description confirms tests were confirmed RED before migration, GREEN after — exactly the discipline I require. The failure messages (expected: 1 but was: 0) prove the tests were meaningfully red, not vacuously passing.
  • SQL comments explain the WHY: Each CREATE INDEX is annotated with the specific query path it speeds up (/persons/[id] Korrespondenz-Überblick (#306), /briefwechsel, admin user detail). No "what" comments — only "why" comments. This is the standard.
  • CREATE INDEX IF NOT EXISTS: Idempotent — safe on re-run, safe against duplicate migration mistakes.
  • Test assertions: queryForObject(..., Integer.class) with assertThat(count).isEqualTo(1) — clean, precise, and readable.
  • No dead code, no unnecessary complexity: 8 lines of SQL, 18 lines of test. As lean as a migration PR should be.

Minor observation (no blocker)

Test naming follows the v{version}_{description} convention already established in MigrationIntegrationTest — consistent with v30_partialUniqueIndex_*, v39_parentId_*, etc. No changes needed.

LGTM. Ship it.

## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ✅ Approved** ### What I checked - **TDD evidence**: PR description confirms tests were confirmed RED before migration, GREEN after — exactly the discipline I require. The failure messages (`expected: 1 but was: 0`) prove the tests were meaningfully red, not vacuously passing. - **SQL comments explain the WHY**: Each `CREATE INDEX` is annotated with the specific query path it speeds up (`/persons/[id] Korrespondenz-Überblick (#306)`, `/briefwechsel`, admin user detail). No "what" comments — only "why" comments. This is the standard. - **`CREATE INDEX IF NOT EXISTS`**: Idempotent — safe on re-run, safe against duplicate migration mistakes. - **Test assertions**: `queryForObject(..., Integer.class)` with `assertThat(count).isEqualTo(1)` — clean, precise, and readable. - **No dead code, no unnecessary complexity**: 8 lines of SQL, 18 lines of test. As lean as a migration PR should be. ### Minor observation (no blocker) Test naming follows the `v{version}_{description}` convention already established in `MigrationIntegrationTest` — consistent with `v30_partialUniqueIndex_*`, `v39_parentId_*`, etc. No changes needed. LGTM. Ship it.
Author
Owner

🏛️ Markus Keller — Application Architect

Verdict: Approved

What I checked

"Push logic down" principle: Adding indexes at the DB layer rather than trying to optimize at the application layer is correct. PostgreSQL does not auto-create indexes for foreign keys — a common misconception. This PR closes that gap.

Migration versioning: V62 follows V61 (add_idx_documents_updated_at) sequentially — no gaps, no conflicts. Flyway will apply cleanly.

Documentation gate: My gate table requires docs/architecture/db/db-orm.puml and db-relationships.puml updates only when tables or columns are added, removed, or renamed. This migration adds only indexes — no topology changes to the schema. No diagram updates required. ✓

CREATE INDEX IF NOT EXISTS: Idempotent DDL — safe for Flyway's checksum model and for environments where the index might already exist.

No architectural concerns: Both FK columns (sender_id, author_id) are already documented in the domain model. The indexes reinforce existing relationships, not new ones.

Suggestion (non-blocker)

The comment on idx_comments_author_id includes "(future) contributor profile" alongside the present concrete reason ("admin user detail"). Keep migration comments anchored to present, concrete query paths — future use cases may never materialize and comments become stale. The concrete present reason is sufficient justification on its own.

Overall: well-scoped, correctly sequenced, appropriate documentation footprint. No concerns.

## 🏛️ Markus Keller — Application Architect **Verdict: ✅ Approved** ### What I checked **"Push logic down" principle**: Adding indexes at the DB layer rather than trying to optimize at the application layer is correct. PostgreSQL does not auto-create indexes for foreign keys — a common misconception. This PR closes that gap. **Migration versioning**: V62 follows V61 (`add_idx_documents_updated_at`) sequentially — no gaps, no conflicts. Flyway will apply cleanly. **Documentation gate**: My gate table requires `docs/architecture/db/db-orm.puml` and `db-relationships.puml` updates only when tables or columns are added, removed, or renamed. This migration adds only indexes — no topology changes to the schema. No diagram updates required. ✓ **`CREATE INDEX IF NOT EXISTS`**: Idempotent DDL — safe for Flyway's checksum model and for environments where the index might already exist. **No architectural concerns**: Both FK columns (`sender_id`, `author_id`) are already documented in the domain model. The indexes reinforce existing relationships, not new ones. ### Suggestion (non-blocker) The comment on `idx_comments_author_id` includes "(future) contributor profile" alongside the present concrete reason ("admin user detail"). Keep migration comments anchored to present, concrete query paths — future use cases may never materialize and comments become stale. The concrete present reason is sufficient justification on its own. Overall: well-scoped, correctly sequenced, appropriate documentation footprint. No concerns.
Author
Owner

🚀 Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

What I checked

Flyway migration versioning: V62 follows V61 without gaps. Flyway will pick this up cleanly on next application startup — no manual intervention required.

Idempotent DDL: CREATE INDEX IF NOT EXISTS means this migration is safe even if the index was created manually against a staging instance. Flyway won't let you re-run it anyway, but the guard is good practice and signals the intent.

Testcontainers: Tests run against real PostgreSQL via PostgresContainerConfigpg_catalog.pg_indexes is a PostgreSQL system catalog that doesn't exist in H2. These tests would fail meaningfully on H2, which is exactly the right property.

No infrastructure changes: No Docker Compose, no CI pipeline, no Caddy config touched. The migration runs automatically on application startup — zero operational overhead to deploy.

Note on locking (non-blocker at current scale)

CREATE INDEX acquires a ShareLock on the target table, briefly blocking writes. At family-archive data volumes (hundreds to low thousands of rows) the lock window is sub-millisecond. If documents ever grows to 100k+ rows, consider switching to CREATE INDEX CONCURRENTLY to avoid any write-blocking window. This is not a concern today.

LGTM for current scale.

## 🚀 Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** ### What I checked **Flyway migration versioning**: V62 follows V61 without gaps. Flyway will pick this up cleanly on next application startup — no manual intervention required. **Idempotent DDL**: `CREATE INDEX IF NOT EXISTS` means this migration is safe even if the index was created manually against a staging instance. Flyway won't let you re-run it anyway, but the guard is good practice and signals the intent. **Testcontainers**: Tests run against real PostgreSQL via `PostgresContainerConfig` — `pg_catalog.pg_indexes` is a PostgreSQL system catalog that doesn't exist in H2. These tests would fail meaningfully on H2, which is exactly the right property. **No infrastructure changes**: No Docker Compose, no CI pipeline, no Caddy config touched. The migration runs automatically on application startup — zero operational overhead to deploy. ### Note on locking (non-blocker at current scale) `CREATE INDEX` acquires a `ShareLock` on the target table, briefly blocking writes. At family-archive data volumes (hundreds to low thousands of rows) the lock window is sub-millisecond. If `documents` ever grows to 100k+ rows, consider switching to `CREATE INDEX CONCURRENTLY` to avoid any write-blocking window. This is not a concern today. LGTM for current scale.
Author
Owner

🔐 Nora "NullX" Steiner — Application Security Engineer

Verdict: Approved

What I checked

This PR adds two B-tree indexes to existing tables. No new endpoints, no authentication changes, no new data exposure surface, no new user input paths.

No SQL injection risk: The migration SQL is static DDL — no user input, no parameterization needed.

No authorization bypass: PostgreSQL indexes are transparent to the access control system. They do not change who can query what, only how fast the query executes.

No data exposure: sender_id and author_id are FK columns pointing to already-accessible entities. The indexes accelerate reads of columns already reachable under the existing permission model — they do not widen the accessible data set.

CREATE INDEX IF NOT EXISTS: Prevents a migration failure from leaving the table in an inconsistent locked state.

Observation (future concern, not a blocker)

The PR description mentions "future contributor profile" as a potential use case for idx_comments_author_id. If that feature involves exposing comment authorship to other users (e.g., a public contributor profile page showing who wrote what), ensure the permission boundary is enforced at the API/service layer at that time — not assumed to be handled by the query layer alone. That's a design concern for that future PR.

No security concerns for this PR. LGTM.

## 🔐 Nora "NullX" Steiner — Application Security Engineer **Verdict: ✅ Approved** ### What I checked This PR adds two B-tree indexes to existing tables. No new endpoints, no authentication changes, no new data exposure surface, no new user input paths. **No SQL injection risk**: The migration SQL is static DDL — no user input, no parameterization needed. **No authorization bypass**: PostgreSQL indexes are transparent to the access control system. They do not change who can query what, only how fast the query executes. **No data exposure**: `sender_id` and `author_id` are FK columns pointing to already-accessible entities. The indexes accelerate reads of columns already reachable under the existing permission model — they do not widen the accessible data set. **`CREATE INDEX IF NOT EXISTS`**: Prevents a migration failure from leaving the table in an inconsistent locked state. ### Observation (future concern, not a blocker) The PR description mentions "future contributor profile" as a potential use case for `idx_comments_author_id`. If that feature involves exposing comment authorship to other users (e.g., a public contributor profile page showing who wrote what), ensure the permission boundary is enforced at the API/service layer at that time — not assumed to be handled by the query layer alone. That's a design concern for that future PR. No security concerns for this PR. LGTM.
Author
Owner

🧪 Sara Holt — QA Engineer & Test Automation Specialist

Verdict: Approved

What I checked

Test layer choice: Integration tests using real PostgreSQL via Testcontainers — exactly right. pg_catalog.pg_indexes is a PostgreSQL system catalog; this query would not exist on H2. These tests provide real guarantee, not H2 approximation.

TDD confirmation: PR description shows the tests failed with expected: 1 but was: 0 before the migration was created, then passed after. The failure message is meaningful — not a compile error or NPE, an actual assertion failure proving the test was red for the right reason. ✓

Test naming: v62_idx_documents_sender_id_exists and v62_idx_comments_author_id_exists follow the existing convention in MigrationIntegrationTest (v30_partialUniqueIndex_*, v39_parentId_*). Consistent.

Assertion correctness: assertThat(count).isEqualTo(1) — index names are unique within a PostgreSQL schema, so COUNT(*) = 1 iff the index exists with that exact name. Precise.

Coverage: Two behaviors, two separate tests. No bundling. ✓

Full suite: 1548 tests, 0 failures on the full backend suite. ✓

Minor suggestion (non-blocker)

The existing naming pattern in this file tends toward behavior descriptions: v23_polygonCheckConstraint_rejectsNonQuadrilateral. The new names (v62_idx_documents_sender_id_exists) describe state rather than behavior. Not wrong — just slightly inconsistent with the rest of the file. Could be v62_documentsTable_hasIndexOnSenderId to follow the pattern more closely, but this is cosmetic.

Solid test coverage at the right layer. LGTM.

## 🧪 Sara Holt — QA Engineer & Test Automation Specialist **Verdict: ✅ Approved** ### What I checked **Test layer choice**: Integration tests using real PostgreSQL via Testcontainers — exactly right. `pg_catalog.pg_indexes` is a PostgreSQL system catalog; this query would not exist on H2. These tests provide real guarantee, not H2 approximation. **TDD confirmation**: PR description shows the tests failed with `expected: 1 but was: 0` before the migration was created, then passed after. The failure message is meaningful — not a compile error or NPE, an actual assertion failure proving the test was red for the right reason. ✓ **Test naming**: `v62_idx_documents_sender_id_exists` and `v62_idx_comments_author_id_exists` follow the existing convention in `MigrationIntegrationTest` (`v30_partialUniqueIndex_*`, `v39_parentId_*`). Consistent. **Assertion correctness**: `assertThat(count).isEqualTo(1)` — index names are unique within a PostgreSQL schema, so `COUNT(*) = 1` iff the index exists with that exact name. Precise. **Coverage**: Two behaviors, two separate tests. No bundling. ✓ **Full suite**: 1548 tests, 0 failures on the full backend suite. ✓ ### Minor suggestion (non-blocker) The existing naming pattern in this file tends toward behavior descriptions: `v23_polygonCheckConstraint_rejectsNonQuadrilateral`. The new names (`v62_idx_documents_sender_id_exists`) describe state rather than behavior. Not wrong — just slightly inconsistent with the rest of the file. Could be `v62_documentsTable_hasIndexOnSenderId` to follow the pattern more closely, but this is cosmetic. Solid test coverage at the right layer. LGTM.
Author
Owner

📋 Elicit — Requirements Engineer

Verdict: Approved

What I checked

Reviewed the implementation against the requirements of Issue #470 (missing B-tree indexes on FK columns).

Traceability: Both indexes are traced to concrete, present use cases:

  • idx_documents_sender_id/persons/[id] Korrespondenz-Überblick (#306) and /briefwechsel
  • idx_comments_author_id → admin user detail

Acceptance criteria: The PR test plan confirms both pg_indexes assertions were RED before the migration and GREEN after. This is automated, repeatable, and deterministic — not manual validation. The "done" condition is verifiable in CI on every future run.

Scope containment: Two files changed (migration SQL + integration test). No scope creep. The PR does exactly what the issue asked for — no more, no less.

Requirements satisfied: Issue #470 asked for indexes to be added; the indexes are created and covered by regression tests. ✓

No requirements gaps. The fix is precisely scoped and traceable to the originating issue.

## 📋 Elicit — Requirements Engineer **Verdict: ✅ Approved** ### What I checked Reviewed the implementation against the requirements of Issue #470 (missing B-tree indexes on FK columns). **Traceability**: Both indexes are traced to concrete, present use cases: - `idx_documents_sender_id` → `/persons/[id] Korrespondenz-Überblick (#306)` and `/briefwechsel` - `idx_comments_author_id` → admin user detail **Acceptance criteria**: The PR test plan confirms both `pg_indexes` assertions were RED before the migration and GREEN after. This is automated, repeatable, and deterministic — not manual validation. The "done" condition is verifiable in CI on every future run. **Scope containment**: Two files changed (migration SQL + integration test). No scope creep. The PR does exactly what the issue asked for — no more, no less. **Requirements satisfied**: Issue #470 asked for indexes to be added; the indexes are created and covered by regression tests. ✓ No requirements gaps. The fix is precisely scoped and traceable to the originating issue.
Author
Owner

🎨 Leonie Voss — UX Designer & Accessibility Strategist

Verdict: Approved

What I checked

This PR contains only a Flyway database migration and a Java integration test. There are no frontend components, no Svelte files, no layout changes, no color or typography decisions, no interactive elements.

From a UX perspective, the indirect effect is positive: the indexes on sender_id and author_id will reduce query latency on the Korrespondenz-Überblick and Briefwechsel views — both data-heavy pages. Faster backend response times improve perceived performance for all users, including the 60+ audience on older hardware where every millisecond counts.

No accessibility concerns, no touch target issues, no responsive design impact, no brand compliance issues to review.

LGTM.

## 🎨 Leonie Voss — UX Designer & Accessibility Strategist **Verdict: ✅ Approved** ### What I checked This PR contains only a Flyway database migration and a Java integration test. There are no frontend components, no Svelte files, no layout changes, no color or typography decisions, no interactive elements. From a UX perspective, the indirect effect is positive: the indexes on `sender_id` and `author_id` will reduce query latency on the Korrespondenz-Überblick and Briefwechsel views — both data-heavy pages. Faster backend response times improve perceived performance for all users, including the 60+ audience on older hardware where every millisecond counts. No accessibility concerns, no touch target issues, no responsive design impact, no brand compliance issues to review. LGTM.
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: ⚠️ Approved with concerns

Blockers

  • mergeable: false — PR has conflicts with main. Rebase needed before this can merge. Most likely conflict is in MigrationIntegrationTest.java since it's frequently modified. Run git rebase main on fix/issue-470-fk-indexes to resolve.

Suggestions

  • The (future) contributor profile note in the SQL comment introduces forward-looking justification for an index that isn't yet needed. Comments in migration files should describe present query patterns. Either remove the parenthetical or open a linked issue for the contributor profile feature so the dependency is tracked.

  • The comments say "Speeds up..." (what). Better to frame as why: -- FK column without index; sequential scan on sender-filter queries, see #306. That gives future readers the actual diagnosis, not just the remedy.

What's Good

  • CREATE INDEX IF NOT EXISTS — idempotent, safe on any existing data, safe for re-runs.
  • TDD followed: PR description confirms RED before V62, GREEN after.
  • Both indexes tested independently — one test, one reason to fail.
  • Test names read as sentences: v62_idx_documents_sender_id_exists.
## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ⚠️ Approved with concerns** ### Blockers - **`mergeable: false`** — PR has conflicts with `main`. Rebase needed before this can merge. Most likely conflict is in `MigrationIntegrationTest.java` since it's frequently modified. Run `git rebase main` on `fix/issue-470-fk-indexes` to resolve. ### Suggestions - The `(future) contributor profile` note in the SQL comment introduces forward-looking justification for an index that isn't yet needed. Comments in migration files should describe *present* query patterns. Either remove the parenthetical or open a linked issue for the contributor profile feature so the dependency is tracked. - The comments say "Speeds up..." (what). Better to frame as why: `-- FK column without index; sequential scan on sender-filter queries, see #306`. That gives future readers the actual diagnosis, not just the remedy. ### What's Good - `CREATE INDEX IF NOT EXISTS` — idempotent, safe on any existing data, safe for re-runs. - TDD followed: PR description confirms RED before V62, GREEN after. - Both indexes tested independently — one test, one reason to fail. - Test names read as sentences: `v62_idx_documents_sender_id_exists`. ✅
Author
Owner

🏛️ Markus Keller — Application Architect

Verdict: ⚠️ Approved with concerns

Blockers

  • mergeable: false — rebase onto main required. Until then the migration sequence can't be verified clean.

Architecture Notes

  • This is exactly the right place to enforce performance: push it to the database layer, not the application. No architectural objection.
  • Flyway versioned migration — correct. V62 slots into the sequence without ambiguity.
  • Documentation check: no docs/architecture/db/db-orm.puml or db-relationships.puml update needed — indexes are not part of the entity/relationship diagrams, which model tables and FKs, not index definitions. No doc debt here.

One Thing to Note (non-blocker)

CREATE INDEX (non-concurrent) takes a ShareLock on the table while it builds, blocking concurrent writes for the duration. For the Familienarchiv's data volume this is completely acceptable at deploy time. If the documents table ever reaches hundreds of thousands of rows and you need zero-downtime deploys, switch to CREATE INDEX CONCURRENTLY — but that requires marking the migration @NonTransactional in Flyway and can't share a transaction with other DDL. File the trade-off in a comment when that day comes; don't pre-optimise now.

## 🏛️ Markus Keller — Application Architect **Verdict: ⚠️ Approved with concerns** ### Blockers - **`mergeable: false`** — rebase onto `main` required. Until then the migration sequence can't be verified clean. ### Architecture Notes - This is exactly the right place to enforce performance: push it to the database layer, not the application. No architectural objection. - Flyway versioned migration — correct. `V62` slots into the sequence without ambiguity. - **Documentation check**: no `docs/architecture/db/db-orm.puml` or `db-relationships.puml` update needed — indexes are not part of the entity/relationship diagrams, which model tables and FKs, not index definitions. ✅ No doc debt here. ### One Thing to Note (non-blocker) `CREATE INDEX` (non-concurrent) takes a `ShareLock` on the table while it builds, blocking concurrent writes for the duration. For the Familienarchiv's data volume this is completely acceptable at deploy time. If the `documents` table ever reaches hundreds of thousands of rows and you need zero-downtime deploys, switch to `CREATE INDEX CONCURRENTLY` — but that requires marking the migration `@NonTransactional` in Flyway and can't share a transaction with other DDL. File the trade-off in a comment when that day comes; don't pre-optimise now.
Author
Owner

🔒 Nora "NullX" Steiner — Application Security Engineer

Verdict: Approved

Checked the full diff. No security concerns.

  • Migration SQL is static — no user input, no injection surface.
  • CREATE INDEX IF NOT EXISTS is a DDL statement with no runtime data handling.
  • Tests query pg_catalog.pg_indexes with hardcoded literals — no parameterisation needed, no risk.
  • No @RequirePermission changes, no auth surface touched.
  • No secrets, no credentials, no external calls introduced.

Clean from a security perspective. Rebase to unblock CI.

## 🔒 Nora "NullX" Steiner — Application Security Engineer **Verdict: ✅ Approved** Checked the full diff. No security concerns. - Migration SQL is static — no user input, no injection surface. - `CREATE INDEX IF NOT EXISTS` is a DDL statement with no runtime data handling. - Tests query `pg_catalog.pg_indexes` with hardcoded literals — no parameterisation needed, no risk. - No `@RequirePermission` changes, no auth surface touched. - No secrets, no credentials, no external calls introduced. Clean from a security perspective. Rebase to unblock CI.
Author
Owner

🧪 Sara Holt — QA Engineer

Verdict: ⚠️ Approved with concerns

Blockers

  • mergeable: false — CI hasn't run on this branch against current main. The 1548-test green mentioned in the PR description is from a local run before main diverged. Need a rebase to confirm the full suite is still green in CI.

What's Good

  • Tests use pg_catalog.pg_indexes against a real Testcontainers PostgreSQL instance — correct layer for schema-level assertions. Would silently fail with H2.
  • One test per index — one reason to fail per test run.
  • Test names are behaviour-first sentences: v62_idx_documents_sender_id_exists.
  • Asserting count == 1 (not > 0) ensures exactly one index with that name exists — guards against accidental duplicates.

Suggestion (non-blocker)

The tests verify index existence after all migrations have run, but don't isolate that specifically V62 created them (a prior migration could have added the same-named index and V62 would still pass). That's acceptable here because IF NOT EXISTS makes the idempotency intentional, but worth noting if the test name is ever read as "V62 created this index."

## 🧪 Sara Holt — QA Engineer **Verdict: ⚠️ Approved with concerns** ### Blockers - **`mergeable: false`** — CI hasn't run on this branch against current `main`. The 1548-test green mentioned in the PR description is from a local run before main diverged. Need a rebase to confirm the full suite is still green in CI. ### What's Good - Tests use `pg_catalog.pg_indexes` against a real Testcontainers PostgreSQL instance — correct layer for schema-level assertions. Would silently fail with H2. ✅ - One test per index — one reason to fail per test run. ✅ - Test names are behaviour-first sentences: `v62_idx_documents_sender_id_exists`. ✅ - Asserting `count == 1` (not `> 0`) ensures exactly one index with that name exists — guards against accidental duplicates. ✅ ### Suggestion (non-blocker) The tests verify index existence after all migrations have run, but don't isolate that specifically V62 created them (a prior migration could have added the same-named index and V62 would still pass). That's acceptable here because `IF NOT EXISTS` makes the idempotency intentional, but worth noting if the test name is ever read as "V62 created this index."
Author
Owner

☁️ Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

Nothing to flag from a platform perspective.

  • No Docker Compose changes, no CI workflow changes, no new infrastructure.
  • Migration runs through standard Flyway startup — MigrationIntegrationTest catches any breakage in CI before it reaches the VPS.
  • CREATE INDEX IF NOT EXISTS is idempotent — safe on the production database whether or not a previous partial deploy left the index half-created.
  • No secrets, no pinned image version changes, nothing that touches the deployment pipeline.

One practical note: on the production VPS (CX32, 4 vCPU, 8 GB RAM), CREATE INDEX on documents will run during the backend container startup. At the current data volume it'll complete in milliseconds. No downtime concern.

Rebase to clear the mergeable: false flag and let CI confirm green, then this is ready to ship.

## ☁️ Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** Nothing to flag from a platform perspective. - No Docker Compose changes, no CI workflow changes, no new infrastructure. - Migration runs through standard Flyway startup — `MigrationIntegrationTest` catches any breakage in CI before it reaches the VPS. - `CREATE INDEX IF NOT EXISTS` is idempotent — safe on the production database whether or not a previous partial deploy left the index half-created. - No secrets, no pinned image version changes, nothing that touches the deployment pipeline. One practical note: on the production VPS (CX32, 4 vCPU, 8 GB RAM), `CREATE INDEX` on `documents` will run during the backend container startup. At the current data volume it'll complete in milliseconds. No downtime concern. Rebase to clear the `mergeable: false` flag and let CI confirm green, then this is ready to ship.
Author
Owner

📋 Elicit — Requirements Engineer

Verdict: Approved

Scope matches issue #470 exactly: two FK columns, two indexes, acceptance criteria met.

What's verified:

  • Issue #470 called out sender_id (documents) and author_id (document_comments) as unindexed FK columns causing sequential scans. Both are addressed, nothing extra.
  • PR description acceptance criteria (RED before V62, GREEN after for both tests) are precise and testable.
  • Query paths cited in the migration comments are traceable to existing features: Korrespondenz-Überblick, Briefwechsel, admin user detail.

One ambiguity to track:

The SQL comment for author_id includes (future) contributor profile. This references a requirement that has no corresponding Gitea issue. The index itself is harmless — author_id FK indexes are unconditionally good practice — but the justification creates an implicit dependency on a feature that doesn't exist yet. Recommend either removing (future) and justifying on current usage alone, or opening a linked issue for the contributor profile so the dependency is visible in the backlog.

## 📋 Elicit — Requirements Engineer **Verdict: ✅ Approved** Scope matches issue #470 exactly: two FK columns, two indexes, acceptance criteria met. **What's verified:** - Issue #470 called out `sender_id` (documents) and `author_id` (document_comments) as unindexed FK columns causing sequential scans. Both are addressed, nothing extra. - PR description acceptance criteria (`RED before V62, GREEN after` for both tests) are precise and testable. ✅ - Query paths cited in the migration comments are traceable to existing features: Korrespondenz-Überblick, Briefwechsel, admin user detail. ✅ **One ambiguity to track:** The SQL comment for `author_id` includes `(future) contributor profile`. This references a requirement that has no corresponding Gitea issue. The index itself is harmless — `author_id` FK indexes are unconditionally good practice — but the justification creates an implicit dependency on a feature that doesn't exist yet. Recommend either removing `(future)` and justifying on current usage alone, or opening a linked issue for the contributor profile so the dependency is visible in the backlog.
Author
Owner

🎨 Leonie Voss — UX Designer & Accessibility Strategist

Verdict: Approved

No UI changes in this PR — pure backend migration.

From a UX perspective this is a win: the /persons/[id] Korrespondenz-Überblick and the Briefwechsel timeline are query-heavy pages that our 60+ audience navigates frequently. Reducing sequential scans on sender_id directly translates to faster page loads on those views. Invisible to the interface, but the right thing to do for perceived responsiveness.

Nothing for me to flag. LGTM.

## 🎨 Leonie Voss — UX Designer & Accessibility Strategist **Verdict: ✅ Approved** No UI changes in this PR — pure backend migration. From a UX perspective this is a win: the `/persons/[id]` Korrespondenz-Überblick and the Briefwechsel timeline are query-heavy pages that our 60+ audience navigates frequently. Reducing sequential scans on `sender_id` directly translates to faster page loads on those views. Invisible to the interface, but the right thing to do for perceived responsiveness. Nothing for me to flag. LGTM.
marcel added 1 commit 2026-05-09 16:31:46 +02:00
fix(db): add indexes on documents.sender_id and document_comments.author_id
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m26s
CI / OCR Service Tests (push) Successful in 32s
CI / Backend Unit Tests (push) Failing after 3m16s
CI / Unit & Component Tests (pull_request) Failing after 4m33s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m16s
7ca44d7df1
Flyway V62 adds idx_documents_sender_id and idx_comments_author_id to speed up
FK-driven queries on the persons page and briefwechsel view. Closes #470.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel force-pushed fix/issue-470-fk-indexes from 5e3b3f9bc9 to 7ca44d7df1 2026-05-09 16:31:46 +02:00 Compare
marcel merged commit 7ca44d7df1 into main 2026-05-09 16:34:26 +02:00
marcel deleted branch fix/issue-470-fk-indexes 2026-05-09 16:34:29 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#491