Skip to content

Give expiring DynamoDB records a TTL - #53

Closed
zzkamzn wants to merge 1 commit into
aws-samples:mainfrom
zzkamzn:fix/dynamodb-retention
Closed

zzkamzn wants to merge 1 commit into
aws-samples:mainfrom
zzkamzn:fix/dynamodb-retention

Conversation

@zzkamzn

@zzkamzn zzkamzn commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Problem

The platform table has no TTL, so every record lives forever. Several record types are only useful for a short time:

  • Invocation ledger. One row per call. The portal reads only the latest 200.
  • Daily quota counters. Two rows per day plus one per active user. Only today's are read.
  • Model gateway grants. A headless invocation routed through the gateway mints one LLMTOKEN row per call (kernel_service.py). Nothing revokes it, so these rows accumulate at the call rate long after the credential has expired. AgentCore-mode revocations (LLMREVOKE) are pruned from the IAM policy but their rows stay.
  • Workspace token lookups. These are WSTOKEN rows. AgentCore sessions last at most 8 hours.
  • Service-entry async results. These are SVCINV rows, up to 200,000 characters each.
  • Terminated sessions. They are soft-deleted and hidden from every listing.

Change

  • DynamoDB TTL is enabled on a dedicated ttl attribute, in all three install paths: Terraform, CDK and deploy-cli.
  • backend/app/services/retention.py computes ttl, and each write site sets it:
Record Kept for Setting
Invocation ledger 90 days RETENTION_LEDGER_DAYS
Daily quota counters 90 days. Only the day's first increment sets it. RETENTION_USAGE_DAYS
Service-entry results 7 days RETENTION_SERVICE_INVOCATION_DAYS
Terminated sessions 30 days after termination. S3 workspaces are untouched. RETENTION_TERMINATED_SESSION_DAYS
Gateway grants (both modes) and revocations 1 day past the credential's expiry. Rotation extends it with expires_at. fixed
Workspace token lookups 2 days fixed
Audit trail, pipeline runs kept RETENTION_AUDIT_DAYS, RETENTION_PIPELINE_RUN_DAYS

0 keeps a record forever. Agents, the registry, schedules, channels, pipelines, evaluations, policy and model configuration get no ttl.

ttl is kept separate from expires_at on purpose:

  • expires_at decides whether a credential is valid. It is checked on every use and moves on rotation.
  • ttl only says when the row is no longer needed. DynamoDB never deletes before ttl, and code already rejects an expired credential, so behaviour does not change.

Only items written after this change get ttl; existing items stay. The new "Data retention" section in docs/deployment.md documents the table and the settings. No IAM changes are needed: the backend writes an extra attribute, and the deployer already has dynamodb:*.

Test plan

  • New backend/tests/test_retention.py, 7 tests:
    • Each record type carries ttl, and retention set to 0 omits it.
    • The quota counter uses if_not_exists so only the first increment sets ttl.
    • Only terminated sessions get ttl.
    • Audit records and pipeline runs are kept by default.
    • Rotation extends a grant's ttl.
  • Ran the three new update expressions against moto's DynamoDB: the quota ADD ... SET if_not_exists, the session terminate and the grant rotation. All three are valid.
  • terraform fmt -check passes on the platform module and bash -n passes on the deploy-cli script.
  • ruff reports no new findings on the changed files.

_create_run in pipeline_service.py is also touched by #52, on adjacent lines. Whichever merges second needs a trivial rebase.

This came out of an inventory of DynamoDB access patterns done for the #44 review. It is independent of #44.

🤖 Generated with Claude Code

Nothing in the platform table ever expired. The invocation ledger, daily
quota counters, service-entry results, gateway grants and revocations,
workspace token lookups and terminated sessions all grew without bound.
A headless invocation routed through the model gateway mints one grant row
per call, and nothing removed them once they expired.

The table now has DynamoDB TTL on a dedicated ttl attribute, enabled in
Terraform, CDK and deploy-cli. retention.py computes it, and each write site
sets it:
- invocation ledger: 90 days
- daily quota counters: 90 days; only the day's first increment sets it
- service-entry results: 7 days
- terminated sessions: 30 days after termination; workspace files untouched
- gateway grants (both modes) and revocations: 1 day past the credential's
  expiry; rotation extends it along with expires_at
- workspace token lookups: 2 days

The durations are backend settings (RETENTION_*_DAYS, 0 keeps forever).
Audit records and pipeline runs are kept by default. ttl is separate from
expires_at on purpose: expires_at decides validity and moves on rotation,
ttl only says when the row is no longer needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@odinwang

Copy link
Copy Markdown
Contributor

Thanks! This change is included in #66 (published together with the related platform fixes). This repository is now updated by snapshot PRs only, so closing this one.

@odinwang odinwang closed this Oct 20, 2026
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