Skip to content

perf(projects): store the parent of top-level projects as null - #3726

Merged
kolaente merged 2 commits into
mainfrom
perf/projects-parent-id-null-index
Sep 4, 2026
Merged

perf(projects): store the parent of top-level projects as null#3726
kolaente merged 2 commits into
mainfrom
perf/projects-parent-id-null-index

Conversation

@tink-bot

@tink-bot tink-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Recursive step of project access CTE joins parent_project_id against project id, which implies NOT NULL. Root projects stored as 0 sit in same index as real children, so index scan walks every project row instead of children only.

Migration nulls out parent_project_id = 0 and adds partial index ON projects (parent_project_id) WHERE parent_project_id IS NOT NULL. Write paths use xorm Nullable, so 0 sentinel lands as NULL. Struct keeps 0: JSON responses, webhooks and events unchanged, AfterLoad still maps NULL back to 0.

MySQL has no partial indexes — keeps existing full index, gets semantic change but no index win. Full index kept on all dialects so parent_project_id IS NULL lookups stay indexed.

How to verify

  1. Create a new top-level project through the API or UI.
  2. Query the database: SELECT id, parent_project_id FROM projects WHERE id = <new id>.
  3. Expected: the stored parent_project_id is NULL, while GET /api/v1/projects/<new id> still returns "parent_project_id": 0.
  4. Before this PR: the column stored 0.

Second flow, for detaching:

  1. Take a project that has a parent and update it with {"parent_project_id": 0} (needs admin permission on the project).
  2. Expected: the request succeeds, the response shows "parent_project_id": 0, the project appears at the top level, and the stored column is NULL.

Third flow, for existing installs:

  1. Run the migration against a database that already has root projects with parent_project_id = 0.
  2. Expected: those rows now hold NULL, projects with a real parent are untouched, and every project still shows up with its usual permissions in the project list.

A nil map value now renders IS NULL instead of an Eq that never matches.
Column names are quoted since the builder does not do it.
The recursive step of the project access CTE joins parent_project_id
against a project id, which implies NOT NULL. Storing root projects as
NULL instead of 0 lets a partial index cover only real children, so the
index scan walks a fraction of the rows.

MySQL has no partial indexes and keeps the existing full index.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Preview Deployment

Preview deployments for this PR are available at:

URL Tag Commit
https://pr-3726.preview.vikunja.dev ghcr.io/go-vikunja/vikunja:pr-3726 latest
https://sha-66f250b2608e43f3b71b6b3d64811e070bc1d95e.preview.vikunja.dev ghcr.io/go-vikunja/vikunja:sha-66f250b2608e43f3b71b6b3d64811e070bc1d95e 66f250b

The preview environment will start automatically on first visit. Subsequent pushes to this PR will update the pr-3726 image — the preview picks up the new version on restart. The per-commit URLs point to a specific version and will not change.

Run locally with Docker
docker pull ghcr.io/go-vikunja/vikunja:pr-3726
docker run -p 3456:3456 ghcr.io/go-vikunja/vikunja:pr-3726

Last updated for commit 66f250b

@github-actions github-actions Bot added area/database Database engine behavior, schema issues, cross-engine DB bugs area/migrations Database schema migrations (NOT data import) area/projects Project CRUD, sub-projects, project tree, archiving, templates concern/performance Slow, laggy, or scaling issues db/mysql MySQL/MariaDB engine-specific issue labels Sep 3, 2026
@tink-bot
tink-bot marked this pull request as ready for review September 3, 2026 22:03
@kolaente
kolaente merged commit fc78cd3 into main Sep 4, 2026
43 checks passed
@kolaente
kolaente deleted the perf/projects-parent-id-null-index branch September 4, 2026 07:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/database Database engine behavior, schema issues, cross-engine DB bugs area/migrations Database schema migrations (NOT data import) area/projects Project CRUD, sub-projects, project tree, archiving, templates concern/performance Slow, laggy, or scaling issues db/mysql MySQL/MariaDB engine-specific issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants