Skip to content

[#13515] improvement(test): Share one MySQL and PostgreSQL container per core test build - #13553

Closed
nevzheng wants to merge 30 commits into
apache:mainfrom
nevzheng:codex/13529-shared-container-databases
Closed

nevzheng wants to merge 30 commits into
apache:mainfrom
nevzheng:codex/13529-shared-container-databases

Conversation

@nevzheng

@nevzheng nevzheng commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

What changes were proposed in this pull request?

Start one MySQL and one PostgreSQL container per Gradle build, instead of one per test JVM, and give each backend fixture its own new database on that server.

  • SharedDbContainerService, a Gradle BuildService in buildSrc, starts the containers with the docker CLI and publishes admin connection details to the MySQL and PostgreSQL test tasks.
  • SharedCoreDatabaseProvisioner creates and migrates a uniquely named database for each fixture. [#6505] improvement(test): Reuse core database fixtures by test class #13518's reuse and rebuild rules are unchanged; this PR only changes how a backend is provisioned.
  • The container data directories are on tmpfs, with tuning flags for disposable test databases.
  • A stale container is removed only when the Gradle daemon that started it is confirmed dead.
  • A database task that doesn't get the shared server fails fast instead of silently starting per-JVM containers.
  • Without the shared-container properties, as in an IDE run outside Gradle, tests fall back to per-JVM containers as before.

Why are the changes needed?

With #13530, each test JVM starts its own MySQL or PostgreSQL container. That multiplies container start-up and memory by the JVM count and adds no isolation, since every fixture already gets its own database. One server per backend is enough, and keeping its data on tmpfs removes most of the remaining I/O cost.

Related to: #13515

Does this PR introduce any user-facing change?

No production change. Test-only: existing MySQLContainer and PostgreSQLContainer callers should note the 63-character database-name limit and a changed exception type.

How was this patch tested?

  • Locally on the current head (macOS, JDK 17, Docker via OrbStack): corePostgreSQLTest ran 588 tests with 0 failures and 3 skipped, and coreMySQLTest ran 588 with 0 failures and 2 skipped, each in 2 JVMs against one shared container.
  • Same Mac, compared with [#13516] improvement(ci): Split core tests into explicit database lanes #13517 alone: PostgreSQL lane 13m48s → 1m35s, MySQL lane 30m27s → 2m30s.
  • ./gradlew -p buildSrc test passes. Two fixes came out of building it on its own: buildSrc now declares gradleApi(), and the daemon-liveness check no longer treats PID 0 as alive on macOS.
  • If a MySQL or PostgreSQL task runs without the shared server published, tests now fail instead of quietly falling back to per-JVM containers. Checked locally with TestJobMetaService and TestJdbcPartitionStatisticStorageIT on both backends.
  • Unit tests cover the docker-CLI output parsing and the PostgreSQL duplicate-database path without needing Docker.

Stack: #13517 → #13518 → #13530 → this PR

@nevzheng nevzheng changed the title Codex/13529 shared container databases [Improvement] Share single MySQL/PostgreSQL container across test build via SharedDbContainerService Sep 28, 2026
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 110c6b2 to 978b96e Compare September 28, 2026 19:14
@nevzheng

nevzheng commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Container count is real and verified; the speed story is small, inconsistent in direction, and there's a bigger unmerged win upstream of this PR.

Ran the same before/after comparison on two independent machines: a 14-CPU/48GB Mac (already posted to gravitino-enterprise#2289) and a Cloud Workstation matched to gcp-arc-runners-large (8vCPU/31GB — this repo's actual CI runner class).

Backend Mac: Before→After Speedup Runner-class: Before→After Speedup
H2 52.5s → 54.6s 0.96x 1m27s → 1m25s 1.02x
MySQL 819.6s → 753.5s 1.09x 26m25s → 27m53s 0.95x
PostgreSQL 332.2s → 370.0s 0.90x 7m10s → 6m59s 1.03x
Total 20m13s → 20m49s 0.97x 35m02s → 36m17s 0.97x

0 deadlocks / 0 lock-waits in any run, either machine — correctness is solid.

Two findings worth flagging:

  1. MySQL and PostgreSQL flip direction between machines (MySQL faster on Mac, slower on the runner-class box; PostgreSQL does the opposite). Only the total is consistent (~3% slower on both machines) — that's the one number I'd trust; the per-backend deltas look like run-to-run noise (n=1 per condition per machine).
  2. Container count confirmed via logs: exactly 1 Started shared MySQL/PostgreSQL test database container line per backend in the shared-container run, vs the pre-patch design's one container per fork by construction. That claim holds cleanly.

Bigger context: dug into the stack (#6505 → #13518 → #13530 → this PR) and #13518 (fixture reuse — stop restarting the DB per test-template invocation) reports a 4.15x speedup on H2 (backend inits 460→48, 89.6% fewer) — that's the real "minimize restarts" win, and it's still open/unmerged with MySQL/PostgreSQL numbers not yet posted. This PR's container-count change is a much smaller, later slice on top of that, and on the data above it's roughly a wash-to-small-loss on wall clock in exchange for halving container footprint.

Next step before deciding whether to ship as-is: test whether the ~3-5% MySQL slowdown is a tuning problem, not an architecture problem — the shared container is running MySQL's default (production-durability-oriented) config against 2x the concurrent DDL/DML load. innodb_flush_log_at_trx_commit=0, sync_binlog=0, and larger buffer/log-buffer sizes are all safe for a disposable test container and may close or reverse the gap without touching the container-sharing design. Will report back.

Recommendation as of now: hold on merging until (a) #13518's own MySQL/PostgreSQL numbers land — that's where the real payoff is, and (b) the tuning experiment above tells us whether this PR's tradeoff is fixable cheaply.

@nevzheng

nevzheng commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Correction to my last comment — the durability-flag config was never actually running. tmpfs did essentially all the work.

Two independent code reviews on 4b0626ba8 caught real bugs, and fixing them (dde1301b7...d9817c144, latest push) turned up something bigger: loadExtraDbArgs() resolved its resource path wrong (ClassLoader#getResourceAsStream on a package-relative path), so every "tuned" run — including the numbers in my last comment — was starting MySQL/PostgreSQL at stock defaults. Verified live against the running container: buffer pool 128M not 2G, doublewrite=ON, log_bin=ON, none of the flags in mysql.args/postgresql.args ever reached the process.

Corrected numbers (8vCPU/31GB, same hardware as before)

Backend Before (2 containers/fork) Shared container, tmpfs only (previously reported as "tuned") Shared container, tmpfs + tuning actually applied (now) Net speedup
MySQL 26m 25s 7m 05s 6m 40s 3.96x
PostgreSQL 7m 10s 2m 11s 2m 10s 3.31x
H2 (control) 1m 27s 1m 21s 1m 20s 1.08x

tmpfs is ~95% of the win. The durability-relaxation flags, now that they genuinely run, add ~6% on MySQL and roughly nothing measurable on PostgreSQL. Net result is still slightly better than what I reported before (the flags do help a little, once they actually execute), but the earlier framing — "durability relaxation solved the regression" — was wrong. It was the disk-vs-RAM data directory the whole time.

520/520 pass, 0 deadlocks, PostgreSQL's 2 skips still pre-existing.

What's fixed as of the latest push

  • --performance-schema=OFF removed — it would have silently broken TestRoleMembershipWrites/TestOwnerAssignmentWrites's lock-contention assertions (they poll performance_schema.data_lock_waits) had the loader bug not made it moot.
  • tmpfs mounts capped (mysql:size=2g, postgresql:size=5g, sized against this repo's real CI runner — ubuntu-22.04, 4 vCPU/16GB — not the 31GB dev box these numbers were measured on).
  • The resource-loading bug itself fixed (Class#getResourceAsStream, package-relative).
  • Logging added for which tuning args actually load (this exact bug would have been caught immediately in CI output if that logging had existed from the start).
  • Container logs now captured on a failed startup instead of a silent 120s timeout.
  • Machine-sizing README added next to the .args files with the headroom-math formula.

Why the durability relaxation is still safe, even though it turned out to matter less than claimed

Unchanged from before: nothing in the test suite asserts crash-recovery/WAL-replay semantics, and the container is destroyed every run regardless. The justification stands — it just isn't doing the heavy lifting I said it was.

Recommendation

Still recommend shipping — the real number is a genuine 3.96x on MySQL, verified against a container where every flag is confirmed live, not a claim I have to walk back a second time. The attribution error doesn't change the outcome, just the "why."

nevzheng added a commit to nevzheng/gravitino that referenced this pull request Sep 29, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng requested a review from jerryshao September 29, 2026 01:57
@nevzheng nevzheng self-assigned this Sep 29, 2026
@nevzheng
nevzheng requested a review from yuqi1129 September 29, 2026 01:57
@nevzheng

Copy link
Copy Markdown
Collaborator Author

Follow-up on this PR's approach — closing the loop on further concurrency work.

After this PR shipped (shared container + tuning, 26m25s → 7m04s, 3.7x), we evaluated two further architectural changes on top of it to push past the existing per-fork concurrency cap: thread-scoped parallelism (ScopedReference — per-thread isolation of the 4 core DB singletons instead of per-process) and classloader-scoped parallelism (per-lane classloaders, zero production-code changes). Both were spiked, measured on CI-matched hardware, and rejected:

  • ScopedReference: broke real concurrency-serialization tests ("waits for uncommitted delete," row-level lock serialization) at the lowest concurrency level tested, and ran 5x slower (35m54s) than this PR's own tuned baseline — worse on both correctness and speed.
  • Classloader isolation: NoClassDefFoundError inside Testcontainers' resource-cleanup hook, crossing the per-lane classloader boundary — nondeterministic, sometimes failing the build outright and sometimes silently skipping cleanup while still reporting green.

Neither is a tuning problem fixable with more iteration — both are structural mismatches between sharing test infrastructure below the process level and libraries (JDBC connection semantics, Testcontainers' Docker client) not designed for that. Recommend not pursuing either further at this time. This PR's container-sharing + tuning approach stands as the shipped result.

@yuqi1129

Copy link
Copy Markdown
Contributor

The PR is too large to review.

@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 1c5d493 to 1d8199e Compare October 13, 2026 21:30
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 1d8199e to 6795e1d Compare October 13, 2026 21:45
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 1d8199e to 6795e1d Compare October 13, 2026 21:45
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 6795e1d to 0249098 Compare October 13, 2026 21:49
nevzheng and others added 7 commits October 13, 2026 16:42
- Revert build.yml timeout from 90 to 120 minutes; 90 was too tight
  given the MySQL lane alone measures ~69 min locally with no margin.
- Rename the coreH2Test/coreMySQLTest/corePostgreSQLTest
  inputs.property key from includeDockerTaggedTests to
  coreTestIncludesDockerTaggedTests, distinguishing it from the
  unrelated extensions.extraProperties[includeDockerTaggedTests]
  flag consumed by root build.gradle.kts's shared test setup.
- Drop the hardcoded "()" from BackendTestExtension's
  getDisplayName(): @testtemplate methods can declare real
  parameters, so an empty parameter list misrepresented the
  method's signature in JUnit XML/HTML reports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
…+ BackendLaneCondition

The three excludeTags(otherTwoBackends) clauses in registerCoreTestTask allowed
two silent failure modes with no build-time check: a class carrying two backend
tags was excluded from every lane, and a class missing its restriction tag ran
redundantly in all three.

Replace them with a single typed source of truth:
- DatabaseBackend: the three core database backends.
- DatabaseTest: annotation declaring backends() (defaults to all three), meta-
  annotated with the lane-inclusion tag and @ExtendWith(BackendLaneCondition).
- BackendLaneCondition: an ExecutionCondition that reads the lane's
  gravitino.core.test.backend system property (already published by
  registerCoreTestTask) and enables/disables the class accordingly, producing a
  visible, reported skip rather than silent exclusion from Gradle's discovery.
- TestDatabaseTestClassification: a build-time reflection check (runs in
  coreUnitTest, no database, no Docker) asserting no @databasetest(backends={})
  and no leftover raw per-backend @tag usages anywhere in core.

core/build.gradle.kts's three-way excludeTags table collapses to a single
includeTags(coreDatabaseTestTag); backend restriction now lives entirely in
test source. Migrated all three existing usage patterns: AbstractEntityStorageTest
(parameter-driven multi-backend via storageProvider()/BackendTestSelector),
TestJDBCBackend (@TestTemplate-driven via BackendTestExtension), and
TestJdbcPartitionStatisticStorageIT's three single-backend @nested classes.

Verified via TestBackendLaneCondition (direct unit coverage) and a real
:core:coreH2Test run before/after the change: identical 520 executed tests and
outcomes: the only delta is 2 classes (14 tests) that were previously excluded
from Gradle's own discovery and now appear as an explicit, reported skip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
… change

654cfe3 dropped the hardcoded "()" from BackendTestExtension's
getDisplayName() but didn't update this test's assertions, leaving
coreUnitTest red on this branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
compare_legacy_partition() (and its "compare-legacy" CLI subcommand)
compared a legacy :core:test manifest against the union of the four
split-lane manifests, but nothing in .github/workflows/build.yml ever
called it - dev/ci/test-shards.sh excludes :core:test from CI entirely,
so no legacy manifest is ever produced to compare against. manifest and
reconcile, which ARE CI-wired, are untouched.

Removes the dead function, its argparse subcommand, its dedicated unit
tests, and the legacy/ fixture directory only they consumed. Drops the
now-unused "legacy" choice from MANIFEST_LANES.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
The three excludeTags(otherTwoBackends) clauses in registerCoreTestTask
excluded a class from every lane if it carried two conflicting backend
tags, instead of running it under each tagged backend.

Replace the hand-written per-backend cases with one JUnit boolean tag
expression built from a backend->tag map: exclude a class only when it
is pinned to another backend and not to this one. A class tagged for
several backends now runs under each of them; adding a backend is one
map entry. Still a discovery-time PostDiscoveryFilter (same as the tags
it replaces), so an excluded class never appears in a lane's JUnit XML -
core_test_identity.py's foreign-backend-marker check, which is wired
into CI, still sees exactly what it did before.

Verified against a real :core:coreH2Test run: reproduced the original
bug on the old scheme (a double-tagged class silently excluded from
every lane), then confirmed the fix runs it correctly with a clean,
single-class XML directory and a passing manifest step.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
nevzheng and others added 11 commits October 13, 2026 16:42
The root setTestEnvironment reads the flag inside param.doFirst, at
execution time, after every register/configureEach action has run, so
the core database lanes keep their Docker-tagged tests regardless of
configuration order. Comment both ends so the read stays in doFirst.
Only the build suite excludes :core:test. BackendIT's others shard still
realizes it through the root test task under -PskipTests, where it
matches nothing in core, so the deprecation warning can appear there.
For a class with no tests of its own, such as
TestJdbcPartitionStatisticStorageIT, coreTestLaneOf now reports each
member test class and its lanes instead of warning that the class runs
nowhere. Non-static @nested classes also pick up their enclosing
classes' tags. TestCoreLaneMembership reuses the same helpers.
TestCoreLaneMembership now fails coreUnitTest for a Docker-tagged class with no backend tag, so the doc no longer describes that case as unguarded.
Wire coreUnitTest/coreH2Test/coreMySQLTest/corePostgreSQLTest into a
core-test matrix job in build.yml directly, so the PR no longer depends
on the Backend IT sharding in apache#13495 (dev/ci/test-shards.sh). The build
job skips :core:test and uploads its JaCoCo reports; a coverage job
merges them with the four core lane .exec files after
core-test-contract reconciles the lanes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019TX7XCnuCMGSJ7xX2iubTH
Drop the design doc, the Python test-identity tool with its tests and
fixtures, the core-test-contract job and manifest steps, and the
coreTestLaneOf helper task. TestCoreLaneMembership keeps the orphan-lane
guard; its two tag helpers move into the test itself.
CoreBackend tags choose the lane; MySQL and PostgreSQL need Docker and
H2 does not. gravitino-docker-test on a backend class only keeps it out
of coreUnitTest and of legacy :core:test, so an H2-tagged class must not
need a container.
Say that an untagged Docker class runs in no lane task (legacy
:core:test still runs it with Docker on), say plainly why lane
annotations must not carry an execution-time hook, and name what the
includeDockerTaggedTests flag actually controls.
Review asked to keep only the core changes, so remove what doesn't
earn its keep:

- TestCoreDatabaseLaneAnnotations only re-tested JUnit's own @tag
  meta-annotation and @inherited handling. The lanes themselves and
  TestCoreLaneMembership already prove the tags select the right tests.
- The lane rules were spelled out three times (core/build.gradle.kts,
  CoreBackend, the :core:test block). CoreBackend's Javadoc is now the
  one place; the Gradle file points to it.
- The coreTestIncludesDockerTaggedTests input property only repeated
  what the coreTestBackend input already tells Gradle's up-to-date check.
… trivia

Review asked to keep only the core changes:

- Gradle already writes each Test task's JUnit XML to
  build/test-results/<task> and its JaCoCo data to build/jacoco/<task>.exec,
  so the lanes no longer set those paths. The coreTestSuite and
  coreTestBackend inputs only repeated what includeTags and the backend
  system property already give the up-to-date check, and the three
  imports are Kotlin DSL defaults.
- validateCoreSuiteCoverage checked that the four .exec files exist, which
  the coverage job already checks before it copies them.
- TestBackendTestSelector no longer pins the six-row legacy matrix or the
  display-name string through a mocked ExtensionContext; the remaining
  tests cover selection, validation and the selected-backend matrix.
Contributors had no guide to the new lanes: name the four tasks and when
Docker is needed, how a class joins a lane through @corebackend, and how
to check a class runs in the lane you expect.
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 0249098 to 460f919 Compare October 13, 2026 23:56
apache#13478 added docs/policies.md as an input and gravitino.repositoryRoot as
a system property to :core:test. TestRestrictedRegoGoldenFile is a unit
test, so CI runs it in coreUnitTest: give that task the same two
settings. The database lanes never run it and get neither.
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 14, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from 460f919 to 3e76e44 Compare October 14, 2026 02:42
@nevzheng nevzheng changed the title [Improvement] Share single MySQL/PostgreSQL container across test build via SharedDbContainerService [#13515] improvement(test): Share one MySQL and PostgreSQL container per core test build Oct 14, 2026
…gain

CloseContainerExtension runs for every integration-test module. Making
it rethrow turned a slow Docker teardown at the end of a green run into
a failed task. Keep ContainerSuite.close() throwing for direct callers,
but log in the extension as before.
The fork count and the number of lanes on the command line don't change
test results, but as task inputs they made every machine and every task
combination miss the build cache.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from d493ec2 to eb6ae02 Compare October 14, 2026 19:28
nevzheng added a commit to nevzheng/gravitino that referenced this pull request Oct 14, 2026
RAT flagged buildSrc/src/main/resources/.../shared-db-tuning/{mysql,postgresql}.args
as unapproved-license files, failing CI (build (17, others) / :rat task) on
apache#13553. Both are plain-text resource files that were missing the standard
ASF header used elsewhere in this module (see common/src/main/resources/project.properties
for the same header on a non-Java text resource). loadServerTuningArgs()
already ignores blank lines and lines starting with #, so this is purely
additive and does not change the flags applied to either container.
Each core MySQL or PostgreSQL test task used to start its own container
per test JVM. SharedDbContainerService, a Gradle BuildService, now
starts one MySQL and one PostgreSQL container for the whole build
through the docker CLI, and each backend gets a fresh, uniquely named
database on that server instead of a fresh container.

- SharedCoreDatabaseProvisioner reads the published admin URL, user and
  password from system properties and creates the per-backend database.
- A core database task that gets no shared server fails fast instead of
  silently starting its own containers.
- Stale containers are reaped only when the Gradle daemon that owns
  them is gone; PIDs 0 and 1 are never treated as live owners.
- The containers use capped tmpfs mounts and durability-off settings
  that are safe for disposable test data; the tuning args are logged,
  and container logs are captured when a container never becomes ready.
- buildSrc declares gradleApi() so `./gradlew -p buildSrc test` builds
  on its own.
- A README in the tuning resources describes the benchmark machine and
  how to size the settings.
@nevzheng
nevzheng force-pushed the codex/13529-shared-container-databases branch from eb6ae02 to 9912611 Compare October 14, 2026 19:44
@github-actions

Copy link
Copy Markdown

Code Coverage Report

Overall Project 71.74% 🟢
Files changed No Java source files changed -

Module Coverage
aliyun 19.74% 🔴
api 54.68% 🟢
authorization-common 85.96% 🟢
authorization-ranger 14.44% 🔴
aws 53.54% 🟢
aws-bundle 0.0% 🔴
azure 32.1% 🔴
azure-bundle 0.0% 🔴
catalog-common 29.49% 🔴
catalog-fileset 82.37% 🟢
catalog-glue 73.24% 🟢
catalog-hive 83.63% 🟢
catalog-jdbc-common 57.62% 🟢
catalog-jdbc-doris 87.56% 🟢
catalog-jdbc-mysql 81.91% 🟢
catalog-jdbc-postgresql 84.02% 🟢
catalog-jdbc-starrocks 79.16% 🟢
catalog-kafka 75.21% 🟢
catalog-lakehouse-generic 80.67% 🟢
catalog-lakehouse-hudi 79.1% 🟢
catalog-lakehouse-iceberg 85.93% 🟢
catalog-lakehouse-paimon 84.36% 🟢
catalog-model 77.99% 🟢
cli 44.62% 🟢
client-java 79.32% 🟢
client-java-runtime 0.0% 🔴
common 60.49% 🟢
core 87.4% 🟢
filesystem-hadoop3 76.89% 🟢
flink 55.44% 🟢
flink-common 32.2% 🔴
flink-runtime 0.0% 🔴
gcp 32.2% 🔴
hadoop-auth 68.0% 🟢
hadoop-common 17.84% 🔴
hive-metastore-common 62.3% 🟢
hive-metastore3-libs 58.8% 🟢
iceberg-aliyun-bundle 0.0% 🔴
iceberg-common 66.53% 🟢
iceberg-rest-server 77.01% 🟢
idp-basic 86.77% 🟢
integration-test-common 0.0% 🔴
jobs 70.23% 🟢
k8s-job-executor 99.81% 🟢
lance-common 39.9% 🔴
lance-rest-server 69.51% 🟢
lineage 59.39% 🟢
optimizer 84.27% 🟢
optimizer-api 40.87% 🟢
server 91.43% 🟢
server-common 83.85% 🟢
spark 61.79% 🟢
tencent 81.78% 🟢
tms 79.75% 🟢
trino-connector 63.31% 🟢

jerryshao added a commit that referenced this pull request Oct 15, 2026
…es (#13517)

### What changes were proposed in this pull request?

Split `core` tests into four Gradle lanes, each run as its own CI job,
so unit tests and each database backend report, fail and time
independently.

- `coreUnitTest` runs tests with no database backend. `coreH2Test`,
`coreMySQLTest` and `corePostgreSQLTest` each run one backend,
sequentially. A class joins a lane with
`@CoreBackend.H2/MySQL/PostgreSQL/All`.
- `TestCoreLaneMembership` fails `coreUnitTest` when a Docker-tagged
class has no backend tag, so a class can't silently drop out of every
lane.
- The `coverage` job merges the four lanes' raw JaCoCo data into one
core report.
- Three plain `@Test` methods move from `TestJobMetaService` to
`TestJobMetaServiceValidation`: they need no database, and in a
`@CoreBackend.All` class they would run three times.
- `:core:test` still works locally and prints a deprecation warning.
- `docs/how-to-test.md` describes the lanes and how to check which lane
a class runs in.

### Why are the changes needed?

`:core:test` combines unit tests with all three database backends in one
task. Failures, timing and coverage can't be told apart per backend, and
the follow-up concurrency work (#13518, #13530, #13553) has no safe
boundary to build on. Running the lanes in parallel also shortens the
critical path (see Timing).

Fix: #13516

Related to: #13515

### Does this PR introduce _any_ user-facing change?

No production API, storage format or configuration changes. Test-only
changes:

- New Gradle tasks `coreUnitTest`, `coreH2Test`, `coreMySQLTest`,
`corePostgreSQLTest`, and the test-only system property
`gravitino.core.test.backend`.
- `:core:test` is deprecated: it still runs all core tests locally but
prints a warning pointing to the lanes.
- Core `@TestTemplate` cases are now reported as `testMethod[H2
Backend]` instead of `[H2 Backend]`, in JUnit XML and HTML for the new
lanes and for legacy `:core:test`. Test history keyed on the old names
starts fresh, and exact-match `--tests` filters need the new name.
Trailing-wildcard filters such as `*[H2 Backend]` still match.
- CI checks: `build (17)` and the BackendIT checks keep their names, but
`build (17)` now skips `:core:test`, so core results appear only under
the new `core-test (17, unit|h2|mysql|postgresql)` checks. `coverage` is
now its own check. `.asf.yaml` requires no status checks by name, so
branch protection needs no change. Merge order for the follow-up PRs is
on #13515.

### How was this patch tested?

Locally (macOS, JDK 17, Docker via OrbStack) on the PR head:

- `./gradlew :core:spotlessCheck :core:compileTestJava -PskipWeb=true
-PskipDockerTests=true`
- `./gradlew :core:coreUnitTest -PskipWeb=true -PskipDockerTests=true`:
2,203 tests, 0 failures.
- `./gradlew :core:coreH2Test -PskipWeb=true -PskipDockerTests=true`:
588 tests, 0 failures; no Docker container was started.
- `./gradlew :core:corePostgreSQLTest -PskipWeb=true
-PskipDockerTests=false`: 588 tests, 0 failures, 3 skipped.
- `./gradlew :core:coreMySQLTest -PskipWeb=true
-PskipDockerTests=false`: 588 tests, 0 failures, 2 skipped.
- The three database lanes ran the same 588 tests once backend names are
normalized, and no class ran in both the unit lane and a database lane.
All 312 core test classes JUnit discovers ran in a lane.
- `TestCoreLaneMembership` fails, naming the four `TestEntityStorage*`
classes, when `@CoreBackend.All` is removed from
`AbstractEntityStorageTest`.
- `actionlint .github/workflows/build.yml`

<details><summary>Timing (CI)</summary>

Lane jobs from [run
38280654558](https://github.com/apache/gravitino/actions/runs/38280654558)
on `656282cc6`. Later commits remove code and comments and leave lane
selection unchanged; the lanes' JUnit and JaCoCo outputs now use
Gradle's default paths.

| Job | Wall time |
| --- | ---: |
| `build (17)` on `main`, core included
([1](https://github.com/apache/gravitino/actions/runs/38357234677),
[2](https://github.com/apache/gravitino/actions/runs/38245414857),
[3](https://github.com/apache/gravitino/actions/runs/38139898061)) |
1h28m – 1h46m |
| `build (17)` on this PR, core skipped | 49m30s |
| `core-test (17, unit)` | 6m04s |
| `core-test (17, h2)` | 6m59s |
| `core-test (17, postgresql)` | 19m11s |
| `core-test (17, mysql)` | 27m20s |
| `coverage` | 1m28s |

The lanes run in parallel with `build`, so the critical path is `build`
plus `coverage`, about 51m.

</details>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jerry Shao <jerryshao@datastrato.com>
@nevzheng

Copy link
Copy Markdown
Collaborator Author

Closing in favour of #13899. Measurements showed the win here was the lower-durability container settings, not the shared-container service. Without extra test JVMs, ContainerSuite already shares one container per JVM. #13899 applies the same settings to the existing containers, scoped to the core database tests, in about 30 lines.

@nevzheng nevzheng closed this Oct 16, 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.

3 participants