Repository navigation
#137 Add Oracle Database 19c+ store (threadmill-store-oracle) - #138
Conversation
Add threadmill-store-oracle with OracleJobStore, held to the shared AbstractJobStoreContractTest like the in-memory, PostgreSQL, and Redis stores. - Standard JDBC only; applications supply the driver (ojdbc11 is test-only). - Startup refuses servers older than 19c, non-AL32UTF8 databases, and sessions with NLS_COMP other than BINARY. - Partial indexes are emulated with indexed virtual columns (CASE WHEN <predicate> THEN <column> END); per-key top-n probes stay sort-free under the 19c optimizer. - Claiming uses index-ordered FOR UPDATE SKIP LOCKED cursors fetched only as far as the page budget, ROWNUM top-n branches per key, MIN loose scans, and EXISTS admission probes; no lateral joins. - Long CLOB values are written through temporary Clobs: direct binds split surrogate pairs at the driver's chunk boundary. - Text keyset pages order in binary, independent of the client locale. - OracleMigrationRunner records per-statement progress and resumes a crashed migration, since Oracle commits DDL implicitly; a lock row on a second connection serializes migrators. - No remote wake channel: Oracle has no lightweight LISTEN/NOTIFY. Refs #137
ThreadmillOracleAutoConfiguration wires OracleJobStore from the application's DataSource, gated by @ConditionalOnClass so applications without threadmill-store-oracle never load Oracle types. - threadmill.store.oracle.schema-mode (migrate, validate, none, drop-and-migrate) mirrors the PostgreSQL schema handling, including the explicit destructive-reset flag. - join_transaction enqueue mode now also works with Oracle through SpringOracleTransactionBoundary. - With both JDBC store modules on the classpath, JdbcStoreSelection picks the store: threadmill.store.jdbc-type, then the spring.datasource.url scheme, then the module present (PostgreSQL on a tie, unchanged behaviour). - OracleJobStore names a non-Oracle DataSource in its startup error. Refs #137
- threadmill-store-oracle README and docs/oracle-schema.md: requirements, privileges, type mapping, virtual-column indexes, claim path, driver and locale notes, resumable migrations, SQL*Plus-ready manual DDL, and how to test against 21c XE or a real 19c database. - Configuration, quickstart, Spring README, transactions, operations, compatibility, execution model, and index cover the Oracle properties, JDBC store selection, join_transaction on Oracle, and the absent remote wake channel. - Release checklist adds the real-19c run; CI runs the Oracle suite on 21c XE next to the 23ai Free run inside check. - AGENTS.md records the Oracle design decisions and regression coverage. Refs #137
- threadmill-soak: OracleHarnessFixture (container or external database via -PoracleUrl / -PoracleUser / -PoraclePassword; the password is never written to run artifacts), soakOracle, Oracle in soakAll and as an opt-in endurance backend, OracleSoakTest in soakRegression (throughput, contention, queue family, recurring no-skip, container-pause recovery), harness smoke tests. - threadmill-simulation: simulateOracle, included in simulate; the run uses a Hikari pool because unpooled bursts exceed Oracle's process limit. - AGENTS.md lists the remaining Oracle follow-ups. Refs #137
hemju
left a comment
There was a problem hiding this comment.
AI code review of PR #138 / issue #137. I read the full PR diff, linked issue, and repository guidance, then ran the Oracle 21c query-plan suite and two targeted local integration probes. The joined-transaction partial commit and mutex ownership findings below both reproduced on Oracle 21c; I recommend fixing them before merge.
The new Oracle module keeps Spring out of the store and reuses the shared JobStore contract, which fits the project architecture. The 21c CI plan-test failure also needs a stable fix. The documented 19c support still needs the planned real-19c qualification run; 21c/23ai coverage and 19.1 optimizer mode do not exercise 19c server behavior. As a maintainability follow-up, consider splitting the 2,850-line OracleJobStore by concern (claim/maintenance, dedup/concurrency, cron/mutex) into package-private collaborators while keeping one store API and transaction boundary.
The current checks also show a memory soak-smoke failure and npm audit advisories. I did not establish that those two failures came from this PR, but the release gate remains red.
- Joined write units are all-or-nothing: Oracle rolls back only the failing statement, so a caller that caught the store's error and committed also committed the unit's earlier statements (e.g. an insertAll batch before its duplicate id). Each write in a caller-owned transaction now rolls back to a savepoint on failure. - releaseMutex rejects a null holder; DECODE treated it as equal to an acquired empty holder (stored as NULL) and freed that mutex. New shared contract case for every store. - Lease and mutex durations keep nanoseconds instead of truncating sub-millisecond leases to zero. - Plan tests locate their cursor by a unique tag in V$SQL instead of the session's previous statement, which returned no plan on 21c XE in CI. - docs: the default poll interval is 500 ms. Refs #137
|
[AI reply] Triage of the AI review All five inline findings were valid and are fixed in 63c074a, with regression rows in Review-level points:
|
hemju
left a comment
There was a problem hiding this comment.
AI follow-up review of commits 63c074a and f98ae27: I read the complete follow-up diff and verified that the five findings from my earlier review are addressed. The joined Oracle write now rolls back its whole unit to a savepoint, with a regression that catches the batch failure and commits an earlier successful write. Null mutex release is rejected and covered by the shared store contract. The duration bind preserves nanoseconds, the plan probe identifies its cursor explicitly, and the poll-interval documentation matches the 500 ms default. I found no new code issue in these changes.
Local verification on JDK 25: Oracle 21c query-plan and JobStore contract suites, Spring Oracle transaction tests, and the new mutex contract test on memory/PostgreSQL/Redis all passed. The PR's Oracle 21c CI job is now green. The full JDK 25 build was still running at the time of this comment. Dependency security remains red due to npm audit advisories for braces, postcss-selector-parser, and source-map-js (the npm lockfile was not changed in this PR). Real Oracle 19c qualification remains listed in the release checklist.
|
[AI] 8-hour Oracle endurance run: passed
Latency and throughput come from a laptop container, so they compare runs rather than measure production capacity. This run does not replace the real Oracle 19c test run, which is still open. |
Closes #137.
Adds
threadmill-store-oracle, an Oracle Database 19c+JobStoreheld to the sameAbstractJobStoreContractTestas the in-memory, PostgreSQL, and Redis stores, plus Spring Boot auto-configuration, soak/simulation backends, docs, and a 21c XE CI job.What's in it
feat(store-oracle)OracleJobStore,OracleMigrationRunner, schema, contract + regression + plan + migration testsfeat(spring-boot)ThreadmillOracleAutoConfiguration,threadmill.store.oracle.*,threadmill.store.jdbc-type,join_transactionon Oracledocsdocs/oracle-schema.md, configuration/transactions/operations/compatibility/release checklist, AGENTS.md, CI job on 21c XEtest(soak, simulation)soakOracle, Oracle insoakRegression,simulateOracle(insimulate)Design (as agreed in #137, with changes found while implementing)
ojdbc11is test-only, so the published artifact carries no Oracle-licensed dependency).AL32UTF8,NLS_COMP=BINARY, and an OracleDataSource(clear message otherwise).CREATE TABLE,CREATE TRIGGER, quota. NoCREATE TYPE, noDBMS_*, no AQ. DBA-applied DDL viaemitPendingSql()/ schema modevalidate./terminators.Things the real database taught me (all pinned by named regression tests)
VIRTUALcolumns, not expression indexes. Under the 19c optimizer feature set, expression (FBI) indexes made every per-key top-n re-sort the key's whole range; virtual columns read index order and stop after n entries. Bonus: queries name plain columns instead of repeatingCASEexpressions.LATERAL … FETCH FIRSTreturned wrong results (0 rows) on Oracle 23.26. Per-key heads areROWNUMtop-n branches (UNION ALL), keys come from a recursiveMINloose scan, admission fromEXISTSrange probes.ROWNUM, notFETCH FIRST, on hot paths: with 19c featuresFETCH FIRSTbecame aWINDOW SORTover the range.ORA-02014) — index-orderedFOR UPDATE SKIP LOCKEDcursors are fetched only as far as the page; a test proves Oracle locks only fetched rows.Clob(freed per statement).ORDER BY: ojdbc derivesNLS_SORTfrom the JVM locale (German here), so linguistic page order disagreed with binary>keyset predicates and skipped rows (recurring-task scans, idle-group reclamation). Paging now orders in binary (NLSSORT … BINARY/ aRAWsort key).EXPLAIN PLANlies forRAWbinds (treats them asVARCHAR2→RAWTOHEX→ full scan).OracleQueryPlanTestruns the store's exact SQL with typed binds and assertsDISPLAY_CURSORplans.Deviations from the issue text
threadmill.store.jdbc-type(postgres|oracle) instead ofthreadmill.store.type: it only chooses between the two JDBC stores; Redis/memory keep their existing switches. Selection is property →spring.datasource.urlscheme → module present (PostgreSQL on a tie, unchanged behaviour), decided in conditions rather than byDatabaseMetaData(conditions run before any bean exists). A mis-selected store still fails fast at startup.threadmill_metadata(unused by any store) is not created on Oracle.Verification
OPTIMIZER_FEATURES_ENABLE='19.1.0'and a German client locale.simulateOracle: 400/400 succeeded, drained, 0 verifier violations (injected failures/timeouts retried).soakOracle(20 s, 20 jobs/s): verdict passed.OracleSoakTest5/5 incl. container-pause recovery (5,000 jobs ≈ 917/s on a laptop container)../gradlew checkgreen on the final head (JDK 25).Open / for review
./gradlew :threadmill-store-oracle:test -PoracleJdbcUrl=… -PoracleUser=… -PoraclePassword=…against a disposable schema on your 19c (release checklist item).soakEndurancekeepspostgres,redis;-Pbackends=oracleworks).productionChecknow also starts Oracle containers (store tests,simulate,soakRegression).ORA-01033/01034); tracked as a follow-up in AGENTS.md.AL32UTF8; if production uses a legacy charset, NCLOB/NVARCHAR2 support would be a follow-up.