Skip to content

feat(BA-8874): purge dangling personal projects after the retention period - #16450

Open
HyeockJinKim wants to merge 1 commit into
mainfrom
BA-8874-project-retention
Open

HyeockJinKim wants to merge 1 commit into
mainfrom
BA-8874-project-retention

Conversation

@HyeockJinKim

@HyeockJinKim HyeockJinKim commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Mark a user's personal project deleted when the user is purged, so it is collected after the retention period instead of outliving the account forever.
  • Add the projects retention category. A backfill migration marks projects already dangling, and a seed migration adds the policy row on upgraded databases.
  • Purge dangling personal projects through the project repository in the leader sweep, clearing their sessions, kernels, deployments and vfolders.

Test plan

  • pants test --changed-since=origin/main: backfill and seed migrations, dangling project purge (sessions, kernels, vfolders, per-project failure isolation), user purge marking, leader task and wiring.

Resolves BA-8874

🤖 Generated with Claude Code


📚 Documentation preview 📚: https://sorna--16450.org.readthedocs.build/en/16450/


📚 Documentation preview 📚: https://sorna-ko--16450.org.readthedocs.build/ko/16450/

@HyeockJinKim
HyeockJinKim requested a review from a team as a code owner November 4, 2026 06:39
@github-actions github-actions Bot added size:XL 500~ LoC comp:manager Related to Manager component comp:common Related to Common component require:db-migration Automatically set when alembic migrations are added or updated labels Nov 4, 2026
HyeockJinKim added a commit that referenced this pull request Nov 4, 2026
@github-actions github-actions Bot added area:docs Documentations size:L 100~500 LoC and removed size:XL 500~ LoC labels Nov 4, 2026
…eriod

Co-Authored-By: Claude Code <noreply@anthropic.com>
@HyeockJinKim
HyeockJinKim force-pushed the BA-8874-project-retention branch from 91d001b to a323dc8 Compare November 4, 2026 14:04

@lablup-octodog lablup-octodog 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.

Summary

This PR fixes personal projects outliving a purged user by collecting them through the retention path, and the direction fits the existing retention structure. The user purge marks the personal project deleted so the FK empties creator_id, and the leader sweep then purges dangling personal projects past the projects policy period through the project repository, with backfill and seed migrations and tests. However, the new policy ships with enabled: false, so no collection happens until an admin enables it.

Findings

Should fix

  • seeds/manager/retention_policy/defaults.yaml:39: The new projects policy is seeded with enabled: false in the migration, the seed file and both fixtures, so until an admin enables it a purged user's personal project is never collected after the period and stays forever, and the staging 1-day period the issue expects is absent from the seed tree.
  • src/ai/backend/manager/leader/tasks/retention_sweep.py:68: The leader task passes only retention.batch_size to the project purge and never consults retention.per_tick_budget, so a configured per-tick budget does not bound project cleanup: each tick may hard-purge up to batch_size projects, each deleting its sessions, kernels and vfolders.
  • src/ai/backend/manager/models/alembic/versions/b7d2a91c4e58_seed_projects_retention_policy.py:28: The seed migration inserts the row without id and lets the database generate one, while the seed file and both fixtures pin the same row to b1e5f4a0-0009-...; applying the seed files to a database that already ran this migration inserts a second projects row and collides on uq_retention_policies_category unless the seeder dedupes by category.

Checks

Of 40 checks, 29 passed, 4 were skipped and 7 are still running (typecheck and scenario shards 0-5); none failed.

Coverage

All 38 changed files' patches were retrieved, and the project, retention and user repositories, the project/retention rows, the migrations and the new tests were read in full at the head commit. The truncated patches for test_composer, test_leader_election, test_retention_policy_row and test_retention_repository were not read, the seed loader and install path were not read so the seed-id mismatch could not be confirmed as a real failure, and the 20-call read_file cap prevented comparing against the base commit.

- id: b1e5f4a0-0009-4c10-8a01-000000000009
category: projects
retention_period_days: 30
enabled: false

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.

The projects policy ships with enabled: false here, in the install fixtures and in seed migration b7d2a91c4e58, and RetentionSweepTask._sweep_dangling_personal_projects() returns as soon as enabled_policy() finds nothing. On a fresh or upgraded deployment a purged user's personal project is therefore still kept forever until an admin turns the policy on, and the issue's staging 1-day period has no override in the seed tree. If collection is meant to take effect out of the box, the policy needs a seeded enabled state (and a staging override).

result = await self._project_repository.purge_dangling_personal_projects(
threshold,
self._config_provider.config.retention.batch_size,
)

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.

Only batch_size is passed here; retention.per_tick_budget, which RetentionDBSource.sweep() uses to stop a tick once enough rows were deleted, is not consulted. With a budget set, one tick can still purge up to batch_size dangling projects, each deleting its sessions, kernels and vfolders (with storage RPCs), so the budget no longer bounds the leader's work. Applying the remaining budget here, or stopping after it is spent, would keep the tick bounded.

depends_on = None

_INSERT_POLICY = sa.text("""
INSERT INTO retention_policies (category, retention_period, enabled)

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.

The migration inserts the row without id, so uuid_generate_v7() assigns a fresh one, while seeds/manager/retention_policy/defaults.yaml and both fixtures pin this row to b1e5f4a0-0009-4c10-8a01-000000000009. If the seed files are applied to a database that already ran this migration, the pinned-id insert adds a second projects row and hits uq_retention_policies_category (unless the seeder dedupes by category). Passing the same fixed id would keep the migration row and the seed row identical.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:common Related to Common component comp:manager Related to Manager component require:db-migration Automatically set when alembic migrations are added or updated size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants