Repository navigation
ARTEMIS-6333 Relational Database Persistence Storage - #6838
clebertsuconic wants to merge 9 commits into
Conversation
34a5032 to
65296cc
Compare
8a2efdd to
94f175f
Compare
4dbffdb to
5206d7e
Compare
|
I ran the transactional send/consume test on 5206d7e, PostgreSQL 16 against the file journal, with everything on one machine: broker, client and PostgreSQL on the same ext4 disk (WSL2) sharing 4 CPUs, fsync and synchronous_commit on. Same build, default
All runs finished, every message was received, and there were no broker warnings. PostgreSQL shared its 4 CPUs with the broker and client, so a separate database host may well do better. Most of the gap at small transactions is
I didn't measure the CPU cost of a period that short, so I can't say what the default should be, but it may be worth a look. One PostgreSQL run (1/1, commit every 100) decayed steadily from about 5k to 1.2k msg/s with nothing in the broker log. It didn't happen again in the other two repetitions or in two more runs on a fresh cluster, and I don't have a cause. Two perf tool problems got in the way, and they're on main too. Separately, |
|
Did you setup to use the new database stuff? |
|
I plan to improve the create of the Db |
|
Yes, it's the new storage: created with Everything else was what |
|
you could increase the number of connections. One thing I have been playing with is that I think it scales a lot better with the database. if you add paging to the mix (not fitting the memory) the new relational database is a lot better than the current JDBC version. I will merge your PRs for the table and benchmark improvements. thanks for those. |
355f738 to
247536e
Compare
|
You could increase the number of connections. I think the new database is going to be very fast, but not as fast as the journal... But if you think about this should be very compelling for mos users. Especially with many independent producers as you could have multiple writers (multiple connections). |
|
Thanks, I ran both on the same setup as before: WSL ext4, 4 shared CPUs, PostgreSQL 16, defaults unless stated, 1 KB persistent messages, producers committing every 100 unless stated. Paging: you're right, it's a big difference.
Means of 5 or 6 fills, 2 drains, and 3 producing and consuming runs of about 60 s. The journal's second producing and consuming run dipped to 20.3k (the others were 31.3k and 28.0k) and I don't know why. Every drain got all 500,000 back. Old JDBC stalled at 0 msg/s for some seconds in each producing and consuming run. The new storage drains about 20x faster than old JDBC, and about half as fast as the journal. The new storage logged AMQ224127 (dispatch from paging blocked) once in each drain and in 2 of the 3 producing and consuming runs. It carried on each time. Connections: they made no difference here, at the default flush period. 16 queues, one producer and one consumer each, received msg/s, mean of 2 runs:
PostgreSQL did get the connections (8, 14 and 26 open), but sampling To check, I set the flush period to 0.1 ms:
Active connections now reached 6 to 9, and commit every 10 nearly doubled, to about 65% of the journal. But 24 connections still did no better than 6, and mean active stayed around 2.5. So the flush period is what limits this workload here. This is one box with 4 shared CPUs and PostgreSQL on the same disk, so it can't show what several connections do on a real database server, where I'd expect more room. One thing from the tests: |
|
@clebertsuconic the |
|
@clebertsuconic here are the transactional consumer numbers I offered on #6873, now that #6870 is in. Same setup as before: 5206d7e with #6867 and #6870 added to the perf client, WSL ext4, 4 shared CPUs, PostgreSQL 16, defaults, so 6 connections. 16 queues, one producer and one consumer each,
Every run ended with sent equal to received, no exceptions and nothing left in That's about the same as the auto-ack runs I posted (24.3k and 5.5k), so batching the consumers' acknowledgements didn't change the result here. The host was also running some unrelated VMs this time, so the small differences don't mean much. I kept to 6 connections and the default flush period, so this doesn't add anything on scaling with more connections, and the limit from one box with shared CPUs still applies. |
|
Auto ack does a lot of batching. |
|
I tried 247536e with many queues paging at once and saw the read connections go over Setup: PostgreSQL 16, default
From the code, I expected this to stall producers when readers hold every connection, but it didn't. A probe sending persistent messages every 50 ms on another queue never took longer than 123 ms. So as far as I can tell, the problems are the limit and the noisy log, not throughput. Possible fixes: cap borrowing at |
|
Oh wow!!! Man you're great. To me the borrowed cursor was an idea I just introduced. I mean I was still wondering if it was the right idea. And you already found improvements to the concept. Sure we could / should limit the borrow limit. Perhaps by closing previous ones. |
Motivation: Artemis's append-only journal delivers outstanding throughput and is the ideal choice when fast local storage is available. However, many cloud and containerised deployments already operate managed relational databases and would benefit from an alternative persistence path that leverages that existing infrastructure — simplifying operations, backup, and disaster recovery without giving up the broker's asynchronous, non-blocking persistence contract. Modifications: Introduce a new `artemis-database-storage` module that implements the full broker persistence layer on top of JDBC, wired through the existing `StorageManager` / `PagingManager` SPIs so no protocol or core-broker code needs to change. Key design choices: - Group-commit / write-coalescing: `DataManager` buffers incoming writes and a scheduled timer flushes them in a single JDBC transaction, amortising the most expensive part (network round-trip + fsync) across many operations — the same technique database engines use internally (e.g. InnoDB group commit). - Command pattern with heterogeneous batching: each `DBData` subclass (messages, references, addresses, queues, prepared-TX, generic KV) dispatches itself to the correct `BatchableStatement`; a single transaction can therefore mix inserts and deletes across different tables atomically. - Worker pool with dedicated connections: multiple `DataWorker` instances, each owning its own JDBC `Connection` and full set of `PreparedStatement`s, cycle through a pool. `BorrowedWorker` adds a lease-based model for callers that need a connection for an extended sequence of operations. - Credit-based flow control: producers are throttled via a credit window so the broker cannot out-run the database. - Async write-behind with `IOCompletion` callbacks: callers never block on commit; each data item carries `storeLineUp()` / `done()` / `onError()` callbacks, preserving the same contract the journal already exposes. - Retry with reconnect: transient `SQLException`s are retried up to five times with connection re-establishment between attempts. - Database paging: `DatabasePagingManager` and `DatabasePagingStoreImpl` replace file-based paging when this store type is selected. `AddressSizeLimiter` is extracted from `PagingStoreImpl` so both the file and database paths share address-level flow control (max size, max messages, blocking, page-full rejection). - `DatabaseStorageMessageReader` provides cursor-based message reading from the database with configurable prefetch for queue consumption. - Six database dialects are supported out of the box: PostgreSQL, MySQL, Oracle, DB2, MS SQL Server, and HSQLDB, each with a dedicated `SQLProvider` handling type mappings and DDL differences. - `AbstractStorageManager` is extracted from `AbstractJournalStorageManager` so both the journal-backed and database-backed managers share common bookkeeping without duplication. Tests cover every layer: statement-level (insert/delete/update per entity), worker-level (retry, flow control, borrowed-worker lifecycle), full integration (StorageManager round-trips, real broker boot, cross-protocol, XA / prepared transactions), and stress (concurrent producers under sustained load). Result: Operators now have a choice between the journal — which remains the highest-throughput option for local storage — and a relational database backend that integrates naturally with managed database services, broadening the set of environments where Artemis can be deployed with minimal operational overhead. Assisted-By: Claude Opus 4.6 <noreply@anthropic.com>
…rimary key duplicates
247536e to
c5ae238
Compare
Motivation:
Artemis's append-only journal delivers outstanding throughput and
is the ideal choice when fast local storage is available. However,
many cloud and containerised deployments already operate managed
relational databases and would benefit from an alternative
persistence path that leverages that existing infrastructure —
simplifying operations, backup, and disaster recovery without
giving up the broker's asynchronous, non-blocking persistence
contract.
Modifications:
Introduce a new
artemis-database-storagemodule that implementsthe full broker persistence layer on top of JDBC, wired through
the existing
StorageManager/PagingManagerSPIs so noprotocol or core-broker code needs to change.
Key design choices:
Group-commit / write-coalescing:
DataManagerbuffers incomingwrites and a scheduled timer flushes them in a single JDBC
transaction, amortising the most expensive part (network
round-trip + fsync) across many operations — the same technique
database engines use internally (e.g. InnoDB group commit).
Command pattern with heterogeneous batching: each
DBDatasubclass (messages, references, addresses, queues, prepared-TX,
generic KV) dispatches itself to the correct
BatchableStatement; a single transaction can therefore mixinserts and deletes across different tables atomically.
Worker pool with dedicated connections: multiple
DataWorkerinstances, each owning its own JDBC
Connectionand full set ofPreparedStatements, cycle through a pool.BorrowedWorkeradds a lease-based model for callers that need a connection for
an extended sequence of operations.
Credit-based flow control: producers are throttled via a credit
window so the broker cannot out-run the database.
Async write-behind with
IOCompletioncallbacks: callers neverblock on commit; each data item carries
storeLineUp()/done()/onError()callbacks, preserving the same contractthe journal already exposes.
Retry with reconnect: transient
SQLExceptions are retried upto five times with connection re-establishment between attempts.
Database paging:
DatabasePagingManagerandDatabasePagingStoreImplreplace file-based paging when thisstore type is selected.
AddressSizeLimiteris extracted fromPagingStoreImplso both the file and database paths shareaddress-level flow control (max size, max messages, blocking,
page-full rejection).
DatabaseStorageMessageReaderprovides cursor-based messagereading from the database with configurable prefetch for queue
consumption.
Six database dialects are supported out of the box: PostgreSQL,
MySQL, Oracle, DB2, MS SQL Server, and HSQLDB, each with a
dedicated
SQLProviderhandling type mappings and DDLdifferences.
AbstractStorageManageris extracted fromAbstractJournalStorageManagerso both the journal-backed anddatabase-backed managers share common bookkeeping without
duplication.
Tests cover every layer: statement-level (insert/delete/update per
entity), worker-level (retry, flow control, borrowed-worker
lifecycle), full integration (StorageManager round-trips, real
broker boot, cross-protocol, XA / prepared transactions), and
stress (concurrent producers under sustained load).
Result:
Operators now have a choice between the journal — which remains
the highest-throughput option for local storage — and a relational
database backend that integrates naturally with managed database
services, broadening the set of environments where Artemis can be
deployed with minimal operational overhead.
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com