From f0e9e7e249db2f7ee2aa310abd1461ba72e7d269 Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 3 Mar 2026 22:17:31 +0100 Subject: [PATCH 1/4] first draft of new event queues docs --- guides/events/event-queues-new.md | 651 ++++++++++++++++++++++++++++++ 1 file changed, 651 insertions(+) create mode 100644 guides/events/event-queues-new.md diff --git a/guides/events/event-queues-new.md b/guides/events/event-queues-new.md new file mode 100644 index 000000000..a572f0077 --- /dev/null +++ b/guides/events/event-queues-new.md @@ -0,0 +1,651 @@ +--- +synopsis: > + Transactional Event Queues allow you to schedule events and background tasks for asynchronous, exactly-once processing with ultimate resilience. +status: released +--- + +# Transactional Event Queues + +{{ $frontmatter.synopsis }} + +[[toc]] + + + +## Motivation + +In distributed systems, things fail. A remote service may be temporarily unavailable, a network call may time out, or your process may crash right after committing a database transaction but before sending the follow-up message. +These failures can leave your system in an inconsistent state — data is committed, but dependent side effects never happen. + +_Transactional Event Queues_ solve this by persisting events and tasks in a database table **within the same transaction** as your business data. +After the transaction commits, a background process picks up the queued entries and executes them asynchronously — with retries, exactly-once guarantees, and a dead letter queue for unrecoverable failures. + +This pattern is widely known as the _Transactional Outbox_, but CAP's event queues go beyond outbound messages. +They provide a unified mechanism for four use cases: + +- **Outbox** — defer outbound calls to remote services until the transaction succeeds. +- **Inbox** — acknowledge inbound messages immediately and process them asynchronously. +- **Background Tasks** — schedule periodic or delayed tasks such as data replication. +- **Callbacks** — react to completed or failed tasks, enabling SAGA-like compensation patterns. + + + +## How It Works { #concept } + +The core principle is straightforward: + +1. Instead of executing side effects directly, you write an event message into a database table — **within the current transaction**. +2. Once the transaction commits, a task runner reads pending messages and dispatches them to the respective service. +3. If processing succeeds, the message is deleted. If it fails, the system retries with exponentially increasing delays. +4. After a configurable maximum number of attempts, the message is moved to the dead letter queue for manual intervention. + +![This graphic is explained in the accompanying text.](../../releases/2025/assets/may25/TaskController.png){width=80%} + +Because the event message and your business data share the same database transaction, you get two fundamental guarantees: + +- **No phantom events** — if the transaction rolls back, no event is ever sent. +- **No lost events** — if the transaction commits, the event is guaranteed to be processed eventually. + + + +## Use Cases + +### Outbox { #outbox } + +The outbox defers outbound calls to remote services until the main transaction succeeds. +This prevents sending requests to external systems when your transaction might still roll back. + +**Example:** When creating a travel booking, you also need to notify an external flight service. +Without the outbox, the notification could be sent even if the booking transaction fails. + +::: code-group +```js [Node.js] +const xflights = await cds.connect.to('xflights') +const qd_xflights = cds.queued(xflights) + +this.after('CREATE', 'Travels', async (travel) => { + // Persisted within the current transaction, sent after commit + await qd_xflights.send('bookFlight', { travelId: travel.ID }) +}) +``` +```java [Java] +@Autowired @Qualifier("MyCustomOutbox") +OutboxService outbox; + +@Autowired @Qualifier(CqnService.DEFAULT_NAME) +CqnService remoteFlights; + +@After(event = CqnService.EVENT_CREATE, entity = Travels_.CDS_NAME) +void notifyFlights(List travels) { + AsyncCqnService outboxedFlights = AsyncCqnService.of(remoteFlights, outbox); + travels.forEach(t -> outboxedFlights.emit("bookFlight", Map.of("travelId", t.getId()))); +} +``` +::: + +Some services are outboxed automatically, including `cds.MessagingService` and `cds.AuditLogService`. +You don't need to call `cds.queued()` or configure anything extra for these — they use the persistent queue by default. + +[Learn more about auto-outboxed services in Node.js.](../../node.js/queue#per-configuration){.learn-more} +[Learn more about the outbox in Java.](../../java/outbox){.learn-more} + + +### Inbox { #inbox } + +The inbox mirrors the outbox pattern for inbound messages. +When a message arrives from a broker like SAP Event Mesh or Apache Kafka, the messaging service immediately persists it to the database, acknowledges it to the broker, and schedules its processing. + +This brings two advantages: + +- **Quick acknowledgment** — the message broker does not have to wait for your processing to complete. This is especially important with brokers like Kafka that expect fast consumer acknowledgments. +- **Flatten the curve** — if a burst of messages arrives, they're queued in your database and processed at a controlled pace, preventing overload. + +Enable the inbox in your configuration: + +::: code-group +```json [Node.js — package.json] +{ + "cds": { + "requires": { + "messaging": { + "inboxed": true + } + } + } +} +``` +```yaml [Java — application.yaml] +cds: + messaging: + services: + - name: messaging-name + inbox: + enabled: true +``` +::: + +::: warning Inboxing moves the dead letter queue into your app +With the inbox enabled, all messages are acknowledged to the message broker regardless of whether processing succeeds. +Failures must be managed through the [dead letter queue](#dead-letter-queue). +::: + + +### Background Tasks { #background-tasks } + +Event queues are not limited to messaging. You can schedule arbitrary background tasks such as data replication, cache refresh, or garbage collection. + +**Example:** Replicate data from a remote service every 10 minutes. + +::: code-group +```js [Node.js] +const srv = await cds.connect.to('RemoteService') +await srv.schedule('replicate', { entity: 'Products' }).every('10 minutes') +``` +::: + +The `schedule` method is a shortcut for `cds.queued(srv).send()` with additional timing options: + +```js +// Execute once, as soon as possible +await srv.schedule('cleanup', { olderThan: '30d' }) + +// Execute once, after a delay +await srv.schedule('cleanup', { olderThan: '30d' }).after('1h') + +// Execute repeatedly +await srv.schedule('replicate', { entity: 'Products' }).every('10 minutes') +``` + +::: tip Real-world example: data federation +The [data federation guide](../integration/data-federation) uses `srv.schedule().every()` to implement polling-based replication, fetching incremental updates from remote services on a regular interval. +::: + + +### Callbacks (SAGA Patterns) { #callbacks } + +In distributed transactions, you often need to react when an asynchronous step completes or fails. +Event queues support this with `#succeeded` and `#failed` callback events, enabling compensation logic similar to SAGA patterns. + +**Example:** After successfully creating a flight booking via the outbox, replicate the full business object from the remote system. If the booking fails, notify the user. + +::: code-group +```js [Node.js] +const flights = await cds.connect.to('FlightService') + +// Called when the queued booking succeeds +flights.after('bookFlight/#succeeded', async (result, req) => { + console.log('Flight booked successfully:', result) + // Replicate booking details from remote +}) + +// Called when the queued booking fails after max retries +flights.after('bookFlight/#failed', async (error, req) => { + console.log('Flight booking failed:', error) + // Trigger compensation logic +}) +``` +::: + +::: tip Register on specific events +Callback handlers must be registered for the specific `#succeeded` or `#failed` events. +The `*` wildcard handler is not called for these events. +::: + + + +## How to Use { #how-to-use } + +### Queueing a Service { #cds-queued } + +Use `cds.queued(srv)` in Node.js to obtain a queued proxy of any non-database service. +All subsequent dispatches on this proxy are persisted to the event queue and processed asynchronously. + +::: code-group +```js [Node.js] +const srv = await cds.connect.to('RemoteService') +const qsrv = cds.queued(srv) + +// All operations are now queued +await qsrv.emit('someEvent', { key: 'value' }) // fire-and-forget +await qsrv.send('someRequest', { key: 'value' }) // request (result discarded) +await qsrv.run(SELECT.from('Products')) // query (result discarded) +``` +::: + +::: tip `await` is still needed +Even though processing is asynchronous, you still need to `await` because the message is written to the database within the current transaction. +::: + +In Java, use `OutboxService.outboxed(srv)` to wrap any CAP service: + +::: code-group +```java [Java] +OutboxService outbox = runtime.getServiceCatalog() + .getService(OutboxService.class, "MyCustomOutbox"); +CqnService remote = runtime.getServiceCatalog() + .getService(CqnService.class, "RemoteService"); + +// Wrap with outbox handling +AsyncCqnService queued = AsyncCqnService.of(remote, outbox); +queued.emit("someEvent", Map.of("key", "value")); +``` +::: + + +### Unqueueing a Service + +If a service is queued by configuration, you can get back the original (synchronous) service: + +::: code-group +```js [Node.js] +const srv = cds.unqueued(qsrv) // back to synchronous +``` +```java [Java] +CqnService original = outbox.unboxed(outboxedService); +``` +::: + + + +### Service API { #service-api } + +When working with event queues, you interact with the standard CAP service APIs: + +| API | Description | +|-----|-------------| +| `srv.emit(event, data)` | Emit a fire-and-forget event message | +| `srv.send(event, data)` | Send a request (return value discarded for queued services) | +| `srv.run(query)` | Run a CQL query (return value discarded for queued services) | +| `srv.schedule(event, data)` | Shortcut for `cds.queued(srv).send()` with timing options | + +The `schedule` method supports a fluent API: + +```js +await srv.schedule('task', data) // execute asap +await srv.schedule('task', data).after('1h') // execute after one hour +await srv.schedule('task', data).every('1h') // repeat every hour +``` + + +### Queueing by Configuration { #by-configuration } + +You can queue any service through configuration without changing code: + +::: code-group +```json [Node.js — package.json] +{ + "cds": { + "requires": { + "RemoteService": { + "kind": "odata", + "outboxed": true + } + } + } +} +``` +```yaml [Java — application.yaml] +cds: + outbox: + services: + MyCustomOutbox: + maxAttempts: 10 +``` +::: + + +### Auto-Outboxed Services { #auto-outboxed } + +The following services are outboxed by default — you don't need any additional configuration: + +| Service | Description | +|---------|-------------| +| `cds.MessagingService` | All messaging services (Event Mesh, Kafka, etc.) | +| `cds.AuditLogService` | Audit log events | + +This ensures that messaging and audit log events are always sent reliably and never lost due to transaction rollbacks. + +[Learn more about auto-outboxed services in Node.js.](../../node.js/queue#per-configuration){.learn-more} +[Learn more about the outbox in Java.](../../java/outbox#persistent){.learn-more} + + + +## Characteristics + +### Exactly Once { #exactly-once } + +The persistent queue guarantees exactly-once processing for database-related operations. +The system only commits database changes from event processing if the event is successfully processed, and vice versa. + +There is one active message processor per service, tenant, app instance, and message. +This prevents duplicate processing, except in the highly unlikely case of an app crash right after successful processing but before the message could be deleted from the queue. + +### No Phantom Events { #no-phantom-events } + +Because the event message is written within the same database transaction as your business data, a rollback of the transaction also removes the event message. +No event is ever dispatched for a transaction that didn't commit. + +### Guaranteed Order { #guaranteed-order } + +In Node.js, messages are processed in the order they were submitted, per service and tenant. + +In Java, the `DefaultOutboxOrdered` outbox processes entries in submission order. +The `DefaultOutboxUnordered` outbox may process entries in parallel across application instances. + +::: code-group +```yaml [Java — Configuring Order] +cds: + outbox: + services: + DefaultOutboxOrdered: + ordered: true # default + DefaultOutboxUnordered: + ordered: false # default +``` +::: + + +### Error Handling { #errors } + +When processing fails, the system retries the message with exponentially increasing delays. +After a configurable maximum number of attempts (default: 20 in Node.js, 10 in Java), the message is moved to the dead letter queue. + +Some errors are identified as _unrecoverable_ — for example, when a topic is forbidden in SAP Event Mesh. +These messages are immediately moved to the dead letter queue without further retries. + +To mark your own errors as unrecoverable in Node.js: + +```js +const error = new Error('Invalid payload') +error.unrecoverable = true +throw error +``` + +In Java, you can suppress retries by catching the error and calling `context.setCompleted()`: + +```java +@On(service = "", event = "myEvent") +void process(OutboxMessageEventContext context) { + try { + // processing logic + } catch (Exception e) { + if (isSemanticError(e)) { + context.setCompleted(); // remove from queue, no retry + } else { + throw e; // retry + } + } +} +``` + + + +## Dead Letter Queue { #dead-letter-queue } + +Messages that exceed the maximum retry count remain in the `cds.outbox.Messages` database table. +These entries form the _dead letter queue_ and require manual intervention — either to fix the underlying issue and retry, or to discard the message. + +### The Data Model + +Your database model is automatically extended with the following entity: + +```cds +namespace cds.outbox; + +entity Messages { + key ID : UUID; + timestamp : Timestamp; + target : String; + msg : LargeString; + attempts : Integer default 0; + partition : Integer default 0; + lastError : LargeString; + lastAttemptTimestamp: Timestamp @cds.on.update: $now; + status : String(23); +} +``` + + +### Managing Dead Letters + +You can expose a CDS service to manage the dead letter queue with actions to revive or delete entries. + +#### 1. Define the Service + +::: code-group +```cds [srv/outbox-dead-letter-queue-service.cds] +using from '@sap/cds/srv/outbox'; + +@requires: 'internal-user' +service OutboxDeadLetterQueueService { + + @readonly + entity DeadOutboxMessages as projection on cds.outbox.Messages + actions { + action revive(); + action delete(); + }; + +} +``` +::: + +::: warning Restrict access +The dead letter queue contains sensitive data. Ensure the service is accessible only to internal users. +::: + +#### 2. Filter for Dead Entries + +As `maxAttempts` is configurable, its value cannot be added as a static filter to the projection, but must be applied programmatically. + +::: code-group +```js [Node.js — srv/outbox-dead-letter-queue-service.js] +const cds = require('@sap/cds') + +module.exports = class OutboxDeadLetterQueueService extends cds.ApplicationService { + async init() { + this.before('READ', 'DeadOutboxMessages', function (req) { + const { maxAttempts } = cds.env.requires.outbox + req.query.where('attempts >= ', maxAttempts) + }) + await super.init() + } +} +``` +```java [Java — DeadOutboxMessagesHandler.java] +@Component +@ServiceName(OutboxDeadLetterQueueService_.CDS_NAME) +public class DeadOutboxMessagesHandler implements EventHandler { + + private final PersistenceService db; + + public DeadOutboxMessagesHandler( + @Qualifier(PersistenceService.DEFAULT_NAME) PersistenceService db) { + this.db = db; + } + + @Before(entity = DeadOutboxMessages_.CDS_NAME) + public void addDeadEntryFilter(CdsReadEventContext context) { + // Filter for entries that exceeded maxAttempts + Optional outboxFilters = createOutboxFilters(context.getCdsRuntime()); + outboxFilters.ifPresent(filter -> { + CqnSelect modified = copy(context.getCqn(), new Modifier() { + @Override + public CqnPredicate where(Predicate where) { + return filter.and(where); + } + }); + context.setCqn(modified); + }); + } +} +``` +::: + +#### 3. Implement Bound Actions + +Entries in the dead letter queue can be _revived_ by resetting the retry counter to zero, or _deleted_ permanently. + +::: code-group +```js [Node.js — srv/outbox-dead-letter-queue-service.js] +this.on('revive', 'DeadOutboxMessages', async function (req) { + await UPDATE(req.subject).set({ attempts: 0 }) +}) + +this.on('delete', 'DeadOutboxMessages', async function (req) { + await DELETE.from(req.subject) +}) +``` +```java [Java] +@On +public void reviveOutboxMessage(DeadOutboxMessagesReviveContext context) { + CqnAnalyzer analyzer = CqnAnalyzer.create(context.getModel()); + Map key = analyzer.analyze(context.getCqn()).rootKeys(); + Messages msg = Messages.create((String) key.get(Messages.ID)); + msg.setAttempts(0); + db.run(Update.entity(Messages_.class).entry(key).data(msg)); + context.setCompleted(); +} + +@On +public void deleteOutboxEntry(DeadOutboxMessagesDeleteContext context) { + CqnAnalyzer analyzer = CqnAnalyzer.create(context.getModel()); + Map key = analyzer.analyze(context.getCqn()).rootKeys(); + db.run(Delete.from(Messages_.class).byId(key.get(Messages.ID))); + context.setCompleted(); +} +``` +::: + +[Learn more about the dead letter queue in Node.js.](../../node.js/queue#managing-the-dead-letter-queue){.learn-more} +[Learn more about the dead letter queue in Java.](../../java/outbox#outbox-dead-letter-queue){.learn-more} + + + +## Deferred Principal Propagation { #principal-propagation } + +When an event is processed asynchronously, the original user context is no longer available. +CAP handles this as follows: + +- The **user ID** is stored with the queued message and re-created when the message is processed. +- **User roles and attributes** are _not_ stored. Asynchronous tasks are always processed in privileged mode. + +This means handlers for queued events must not rely on role-based authorization checks. +If you need to perform authorization in queued processing, store the necessary information in the event payload. + + + +## Configuration + +### Persistent Queue (Default) { #persistent-queue } + +The persistent queue is enabled by default. Messages are stored in a database table within the current transaction. + +::: code-group +```json [Node.js — package.json] +{ + "cds": { + "requires": { + "queue": { + "kind": "persistent-queue", + "maxAttempts": 20, + "storeLastError": true, + "timeout": "1h" + } + } + } +} +``` +```yaml [Java — application.yaml] +cds: + outbox: + services: + DefaultOutboxOrdered: + maxAttempts: 10 + ordered: true + DefaultOutboxUnordered: + maxAttempts: 10 + ordered: false +``` +::: + +Configuration options for Node.js: + +| Option | Default | Description | +|--------|---------|-------------| +| `maxAttempts` | `20` | Maximum retries before moving to dead letter queue | +| `storeLastError` | `true` | Store error information of the last failed attempt | +| `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned | + +Configuration options for Java: + +| Option | Default | Description | +|--------|---------|-------------| +| `maxAttempts` | `10` | Maximum retries before the entry is considered dead | +| `ordered` | `true` | Process entries in submission order | + + +### In-Memory Queue + +For development and testing, you can use the in-memory queue. Messages are held in memory and emitted after the transaction commits. + +::: code-group +```json [Node.js — package.json] +{ + "cds": { + "requires": { + "queue": { + "kind": "in-memory-queue" + } + } + } +} +``` +::: + +::: warning No retry mechanism +With the in-memory queue, messages are lost if processing fails. There is no retry mechanism. +::: + + +### Disabling the Queue + +You can disable event queues globally: + +```json +{ + "cds": { + "requires": { + "queue": false + } + } +} +``` + +Or disable queueing for a specific service: + +```json +{ + "cds": { + "requires": { + "messaging": { + "outboxed": false + } + } + } +} +``` + + + +## Manual Processing { #flush } + +If the app crashes, another emit for the respective tenant and service is necessary to restart processing. +You can trigger it manually using the `flush` method: + +::: code-group +```js [Node.js] +const srv = await cds.connect.to('RemoteService') +cds.queued(srv).flush() +``` +::: From f7e6aaeb1419a79f6a6bbbea09dc970f8df7c086 Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 3 Mar 2026 22:21:38 +0100 Subject: [PATCH 2/4] add to menu --- guides/events/_menu.md | 1 + 1 file changed, 1 insertion(+) diff --git a/guides/events/_menu.md b/guides/events/_menu.md index 5f12223b2..10a792f27 100644 --- a/guides/events/_menu.md +++ b/guides/events/_menu.md @@ -1,6 +1,7 @@ # [CAP-level Messaging](core-concepts) # [Event Queues](event-queues) +# [Event Queues NEW](event-queues-new) # [Messaging](messaging) # [Apache Kafka](../../../guides/events/apache-kafka) # [Advanced Event Mesh](is-aem) From 4b71456c1fa3766a13bece3eb15db341143065c3 Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 17 Mar 2026 23:24:39 +0100 Subject: [PATCH 3/4] stash --- .../_event-queues/EventQueuesScheduling.png | Bin 0 -> 79433 bytes .../_event-queues/EventQueuesScheduling.svg | 3 + guides/events/_event-queues/architecture.md | 137 ++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 guides/events/_event-queues/EventQueuesScheduling.png create mode 100644 guides/events/_event-queues/EventQueuesScheduling.svg create mode 100644 guides/events/_event-queues/architecture.md diff --git a/guides/events/_event-queues/EventQueuesScheduling.png b/guides/events/_event-queues/EventQueuesScheduling.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb7113672015ded5624d95d201f2ee51459bcab GIT binary patch literal 79433 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

GAS!*7bMqf?Kn)B=YK99$e6co1D>9 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 literal 0 HcmV?d00001 diff --git a/guides/events/_event-queues/EventQueuesScheduling.svg b/guides/events/_event-queues/EventQueuesScheduling.svg new file mode 100644 index 000000000..76b2c95e4 --- /dev/null +++ b/guides/events/_event-queues/EventQueuesScheduling.svg @@ -0,0 +1,3 @@ + + +
Whoever
Whoever
t1
t1
t0
t0
write event
into outbox
write event...
task for t1
at XX:XX:XX
→ "marker"
task for t1...
read
messages
read...
read
tasks
read...
delete
messages
delete...
delete
tasks
delete...
3.1
3.1
3.4
3.4
1.1
1.1
queued Service
queued Service
Service
Service
send/ emit
event
send/ emit...
t0 Task Runner
t0 Task Runner
runs on startup
+ every X mins
runs on startup...
Task Scheduler
Task Scheduler
schedule
event
scheduleevent
trigger (next) exec
(on commit)
trigger (next) exec...
send/ emit
event
send/ emit...
trigger
("flush t1")
trigger...
done
done
3.2
3.2
3.3
3.3
schedule
next exec
(if applicable)
schedule...
2.2
2.2
2.1
2.1
2.3
2.3
2.4
2.4
1.2
1.2
1.3a
1.3a
1.3b
1.3b
1.4
1.4
t1 Task Runner
t1 Task Runner
runs on startup
in non-mtx
runs on startup...
\ No newline at end of file diff --git a/guides/events/_event-queues/architecture.md b/guides/events/_event-queues/architecture.md new file mode 100644 index 000000000..c2d10f0b3 --- /dev/null +++ b/guides/events/_event-queues/architecture.md @@ -0,0 +1,137 @@ +## Overview + +![Event Queues Scheduling](./EventQueuesScheduling.png) + + + +## Approach + +The approach features three independent flows/loops that work as follows: + +### 1. Scheduling + +_Anybody_ sends/emits a request/event (hereafter simply _event_) to a service (1.1). +Because this service is _queued_, the event is intercepted and _scheduled_ for execution. +Via the additional API `srv.schedule()`, it is possible to supply `task`, `after`, and `every` arguments to make the task a _named task_ (see below) and to add delays and/or recurrence. + +The _scheduling_ described above is done by passing the event to the _task scheduler_ (1.2). +The task scheduler has three responsibilities: +1. Write the _message_ (following the outbox convention) to the tenant database (_t1_), in the same transaction if applicable, for atomicity (1.3a) +2. Write a _marker_ (see below) to the mtx database (_t0_) that captures that there is "something to do" for tenant _t1_ (1.3b) +3. Register an _on-commit_ listener that triggers execution of the scheduled task (1.4) + +Note: The task scheduler only `UPSERT`s messages and markers. + +### 2. Processing + +The _tenant task runner_ reads a configurable _chunk_ of messages from the database (2.1) and emits the respective event to the respective (_unqueued_) service (2.2). + +Each event is processed _individually_ and _in parallel_, each in its own transaction. +This must be taken into account when configuring the (default) chunk size. + +Events are also executed _exactly once_. +Two mechanisms ensure this: +1. _Application-level locking_: _Processable messages_ (see below) are `SELECT`ed `FOR UPDATE` and marked as _processing_. + - Alternatively, processable messages are `SELECT`ed `FOR UPDATE` and the lock is held for the entire duration (`legacyLocking: true`). + For migration reasons, this is still the default in cds^9, but the default will change in cds^10. +2. Messages are deleted within the same transaction in which they are processed. + - This is not possible with the legacy locking approach, because the reading transaction holds the lock on the message throughout. + +After successful processing, the message is deleted from the database (2.3). +For recurring tasks, the next execution is then scheduled via the task scheduler (2.4). + +After failed processing, the message's next attempt is scheduled via the task scheduler (2.4). +That is, the message is updated by incrementing `attempts`, setting `lastError` and `lastAttemptTimestamp`, and clearing `status`. +Scheduling the next attempt via the task scheduler is important to ensure that a respective marker is `UPSERT`ed. + +Notes: +- The task processor only `READ`s and `DELETE`s messages. +- In non-mtx scenarios, the task runner starts on app startup. + +### 3. Startup and Recovery + +The _mtx task runner_ reads a configurable _chunk_ of markers from the database (3.1) and emits the respective _flush_ event to the respective _tenant task runner_ (3.2). +A flush event resolves when all _processable messages_ have been processed (3.3). +Afterwards, all _previous markers_ (see Marker Deduplication) are deleted (3.4). + +Notes: +- It does not matter whether messages were processed successfully or not, because the next attempt is scheduled via the task scheduler, which writes a new marker. +- The mtx task runner runs on startup (only markers for "hot tenants" exist at that point) and every X minutes thereafter. +- In the future, mtx will also use the runtime's event queues implementation, so `t0` may contain markers as well as messages. + + + +## Markers + +_Markers_ contain no business data — only information about which queue of which tenant needs to be flushed at what point in time. + +### (Tenant-specific) Offset + +Because markers serve as a recovery/backup mechanism, their _timestamp_ differs from the _timestamp_ of the queued event. +Instead, a configurable _offset_ is added. + +To reduce the number of markers, they are placed on a configurable _grid_: the timestamp is determined by adding the offset to the original timestamp and then _ceiling_ the result to the next grid point. + +However, this can cause bursts of activity because task processing for multiple tenants becomes synchronized. +To avoid this, an additional _tenant-specific offset_ is added to the ceiled timestamp. +Because this offset requires no coordination, the tenant identifier (`zone id`/`app_tid`) is used as the seed of a random number generator; its first output, multiplied by the grid interval, becomes the tenant-specific offset. + +### Deduplication + +During marker selection for processing, there may be multiple "flush t1" markers with different timestamps. +However, a flush always includes all processable messages, so only a single flush is needed. +Therefore, a `SELECT DISTINCT` is used to skip logical duplicates, and after the flush, all markers with a timestamp ≤ the selected marker's timestamp are deleted. + + + +## Named Tasks + +_Named tasks_ (or _singleton tasks_) are scheduled events that: +1. Must exist only once +2. Have a non-null `task` property that allows them to be identified and addressed + +### Concurrency Issue + +There is a concurrency issue when scheduling named tasks. +Database transactions are _read-committed_ by default (on HANA and Postgres), meaning they only see committed data. +If two parallel transactions (which is common during bootstrapping) both try to schedule the same named task _for the first time_, they will not detect a conflict when `UPSERT`ing that task. + +Preventing all but the first commit would require a deferred check. +Because of the `appid` column for shared HDI containers, this would need to be a `UNIQUE INDEX` (which supports a `WHERE` clause). +Such a unique index cannot currently be created via cds. + +The alternatives are: +1. Acquire a table lock (which would require executing database-specific plain SQL, at least in Node.js), or +2. Rely on the primary key constraint of the outbox table by hashing `task` + `appid` into a deterministic `UUID` (or `String(36)`) + + + +## Messages + +### Processable Messages + +A message is _processable_ if: +1. Message timestamp + retry offset (= attempts × some exponential factor) < current time +2. Attempts < max attempts +3. Status ≠ `processing` OR the processing status has timed out + +### Schema Enhancements + +To efficiently manage markers (see Marker Deduplication), some fields currently encoded in `msg` — namely `tenant`, `queue`, and `event` — should be promoted to the top level (cf. https://github.tools.sap/cap/cds/pull/6170). + +### Migration Issue + +As with the introduction of application-level locking in cds^9, there is also a migration issue with the schema enhancement. +Old task runners may select messages written by new task schedulers, in which `tenant`, `queue`, and `event` are no longer encoded in `msg`. +(Because such old task runners are always _tenant task runners_, `tenant` is not relevant here.) +As a mitigation, `queue` and `event` must continue to be encoded in `msg` until cds^11. + + + +## TODOs + +1. The chunk size should be dynamic, based on the number of available connections. +2. The `t0` pool min should be 1 to make `UPSERT`ing a marker faster. +3. Should the message schema include a version property to avoid migration issues in the future (i.e., older runners selecting messages written by newer schedulers)? +4. Scheduled task runner runs (step 1.4) should probably be combined at some granularity. +5. Should the task runner also run every X minutes in non-mtx scenarios? From 78db55c200017d2382142ba3b9d94602c5766cad Mon Sep 17 00:00:00 2001 From: D050513 Date: Tue, 17 Mar 2026 23:37:06 +0100 Subject: [PATCH 4/4] review --- guides/events/event-queues-new.md | 85 +++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/guides/events/event-queues-new.md b/guides/events/event-queues-new.md index a572f0077..15d9ae546 100644 --- a/guides/events/event-queues-new.md +++ b/guides/events/event-queues-new.md @@ -6,7 +6,18 @@ status: released # Transactional Event Queues +Persist events in the same database transaction as your business data. Process them asynchronously — with retries, ordering, and a dead letter queue. +{.subtitle} + {{ $frontmatter.synopsis }} +{.abstract} + +> [!tip] Guiding Principles +> +> 1. **Transactional** — events are written to the database within the same transaction as your business data +> 2. **Asynchronous** — a background runner dispatches events after commit, not during the request +> 3. **Resilient** — failed events are retried with exponential backoff; unrecoverable ones land in a dead letter queue +> 4. **Unified** — one mechanism covers four use cases: outbox, inbox, background tasks, and callbacks [[toc]] @@ -15,10 +26,10 @@ status: released ## Motivation In distributed systems, things fail. A remote service may be temporarily unavailable, a network call may time out, or your process may crash right after committing a database transaction but before sending the follow-up message. -These failures can leave your system in an inconsistent state — data is committed, but dependent side effects never happen. +These failures leave your system in an inconsistent state — data is committed, but dependent side effects never happen. _Transactional Event Queues_ solve this by persisting events and tasks in a database table **within the same transaction** as your business data. -After the transaction commits, a background process picks up the queued entries and executes them asynchronously — with retries, exactly-once guarantees, and a dead letter queue for unrecoverable failures. +After the transaction commits, a background runner picks up the queued entries and executes them asynchronously — with retries, exactly-once guarantees, and a dead letter queue for unrecoverable failures. This pattern is widely known as the _Transactional Outbox_, but CAP's event queues go beyond outbound messages. They provide a unified mechanism for four use cases: @@ -35,11 +46,35 @@ They provide a unified mechanism for four use cases: The core principle is straightforward: 1. Instead of executing side effects directly, you write an event message into a database table — **within the current transaction**. -2. Once the transaction commits, a task runner reads pending messages and dispatches them to the respective service. +2. Once the transaction commits, a background runner reads pending messages and dispatches them to the respective service. 3. If processing succeeds, the message is deleted. If it fails, the system retries with exponentially increasing delays. 4. After a configurable maximum number of attempts, the message is moved to the dead letter queue for manual intervention. -![This graphic is explained in the accompanying text.](../../releases/2025/assets/may25/TaskController.png){width=80%} +```mermaid +sequenceDiagram + participant H as Event Handler + participant DB as Database + participant R as Background Runner + participant S as Remote Service + + H->>DB: Write business data + H->>DB: Write event to outbox table + Note over H,DB: Both writes in the same transaction + DB-->>H: COMMIT + + loop Background processing + R->>DB: Poll for pending events + R->>S: Dispatch event + alt Success + R->>DB: Delete message + else Transient failure + R->>DB: Increment retry counter + Note over R: Retry with exponential backoff + else Max retries exceeded + R->>DB: Mark as dead letter + end + end +``` Because the event message and your business data share the same database transaction, you get two fundamental guarantees: @@ -97,9 +132,13 @@ When a message arrives from a broker like SAP Event Mesh or Apache Kafka, the me This brings two advantages: -- **Quick acknowledgment** — the message broker does not have to wait for your processing to complete. This is especially important with brokers like Kafka that expect fast consumer acknowledgments. +- **Quick acknowledgment** — the message broker does not have to wait for your processing to complete. This reduces backpressure and prevents consumer group rebalancing under load. - **Flatten the curve** — if a burst of messages arrives, they're queued in your database and processed at a controlled pace, preventing overload. +> [!note] Especially useful when brokers don't support redelivery +> Some message brokers (for example, SAP Event Mesh) do not allow retriggering delivery or correcting message payloads. +> With the inbox, failures are handled inside your app via the [dead letter queue](#dead-letter-queue), where you have full control over retry and correction. + Enable the inbox in your configuration: ::: code-group @@ -143,7 +182,11 @@ await srv.schedule('replicate', { entity: 'Products' }).every('10 minutes') ``` ::: -The `schedule` method is a shortcut for `cds.queued(srv).send()` with additional timing options: +> [!note] Node.js only +> The `srv.schedule()` API is currently available in Node.js only. +> In Java, use a `@Scheduled` annotation in combination with a queued outbox service to achieve equivalent behavior. + +The `schedule` method is a convenience shortcut that internally queues the call using `cds.queued(srv)` and adds timing options: ```js // Execute once, as soon as possible @@ -216,7 +259,7 @@ await qsrv.run(SELECT.from('Products')) // query (result discarded) Even though processing is asynchronous, you still need to `await` because the message is written to the database within the current transaction. ::: -In Java, use `OutboxService.outboxed(srv)` to wrap any CAP service: +In Java, use `AsyncCqnService.of(srv, outbox)` to wrap any CAP service with an outbox: ::: code-group ```java [Java] @@ -256,7 +299,7 @@ When working with event queues, you interact with the standard CAP service APIs: | `srv.emit(event, data)` | Emit a fire-and-forget event message | | `srv.send(event, data)` | Send a request (return value discarded for queued services) | | `srv.run(query)` | Run a CQL query (return value discarded for queued services) | -| `srv.schedule(event, data)` | Shortcut for `cds.queued(srv).send()` with timing options | +| `srv.schedule(event, data)` | Schedule a task with optional timing — Node.js only | The `schedule` method supports a fluent API: @@ -315,10 +358,10 @@ This ensures that messaging and audit log events are always sent reliably and ne ### Exactly Once { #exactly-once } The persistent queue guarantees exactly-once processing for database-related operations. -The system only commits database changes from event processing if the event is successfully processed, and vice versa. +Database changes made during event processing are only committed if — and only if — the event is successfully processed. -There is one active message processor per service, tenant, app instance, and message. -This prevents duplicate processing, except in the highly unlikely case of an app crash right after successful processing but before the message could be deleted from the queue. +To prevent duplicate processing across application instances, there is at most one active processor per service and tenant at any given time. +In the unlikely event of a process crash immediately after successful processing but before the message could be deleted, the message may be processed a second time. Handlers should therefore be idempotent where possible. ### No Phantom Events { #no-phantom-events } @@ -361,7 +404,7 @@ error.unrecoverable = true throw error ``` -In Java, you can suppress retries by catching the error and calling `context.setCompleted()`: +In Java, suppress retries by catching the error and calling `context.setCompleted()`: ```java @On(service = "", event = "myEvent") @@ -382,7 +425,7 @@ void process(OutboxMessageEventContext context) { ## Dead Letter Queue { #dead-letter-queue } -Messages that exceed the maximum retry count remain in the `cds.outbox.Messages` database table. +Messages that exceed the maximum retry count remain in the `cds.outbox.Messages` database table with their error information intact. These entries form the _dead letter queue_ and require manual intervention — either to fix the underlying issue and retry, or to discard the message. ### The Data Model @@ -524,14 +567,14 @@ public void deleteOutboxEntry(DeadOutboxMessagesDeleteContext context) { ## Deferred Principal Propagation { #principal-propagation } -When an event is processed asynchronously, the original user context is no longer available. +When an event is processed asynchronously, the original HTTP request context is no longer available. CAP handles this as follows: - The **user ID** is stored with the queued message and re-created when the message is processed. -- **User roles and attributes** are _not_ stored. Asynchronous tasks are always processed in privileged mode. +- **User roles and attributes** are _not_ stored. Asynchronous processing always runs in privileged mode. This means handlers for queued events must not rely on role-based authorization checks. -If you need to perform authorization in queued processing, store the necessary information in the event payload. +If you need to enforce authorization in queued processing, encode the necessary information in the event payload itself. @@ -575,7 +618,7 @@ Configuration options for Node.js: |--------|---------|-------------| | `maxAttempts` | `20` | Maximum retries before moving to dead letter queue | | `storeLastError` | `true` | Store error information of the last failed attempt | -| `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned | +| `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned and eligible for reprocessing | Configuration options for Java: @@ -604,7 +647,7 @@ For development and testing, you can use the in-memory queue. Messages are held ::: ::: warning No retry mechanism -With the in-memory queue, messages are lost if processing fails. There is no retry mechanism. +With the in-memory queue, messages are lost if processing fails. There is no retry mechanism and no dead letter queue. ::: @@ -640,12 +683,12 @@ Or disable queueing for a specific service: ## Manual Processing { #flush } -If the app crashes, another emit for the respective tenant and service is necessary to restart processing. -You can trigger it manually using the `flush` method: +After an application restart or crash, pending events in the database are not automatically picked up until a new outbox write occurs for the same service and tenant. +You can trigger reprocessing manually using the `flush` method, for example from a startup hook or admin endpoint: ::: code-group ```js [Node.js] const srv = await cds.connect.to('RemoteService') -cds.queued(srv).flush() +await cds.queued(srv).flush() ``` :::