Skip to content

fix(sqlalchemy-spanner): robust session-isolated test databases and clean lifecycle management - #18179

Merged
daniel-sanche merged 1 commit into
mainfrom
refactor/sqlalchemy-spanner-clean-isolation
Aug 26, 2026
Merged

fix(sqlalchemy-spanner): robust session-isolated test databases and clean lifecycle management#18179
daniel-sanche merged 1 commit into
mainfrom
refactor/sqlalchemy-spanner-clean-isolation

Conversation

@chalmerlowe

@chalmerlowe chalmerlowe commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Problem

We recently updated our various system CI session to run in parallel across multiple cores. When running Cloud Spanner SQLAlchemy dialect test suites (both compliance and system tests) in parallel or across multiple Nox sessions, the test runs shared a single hardcoded database using a static configuration file (test.cfg). This caused race conditions and test pollution:

  • Intermittent 404 errors due to one test session's teardown dropping the database while another session was actively running tests against it.
  • Schema conflicts (400 Duplicate name in schema) when multiple test runs attempted to create identical test tables simultaneously.
  • Environment state leakage across parameterized Nox sessions due to global process-wide environment variable manipulation.

Solution

This pull request introduces session-isolated test execution for SQLAlchemy Spanner:

  1. Ephemeral Unique Databases: Each Nox session (compliance_test_14, compliance_test_20, migration, and system) now provisions its own dedicated Spanner database named with a timestamp and a 6-character random hex suffix (e.g., sp_test_1787306641_a3f2b1), ensuring test isolation and parallelization safety.
  2. Explicit Configuration Passing: Replaced global process-wide environment mutation (os.environ.*) with explicit configuration passing via command-line arguments and subprocess env dictionaries (env={"SQLALCHEMY_SPANNER_CONFIG": config_file}), preventing state bleed across runs.
  3. Modern Path Management: Adopted Python's pathlib for clean, modern file existence checks and unlinking across database creation and teardown scripts.
  4. Streamlined Nox Lifecycle: Refactored Nox session execution to dispatch directly to dedicated test functions without duplicate wrapper teardown logic.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces isolated, parallel test sessions for the Spanner SQLAlchemy dialect by dynamically provisioning unique test databases and configuration files. Key feedback from the review highlights critical issues with duplicate database creation in create_test_database.py that leads to leaked databases, potential race conditions from using a static session metadata file (recommending session-isolated metadata files and path normalization), environment variable pollution from os.environ.setdefault in noxfile.py, and an unused configuration variable in the system test session.

Comment thread packages/sqlalchemy-spanner/create_test_database.py Outdated
Comment thread packages/sqlalchemy-spanner/create_test_database.py Outdated
Comment thread packages/sqlalchemy-spanner/drop_test_database.py Outdated
Comment thread packages/sqlalchemy-spanner/noxfile.py Outdated
Comment thread packages/sqlalchemy-spanner/noxfile.py Outdated
@chalmerlowe
chalmerlowe force-pushed the refactor/sqlalchemy-spanner-clean-isolation branch 25 times, most recently from ecb6617 to 0af020c Compare August 26, 2026 10:40
def get_db_url():
"""Retrieve the database URL from the active session config file.

Respects the SQLALCHEMY_SPANNER_CONFIG environment variable set by Nox

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Many of the changes in this PR are focused on trying to ensure that one session does not clobber another session's database as we run sessions in parallel. So we have multiple changes similar to this, where we move away from using a single global test.cfg file that used to be overwritten with the current DB name as CI checks moved from one session to another. We move to using highly unique DB names that are handled within a session using an environment variable that is local to that session.

cutoff we quickly bump up against Cloud Spanner's limit of 100 databases per instance.
"""Delete test databases that are older than 4 hours.

Uses a .stale_cleanup_done sentinel file gate to ensure this global sweep

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

We had two conflicting problems:

  • If we accidentally leak Databases (i.e. if cleanup does not happen properly) then we quickly run into the 100 DB limit imposed by Spanner.
  • We used to be pretty robust about deleting DBs before any session starts regardless of who created it and when. We tried to use various timings with different levels of success (If we don't wait long enough, a slow running test may have their DB deleted. If we wait too long, then DBs may start stacking up.)

This approach attempts to be a reasonable compromise:

  • When a given CI check starts, we do a global delete up front when the first session starts of any DB older than 4 hours. This global delete covers all DBs regardless of name, etc and are meant specifically to clean up if we have leaks OR lingering DBs.
  • For the remainder of that CI check we avoid additional global deletes to avoid clobbering.
  • One reason for the four hour delay is to help ensure that manual commits early in the day don't clobber continuous OR nightly CI checks that might still be running in the early morning.

Note

Other tests in the PR are more tailored and focused as much as possible on ensuring that individual sessions don't leak.

@chalmerlowe
chalmerlowe force-pushed the refactor/sqlalchemy-spanner-clean-isolation branch from 0af020c to aad2523 Compare August 26, 2026 11:49
…dividual compliance/migration sessions now that system dispatcher handles them upfront
@chalmerlowe
chalmerlowe force-pushed the refactor/sqlalchemy-spanner-clean-isolation branch from aad2523 to 947fa0b Compare August 26, 2026 11:58
@chalmerlowe
chalmerlowe marked this pull request as ready for review August 26, 2026 15:01
@chalmerlowe
chalmerlowe requested a review from a team as a code owner August 26, 2026 15:01
@daniel-sanche
daniel-sanche merged commit 3e0bda4 into main Aug 26, 2026
46 checks passed
@daniel-sanche
daniel-sanche deleted the refactor/sqlalchemy-spanner-clean-isolation branch August 26, 2026 16:39
@release-please release-please Bot mentioned this pull request Aug 26, 2026
noahdietz pushed a commit that referenced this pull request Sep 3, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>django-google-spanner: 5.1.0</summary>

##
[5.1.0](django-google-spanner-v5.0.1...django-google-spanner-v5.1.0)
(2026-09-03)


### Features

* **django-google-spanner:** support Django 6.0
([#18128](#18128))
([207d947](207d947))
</details>

<details><summary>google-ads-admanager: 0.10.3</summary>

##
[0.10.3](google-ads-admanager-v0.10.2...google-ads-admanager-v0.10.3)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-api-core: 2.36.0</summary>

##
[2.36.0](google-api-core-v2.35.0...google-api-core-v2.36.0)
(2026-09-03)


### Features

* **api-core:** add ClientInterceptor and apply_interceptors helper (A)
([#18236](#18236))
([b6db5a9](b6db5a9))
* **api-core:** add tracer_provider to ClientOptions for OTel support
([#18139](#18139))
([9ffebc2](9ffebc2))


### Bug Fixes

* **api-core:** handle list-shaped REST error payloads
([#18232](#18232))
([02d1fd8](02d1fd8))
* **api-core:** move http encoding into transcode method
([#18218](#18218))
([082a99a](082a99a))
</details>

<details><summary>google-cloud-batch: 0.22.3</summary>

##
[0.22.3](google-cloud-batch-v0.22.2...google-cloud-batch-v0.22.3)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-biglake-hive: 0.3.3</summary>

##
[0.3.3](google-cloud-biglake-hive-v0.3.2...google-cloud-biglake-hive-v0.3.3)
(2026-09-03)


### Features

* **google/cloud/biglake/hive/v1:** onboard a new library
([#18172](#18172))
([97668d8](97668d8))
</details>

<details><summary>google-cloud-bigquery: 3.45.0</summary>

##
[3.45.0](google-cloud-bigquery-v3.44.0...google-cloud-bigquery-v3.45.0)
(2026-09-03)


### Features

* add user-agent delegation telemetry for pandas-gbq
([#18184](#18184))
([85ddfe5](85ddfe5))
</details>

<details><summary>google-cloud-bigquery-migration: 0.15.1</summary>

##
[0.15.1](google-cloud-bigquery-migration-v0.15.0...google-cloud-bigquery-migration-v0.15.1)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-bigquery-reservation: 1.27.0</summary>

##
[1.27.0](google-cloud-bigquery-reservation-v1.26.0...google-cloud-bigquery-reservation-v1.27.0)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-bigtable: 2.44.0</summary>

##
[2.44.0](google-cloud-bigtable-v2.43.0...google-cloud-bigtable-v2.44.0)
(2026-09-03)


### Features

* **bigtable:** Rerouted CheckAndMutateRows and ReadModifyWriteRows to
data client
([#18190](#18190))
([990f86e](990f86e))
* **bigtable:** Rerouted DirectRow.commit to use mutate_row
([#18191](#18191))
([7126a54](7126a54))
* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-compute: 1.52.0</summary>

##
[1.52.0](google-cloud-compute-v1.51.0...google-cloud-compute-v1.52.0)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-compute-v1beta: 0.12.3</summary>

##
[0.12.3](google-cloud-compute-v1beta-v0.12.2...google-cloud-compute-v1beta-v0.12.3)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-confidentialcomputing: 0.11.1</summary>

##
[0.11.1](google-cloud-confidentialcomputing-v0.11.0...google-cloud-confidentialcomputing-v0.11.1)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-config: 0.7.1</summary>

##
[0.7.1](google-cloud-config-v0.7.0...google-cloud-config-v0.7.1)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-container: 2.66.0</summary>

##
[2.66.0](google-cloud-container-v2.65.0...google-cloud-container-v2.66.0)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-discoveryengine: 0.20.3</summary>

##
[0.20.3](google-cloud-discoveryengine-v0.20.2...google-cloud-discoveryengine-v0.20.3)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-dlp: 3.39.0</summary>

##
[3.39.0](google-cloud-dlp-v3.38.0...google-cloud-dlp-v3.39.0)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-firestore: 2.30.0</summary>

##
[2.30.0](google-cloud-firestore-v2.29.0...google-cloud-firestore-v2.30.0)
(2026-09-03)


### Features

* **firestore:** configure gRPC message length limits for large
documents
([#18220](#18220))
([6e0f1d9](6e0f1d9))
</details>

<details><summary>google-cloud-geminidataanalytics: 0.13.2</summary>

##
[0.13.2](google-cloud-geminidataanalytics-v0.13.1...google-cloud-geminidataanalytics-v0.13.2)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
</details>

<details><summary>google-cloud-pubsub: 2.40.0</summary>

##
[2.40.0](google-cloud-pubsub-v2.39.2...google-cloud-pubsub-v2.40.0)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-cloud-spanner: 3.71.0</summary>

##
[3.71.0](google-cloud-spanner-v3.70.0...google-cloud-spanner-v3.71.0)
(2026-09-03)


### Features

* add send and ack mutations Cloud Spanner Queues
([#17728](#17728))
([182ff9f](182ff9f)),
refs
[#17727](#17727)
</details>

<details><summary>google-cloud-storage-control: 1.15.0</summary>

##
[1.15.0](google-cloud-storage-control-v1.14.0...google-cloud-storage-control-v1.15.0)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-developer-knowledge: 0.1.2</summary>

##
[0.1.2](google-developer-knowledge-v0.1.1...google-developer-knowledge-v0.1.2)
(2026-09-03)


### Features

* update API sources and regenerate
([#18267](#18267))
([813a5c1](813a5c1))
* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-maps-areainsights: 0.5.1</summary>

##
[0.5.1](google-maps-areainsights-v0.5.0...google-maps-areainsights-v0.5.1)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>google-maps-mapmanagement: 0.1.1</summary>

##
[0.1.1](google-maps-mapmanagement-v0.1.0...google-maps-mapmanagement-v0.1.1)
(2026-09-03)


### Features

* **google/maps/mapmanagement/v2:** onboard a new library
([#18173](#18173))
([f0f214b](f0f214b))
</details>

<details><summary>google-maps-routing: 0.11.1</summary>

##
[0.11.1](google-maps-routing-v0.11.0...google-maps-routing-v0.11.1)
(2026-09-03)


### Features

* update googleapis and regenerate
([#18229](#18229))
([d4f8a57](d4f8a57))
</details>

<details><summary>googleapis-common-protos: 1.75.3</summary>

##
[1.75.3](googleapis-common-protos-v1.75.2...googleapis-common-protos-v1.75.3)
(2026-09-03)


### Bug Fixes

* **googleapis-common-protos:** remove deprecated license classifier
([#18242](#18242))
([6f0216a](6f0216a)),
refs
[#18235](#18235)
</details>

<details><summary>sqlalchemy-spanner: 1.20.1</summary>

##
[1.20.1](sqlalchemy-spanner-v1.20.0...sqlalchemy-spanner-v1.20.1)
(2026-09-03)


### Bug Fixes

* **sqlalchemy-spanner:** robust session-isolated test databases and
clean lifecycle management
([#18179](#18179))
([3e0bda4](3e0bda4))


### Documentation

* **sqlalchemy-spanner:** centralize CONTRIBUTING.rst pointers
([#18142](#18142))
([f55ae02](f55ae02))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
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.

3 participants