-
Notifications
You must be signed in to change notification settings - Fork 116
Sweep leaked test databases daily with a one-day cutoff #907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a1ee009
8756735
c986038
8a0e072
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,7 +95,8 @@ AWS_ATHENA_S3_TABLES_CATALOG=s3tablescatalog/your-table-bucket | |
|
|
||
| Each test process creates its own namespace in the table bucket, named like its schema, and deletes it with any remaining tables at the end; with pytest-xdist that is each worker, not the controller. | ||
| The test identity needs `s3tables:CreateNamespace`, `s3tables:DeleteNamespace`, `s3tables:ListTables`, and `s3tables:DeleteTable` on the table bucket. | ||
| A session that stops early leaves its namespace behind; `scripts/sweep_databases.py` removes such namespaces once they are more than seven days old. | ||
| A session that stops early leaves its namespace behind; `scripts/sweep_databases.py` removes such namespaces once they are more than one day old. | ||
| It also removes the database and namespace of a session still running after one day, if they are in the account, region, and table bucket that the sweep covers. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Independent follow-up (relayed): Codex, read-only and static, on a1ee009..c986038. FINDINGS: the sentence at c986038 said the script removes a long-running local session's resources in the same account. The scheduled sweep uses one fixed region and S3 Tables bucket, so resources in another region or table bucket stay. Repaired in 8a0e072: the sentence is now limited to "the account, region, and table bucket that the sweep covers". The PR description is amended too. Self-review of the repair: CLEAN. Second independent follow-up (Codex, read-only, static) on c986038..8a0e072, covering |
||
|
|
||
| The S3 Tables tests live in `tests/pyathena/sqlalchemy/test_base.py` and `tests/pyathena/test_glue.py` and run under `just test pyathena`, not the SQLAlchemy compliance-suite commands. | ||
| Managed storage and S3 Tables tests skip when their respective optional configuration is absent. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,27 +13,30 @@ | |
| # AWS_PROFILE and AWS_DEFAULT_REGION can select the account and region. | ||
| # | ||
| # Eligible databases must exactly match a PyAthena or SQLAlchemy fixture name | ||
| # and have a creation time more than seven days old. Resource links and federated | ||
| # databases are excluded. The script completes inventory before deleting and | ||
| # rechecks eligibility and creation time immediately before each deletion. | ||
| # Only missing-database errors are ignored; other API failures stop the sweep. | ||
| # and have a creation time more than one day old, longer than a CI test session | ||
| # can run (a GitHub-hosted job stops after six hours). A local session against | ||
| # the same account that stays open longer can lose its database. Resource | ||
| # links and federated databases are excluded. The script completes inventory | ||
| # before deleting and rechecks eligibility and creation time immediately | ||
| # before each deletion. Only missing-database errors are ignored; other API | ||
| # failures stop the sweep. | ||
| # Deletion removes Glue database and table metadata, not S3 objects. | ||
| # | ||
| # With AWS_ATHENA_S3_TABLES_CATALOG set (s3tablescatalog/<table-bucket>), the | ||
| # script also sweeps that table bucket's namespaces named like PyAthena test | ||
| # schemas and more than seven days old, deleting their tables first. Test | ||
| # schemas and more than one day old, deleting their tables first. Test | ||
| # sessions create and delete such a namespace; a session that stops early | ||
| # leaves it behind. The namespace ID is rechecked before its tables are deleted | ||
| # and again before the namespace is deleted. Each table is deleted only if it | ||
| # still belongs to that namespace, at the version just read; missing tables are | ||
| # skipped. DeleteNamespace takes only a name, so an empty namespace recreated | ||
| # under the same name right after the last recheck would still be deleted. | ||
| # | ||
| # .github/workflows/database-sweep.yaml runs this script after scheduled Test | ||
| # runs complete on master, including failures and cancellations. It does not run | ||
| # after PR tests or manually dispatched tests. Manual sweep dispatch on master | ||
| # defaults to preview. The job has a 15-minute timeout; a timeout or API failure | ||
| # can leave eligible databases for a later run. | ||
| # .github/workflows/database-sweep.yaml runs this script daily on master, so | ||
| # while those runs succeed, a cancelled test run's leftovers are removed within | ||
| # about two days. Manual sweep dispatch on master defaults to preview. The job | ||
| # has a 60-minute timeout; a timeout or API failure can leave eligible databases | ||
| # for a later run. | ||
|
|
||
| import argparse | ||
| import contextlib | ||
|
|
@@ -54,6 +57,8 @@ | |
| rf"(?:{_PYATHENA_TEST_SCHEMA}|test_[0-9a-f]{{12}}(?:_test_schema(?:_2)?)?)" | ||
| ) | ||
| _TEST_NAMESPACE = re.compile(_PYATHENA_TEST_SCHEMA) | ||
| # Longer than a CI test session can run; see the module comment. | ||
| _RETENTION = timedelta(days=1) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Self-review round one (implementation behavior): CLEAN
|
||
|
|
||
|
|
||
| def _eligible(database: dict[str, Any], cutoff: datetime) -> bool: | ||
|
|
@@ -69,13 +74,21 @@ def _eligible(database: dict[str, Any], cutoff: datetime) -> bool: | |
|
|
||
|
|
||
| def sweep_databases(client: Any, catalog_id: str, *, dry_run: bool = True) -> dict[str, int]: | ||
| """Preview or delete test databases older than seven days. | ||
| """Preview or delete test databases older than one day. | ||
|
|
||
| Fixtures generate fresh database names for each session or worker. | ||
| Databases younger than seven days are retained, including concurrent CI runs. | ||
| Databases younger than one day are retained, including concurrent CI runs. | ||
| Only Glue metadata is deleted; S3 objects and child catalogs are untouched. | ||
|
|
||
| Args: | ||
| client: A boto3 Glue client. | ||
| catalog_id: The ID of the Data Catalog to sweep. | ||
| dry_run: Only count eligible databases. | ||
|
|
||
| Returns: | ||
| The numbers of eligible, deleted and skipped databases. | ||
| """ | ||
| cutoff = datetime.now(UTC) - timedelta(days=7) | ||
| cutoff = datetime.now(UTC) - _RETENTION | ||
| # Finish pagination before deleting anything from the catalog. | ||
| candidates = [ | ||
| database | ||
|
|
@@ -127,11 +140,11 @@ def _eligible_namespace(namespace: dict[str, Any], cutoff: datetime) -> bool: | |
| def sweep_s3tables_namespaces( | ||
| client: Any, table_bucket_arn: str, *, dry_run: bool = True | ||
| ) -> dict[str, int]: | ||
| """Preview or delete test S3 Tables namespaces older than seven days. | ||
| """Preview or delete test S3 Tables namespaces older than one day. | ||
|
|
||
| Test sessions create a namespace named like their schema and delete it when | ||
| they finish; this removes the ones a session left behind. Namespaces younger | ||
| than seven days are retained, including those of running sessions. | ||
| than one day are retained, including those of running sessions. | ||
|
|
||
| Args: | ||
| client: A boto3 S3 Tables client. | ||
|
|
@@ -141,7 +154,7 @@ def sweep_s3tables_namespaces( | |
| Returns: | ||
| The numbers of eligible, deleted and skipped namespaces. | ||
| """ | ||
| cutoff = datetime.now(UTC) - timedelta(days=7) | ||
| cutoff = datetime.now(UTC) - _RETENTION | ||
| # Finish pagination before deleting anything from the table bucket. | ||
| candidates = [ | ||
| namespace | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review round two (claims and operations): CLEAN
8679f04a36f6ffd7190b892c32b1938672ab502f, heada1ee0099081d5dcf78a79621fd0d4d4aeca774d4.timeout-minutes;database-sweep.yamlis the only workflow with one. The longest recent Test run, 36526628480, took 4h27m of wall-clock time including reruns, each of which is its own session.gh run list --created; 09-25 through 09-28 are from the issue. 09-22 had no runs, and 09-27 had no cancellations and left nothing.CreateTimedate.git grep).changesfilter skips the SQLAlchemy and Spark suites for this PR; only the PyAthena suite runs on Ready.