Event pipeline simulation
SNS → SQS → Avro → ActiveMQ Artemis → API → Aurora PostgreSQL, in two AWS regions
Applicability
| System | The web app event-pipeline-v2. It is a Cloudflare Worker. It serves static assets, and a small API for the notes in this manual. |
|---|---|
| Pages | / (3D view), /2d (2D view), /manual (this manual). |
| Model | An event pipeline in AWS us-east-1 and us-east-2. Its parts are SNS, SQS, KEDA pods, Avro, PrivateLink, a Network Load Balancer and an ActiveMQ Artemis cluster. Each cluster has a mirror to the other region. After the Artemis consumers, each region has an API (1 to 10 pods, HorizontalPodAutoscaler). The API stores each event in an Aurora PostgreSQL Global Database (Serverless v2). The primary cluster in us-east-1 has the writer e1-1 and the reader e1-2. The secondary cluster in us-east-2 has the read-only reader e2-1. Each region also has a DLQ processor with an S3 bucket, an ALB in front of the API, and a read path: Center, Cube and a Query API (1 to 5 pods, HorizontalPodAutoscaler). |
| App version | Project event-pipeline-v2-3d, package version 0.0.0. The project has no release numbers. |
| Data date | Research notes: 2026-10-01, with later additions up to 2026-11-03. Scenario results: 2026-11-02 for scenarios 1 to 31, 2026-11-03 for scenarios 32 to 43. Manual: 2026-11-03. |
| Readers | Engineers who build or operate this pipeline. Users of the app. |
How to use this manual
- Read Section 1 to get an overview of the system.
- Use Section 2 to find data about one part. Each part has the same id as in the app, for example
e1-sqs. - Use Section 3 to learn how the system operates. Each subsection agrees with one step of the guided tour.
- Use Section 4 to do a task in the app or in the project.
- Use Section 5 when the app or the pipeline shows a problem.
- Use Section 6 for numbers, Section 7 for terms and Section 8 for sources.
- To change the theme, the font, the text size or the line width, press ⌘P (Ctrl+P on Windows and Linux), or click Aa at the top of the page.
- To add a highlight or a note, select text. To see all your notes, click ✎ Notes. If you are not signed in, notes stay in this browser. If you sign in, they sync to your account, and you see them on each device. To sign in, click Sign in to sync in the notes panel.
Values with the label Simulation are illustrative values of the app. They are not AWS, KEDA, Kubernetes or Artemis defaults.
Warnings, cautions and notes
A warning tells you about a condition that can cause loss of messages, loss of stored rows or duplicate processing in a real pipeline.
A caution tells you about a condition that can cause incorrect results in the app, a failed check or a change to a live deployment.
A note gives you more data. The data helps you to do a task or to understand the system.
A warning or a caution comes before the step that it applies to.
1Description of the system
1.1Purpose
The app shows how one event moves through an event pipeline in two AWS regions. At the end of the pipeline, an API stores the event in an Aurora PostgreSQL database. The app also shows what occurs when a part of the pipeline or the database fails. The default view also shows a read path: two UIs that query the stored data. The Show control hides the read path.
A live simulation runs in the app. You can change the traffic, the topology, the failures, the API and the database in the Simulate panel. Two pages show the same simulation: a 3D page and a 2D page.
1.2Message path in short
- The producer publishes an event to an SNS topic.
- SNS sends a copy of the event to each subscribed SQS queue.
- A pod asks SQS for a message with an HTTP request, and the answer carries the message. This is a pull. KEDA sets the number of pods.
- The pod puts the SQS body into an Avro record.
- The pod sends the Avro message over a Core connection, through PrivateLink and a Network Load Balancer (NLB), to one Artemis broker.
- The pod deletes the SQS message only after the broker confirms the send.
- The Artemis cluster puts the message on a broker that has consumers.
- The broker sends a copy of the message to its paired broker in the other region. This copy is the mirror copy.
- A broker sends the message over a Core connection to one of 10 consumers. This is a push. The consumer does its own work. Then it sends an HTTP request to an API pod in its region.
- The API pod writes the event to the one Aurora writer:
INSERT … ON CONFLICT (event_id) DO NOTHING. A request from us-east-2 crosses the region for this write. - The writer commits the row. Aurora replicates the row to the readers.
- The API sends a 2xx response. The consumer then sends an acknowledgement (ack). The broker also mirrors the ack.
- If the API sends a 4xx or a 5xx response, the consumer rolls back. The broker delivers the message again, or moves it to the dead-letter address.
For an event, the API by default only writes. With Read before write on, the API first reads its local reader. If the row is already there, the API sends 200 and does not write. Refer to 3.18.
The read path is separate from this message path. It only reads:
- Center or Cube sends a query to the Query API of its region. Center and Cube are two separate deployments.
- A Query API pod sends the query through the ALB to an API pod. The consumers use the same ALB and the same API pods.
- The API pod reads the reader of its region.
- The answer goes back on the same path.
1.3Block diagram
Figure 1 does not show the read path. Section 3.22 shows it. Figure 1 also does not show the DLQ processor, the S3 bucket or the ALB between the consumers and the API. Section 3.21 shows them. In the app, they are on the outer side of each region.
1.4Regions, queues and database
Each region has its own address and queue. The mirror copies messages under the names of their source region. Thus each region also holds the queue of the other region as a copy.
| us-east-1 brokers | us-east-2 brokers | |
|---|---|---|
| Own traffic (consumed) | use1.address → use1.queue | use2.address → use2.queue |
| Standby copy (not consumed) | use2.queue | use1.queue |
The consumers in each region read only the queue of their region. The mirrored ack deletes a standby copy. A consumer reads a standby copy only after a failover. In the two “Same queue name” modes of the app, both regions use the queue name events.queue.
Each region also has an API and an Aurora cluster. There is only one writer for the two regions. The table shows the start state of the app.
| us-east-1 | us-east-2 | |
|---|---|---|
| API | e1-api: 1 to 10 pods (HPA) | e2-api: 1 to 10 pods (HPA) |
| Aurora cluster | Primary cluster e1-aurora: writer e1-1, reader e1-2 | Secondary cluster e2-aurora: reader e2-1 (read-only) |
| Writes go to | The writer e1-1, in the same region | The writer e1-1, across the region (cross-write) |
| Reads go to (queries from Center and Cube; consumer calls only with Read before write on) | e1-2 | e2-1 |
| Read path | e1-ui-center, e1-ui-cube, e1-query-api | e2-ui-center, e2-ui-cube, e2-query-api |
| Rows get to the readers by | Shared cluster storage (sim: 0.15 s) | Global Database replication aurora-repl (sim: Replication lag to us-east-2, default 0.8 s) |
After a failover, the writer can be e1-2 or e2-1. The two APIs then write to that instance. The counter writer in the Simulate panel shows the current writer.
1.5Colour code
The app and the diagrams in this manual use the same colours. In print, the diagrams use darker shades of the same colours.
| Colour | Meaning |
|---|---|
| Amber | Event (SNS or SQS message) |
| Amber ring | A pod asks SQS for a message (pull, HTTP). Refer to 1.7. |
| Violet | Avro message in Artemis (push, Core) |
| Cyan | Mirror copy, mirrored ack or Aurora replication |
| Teal | HTTP request from a consumer through the ALB to the API |
| Light blue | SQL read or write from an API pod to an Aurora instance; stored rows |
| White | Read query on its way out: UI, Query API, ALB, API. The answer comes back green. |
| Red-orange | Poison message (fails on each attempt) |
| Red | Dead-lettered, a 4xx or 5xx response, or failed |
| Orange | SQS queue and S3 bucket, objects in S3, a message on its way to S3, and the Center and Cube boxes |
| Periwinkle | Query API pods |
| Green | Processed: a 2xx response from the API. Also the answer to a query. |
| Light blue / pink / yellow | Connection pinned to broker 1 / broker 2 / broker 3 |
| Yellow (counter) | The “duplicates” counter in the Simulate panel |
Other display rules in the app:
- In the SQS queue, a bright cube is a visible message. A dim cube is a message in flight.
- A pod or a consumer shows red for a short time after a failure.
- A broker that is down shows grey. Its stored messages also show grey.
- Cubes on top of a broker are its stored messages. Violet cubes are messages of this region. Cyan cubes are mirror copies.
- A consumer that waits for the API shows teal (2D view).
- An API pod that starts shows hatched. A pod with requests in flight shows teal. Small marks on the pod show its in-flight requests (2D view).
- An Aurora instance shows its role (WRITER, READER or PROMOTING…), an ACU gauge, its busy slots and its queued operations. Light blue cubes are its rows. A hollow cube is a row that has not replicated to this instance yet (2D view). The writer also shows its writes per second.
- Center and Cube show the number of rows in their last answer. A short red outline shows a failed query.
- A Query API pod uses the same display rules as an API pod, in periwinkle.
1.6Simulation values and assumptions
The rules in the app come from AWS, KEDA, Kubernetes, PostgreSQL and Artemis documentation. The times, rates and counts are illustrative, and simulation time is compressed. The app changes these values so that you can see the effects quickly:
| Item | Documented value | Value in the app |
|---|---|---|
| SQS visibility timeout | 30 s default | 3 s Simulation |
SQS maxReceiveCount | Set by you (no default in the sources) | 3 Simulation |
Artemis max-delivery-attempts | 10 default | 3 Simulation |
Artemis redelivery-delay | 0 default | 0 to 5 s, default 0 (panel slider) Simulation |
| KEDA polling interval | 30 s default | 2 s Simulation |
| Pod replicas | KEDA default maximum 100 | 1 to 10 (maximum 10 to 50 in the panel) Simulation |
| HPA sync period | 15 s default | 3 s (HPA_POLL) Simulation |
| HPA scale-down stabilization window | 300 s default | 10 s (HPA_DOWN) Simulation |
| HPA metric and target | Set by you | In-flight + queued requests for each pod, target 1 (HPA_TARGET) Simulation |
| API replicas | Set by you (minReplicas, maxReplicas) | 1 to 10 for each region (maximum 10 to 50 in the panel) Simulation |
| Query API replicas | Set by you (minReplicas, maxReplicas) | 1 to 5 for each region (maximum 5 to 50 in the panel) Simulation |
| Time until a new API pod is ready | Depends on the image and the probes | 2 s (POD_START) Simulation |
| Requests at the same time in one API pod | Depends on the API | 2 (API_CONC) Simulation |
| Serverless v2 capacity range | 0.5 to 256 ACU, or 0 to 256 ACU with auto-pause (engine version) | 0.5 ACU to the Serverless v2 max capacity slider (1 to 32 ACU, default 8) Simulation |
| Reader lag in the same cluster | Usually much less than 100 ms | 0.15 s (LOCAL_LAG) Simulation |
| Global Database replication lag | Typically less than 1 s | 0.1 to 5 s, default 0.8 s (panel slider) Simulation |
| In-region writer failover | Typically less than 60 s, often less than 30 s | 3 s (DB_LOCAL_FAILOVER) Simulation |
| Global failover (promote the secondary) | Minutes (RTO) | 5 s (DB_PROMOTE) Simulation |
The model also uses these assumptions. The research notes give them because the original request does not specify them:
- Each region has one SNS topic, and the producer publishes in its region. The panel has a switch for one topic to both regions.
- Each region has 10 Artemis consumers.
- The mirror pairs are broker N in us-east-1 and broker N in us-east-2.
- The consumers get to the brokers through the same PrivateLink endpoint service as the pods.
- Each region uses its own address and queue names (
use1.*anduse2.*). A failover makes the standby copy available to the other region. - The API is after the Artemis consumers. The consumers call it over HTTP. Its HPA metric is in-flight + queued requests for each pod, with target 1.
- One Aurora writer serves the two regions. The us-east-2 API writes directly across the region. Write forwarding is the alternative.
- For an event, the API by default only writes. The read before write is an option (Read before write, off by default).
- Each region has one Center, one Cube and one Query API Deployment. The Query API calls the existing API through the existing ALB.
- By default, each region sends 1 query each second, and Center and Cube take turns. Each query goes to the next ready Query API pod. The app draws no load balancer in front of the Query API.
- A Cube query takes 4 times the time of a Center query on the reader, and costs 4 times the ACUs. A UI holds a maximum of 20 open queries. Above this limit, it skips the refresh.
- After a failover, the old us-east-1 instances come back as readers that already have all rows. A real failover builds the old primary volume again.
Serverless v2 scaling in the app is a simple demand rule. Each write costs 0.45 ACU-seconds. Each read and each Center query costs 0.15 ACU-seconds. Each Cube query costs 0.6 ACU-seconds. Each second, the app divides the demand by the current ACUs. Above 70%, the instance grows by 50% (at least 0.5 ACU). Below 30% for 3 polls, it shrinks by 0.5 ACU for each poll. Above 100%, each statement takes longer, up to 4 times. Real Serverless v2 scales on CPU, memory and network load. Refer to 3.19.
1.7Pull and push
The two queues give messages to their clients in different ways. SQS is pull: the client asks. Artemis is push: the broker sends.
| Item | SQS, to the pods | Artemis, to the consumers |
|---|---|---|
| Who starts a transfer | The pod. SQS never sends to a pod. [26] | The broker, after the consumer opens its connection once. [26] |
| Protocol | HTTP. Each ask is an HTTPS POST to the SQS endpoint. [26] | Core, the protocol of Artemis, over one long-lived TCP connection. [27] |
| One ask or transfer | ReceiveMessage gets 1 to 10 messages. The default is 1. [26] | The broker sends to a client buffer until it holds consumerWindowSize. The default is 1 MiB. [27] |
| Empty queue | The answer has no message, and the pod asks again. WaitTimeSeconds above 0 holds the answer open (a long poll). [26] | The connection stays open. The broker sends when a message comes. |
| Slow client | The client asks only when it is ready. | The client buffer holds messages that other consumers could take. Set consumerWindowSize to 0 to stop this. [27] |
| In the app | A PULL · HTTP label and an arrow from the pods to SQS. A hollow ring is an ask. | A PUSH · Core label and an arrow from the brokers to the consumers. There are no rings. |
Simulation The rings are only a display. The app gives a pod its message with no time for the ask, and it gives a consumer one message at a time, as with a small consumerWindowSize. Thus the pull and the push move at the same speed in the app. The app does not show the cost of empty polls or of a full client buffer.
2Components
This section has one subsection for each part that you can click in the app. A part that exists in each region has one subsection for the two regions. The part id is the id that the app uses, for example in the URL parameter pin.
The prefix e1- identifies a part in us-east-1. The prefix e2- identifies a part in us-east-2. Use the “Show in app” links to open the part in the app with its info card.
Parts of the “Both regions (live)” view
2.1Producer
The producer calls the SNS Publish action. In the default model, each region has its own topic, and the producer publishes to the topic in its region. In one-topic mode, the producer publishes to the us-east-1 topic. Each subscribed queue in the two regions then gets its own copy.
In the simulation, the producer sends odd-numbered events to us-east-1 and even-numbered events to us-east-2. When a region is down, the producer sends to the topic in the other region. Simulation
2.2AWS region
The plate holds all parts that run in one region. Each region has its own SQS queues, DLQs, consumer pods, PrivateLink endpoint, Artemis cluster, API and Aurora cluster.
The Aurora cluster in us-east-1 is the primary cluster. It holds the one writer. The Aurora cluster in us-east-2 is the read-only secondary cluster. Its API sends writes to the writer in us-east-1.
2.3SNS topic
SNS sends a copy of each published message to each subscription. This is fan-out. SNS can also send to SQS queues in other regions.
Raw message delivery removes the SNS JSON envelope. The SQS body is then the original message.
| Data | Value |
|---|---|
| Retries to SQS | 100,015 attempts over 23 days (fixed policy) [4] |
| Subscription DLQ | Optional, for each subscription. It must be in the same account and region as the subscription. [4] |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: SQS queue, another team’s queue, producer. Operation: 3.2.
2.4Another team’s queue
This queue is a second subscription on the same topic. It gets a full copy of each event. Our queue and this queue do not affect each other.
In the app, this queue becomes empty slowly. The consumers of this queue are not part of the model. Simulation
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: SNS topic.
2.5Cross-region subscription
This subscription exists only in one-topic mode (Fan-out: 1 topic → both regions). The us-east-1 topic then also sends each event to the us-east-2 queue. SNS supports cross-region delivery to SQS. As a result, both regions process each event.
Show in app: 3D · 2D (visible in one-topic mode). Related: SNS topic, SQS queue. Operation: 3.3.
2.6SQS queue
The SQS queue keeps each event until a pod deletes it. SQS is pull: it never sends to a pod. A pod asks with ReceiveMessage, an HTTP request, and the answer carries the message. Refer to 1.7. When a pod receives a message, SQS does not delete the message. SQS makes the message invisible for the visibility timeout. If the pod does not delete the message, the message becomes visible again.
Standard queues give at-least-once delivery with best-effort order. In the app, bright cubes are visible messages and dim cubes are messages in flight.
| Data | Value |
|---|---|
| Delivery | Pull, over HTTPS [26] |
ReceiveMessage | 1 to 10 messages, default 1 [26]. App: 1 Simulation |
WaitTimeSeconds | 0 to 20 s. Above 0 is a long poll [26] |
| Visibility timeout | 30 s default, range 0 to 43,200 s [6]. App: 3 s Simulation |
| Retention | 4 days default, 14 days maximum [6] |
maxReceiveCount | App: 3 Simulation |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: SQS DLQ, pods, SNS topic. Operation: 3.4.
2.7SQS dead-letter queue
SQS moves a message to this queue when the ReceiveCount of the message is more than maxReceiveCount. This occurs when pods receive the message many times and do not delete it.
The DLQ must be in the same account and region as the source queue. It must also be the same queue type.
| Data | Value |
|---|---|
| Trigger | ReceiveCount > maxReceiveCount [6] |
| Retention advice | Set the DLQ retention to more than the source retention. |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: SQS queue. Operation: 3.11.
2.8SQS consumer pods (KEDA)
Each pod pulls from SQS: it asks over HTTP, and the answer carries the message. The pod puts the body into an Avro record and sends the record to Artemis over Core. The pod deletes the SQS message only after Artemis confirms the send.
KEDA uses the aws-sqs-queue scaler. The scaler sets the number of replicas to approximately ceil((visible + in-flight messages) ÷ queueLength).
In the app, an idle pod is grey and a busy pod is light violet. A hollow ring from a pod to SQS is an ask. An idle pod continues to ask. A busy pod does not ask. In the 2D view, click one pod to see its pinned broker and its state.
| Data | Value |
|---|---|
queueLength | 5 messages for each pod (KEDA default) [9] |
| Replicas | KEDA default maximum 100 [9]. App: 1 to 10 (maximum 10 to 50 in the panel) Simulation |
| KEDA polling interval | 30 s default [9]. App: 2 s Simulation |
| KEDA cooldown period | 300 s default [9]. The app has no cooldown. Simulation |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: SQS queue, PrivateLink endpoint, NLB. Operation: 3.4, 3.5.
2.9PrivateLink interface endpoint
The endpoint is a set of ENIs with private IP addresses in the consumer VPC. Traffic goes privately to the endpoint service in the broker VPC. A Network Load Balancer is in front of the endpoint service.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: NLB, pods.
2.10Network Load Balancer
The NLB selects a broker for each TCP connection with a flow hash. The connection stays on that broker for its full life. The NLB does not balance individual messages.
In the app, the colour of each line shows the broker of that connection.
| Data | Value |
|---|---|
| Target selection | Flow hash of protocol, source and destination IP address and port, and TCP sequence number [8] |
| TCP idle timeout | 350 s default, range 60 to 6000 s [8] |
| Cross-zone load balancing | Off by default [8] |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: PrivateLink endpoint, brokers. Operation: 3.6.
2.11Artemis cluster
The cluster has three brokers with cluster connections between them. Each broker stores only its own messages. Thus three brokers give more capacity, not three copies.
With message-load-balancing=ON_DEMAND (the default), each message that arrives goes round-robin to the brokers whose queue has consumers. The broker that receives the message is one of these brokers. This rule spreads the messages across the brokers. The NLB spreads only the connections.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: brokers. Operation: 3.8.
2.12Brokers 1, 2 and 3 (each region)
Each broker holds these items:
- The multicast address of its region and the queue that its region consumes. In us-east-1, this is
use1.address→use1.queue. In us-east-2, this isuse2.address→use2.queue. - The standby copy of the queue of the other region. The mirror fills this queue. Consumers do not read it until the other region fails.
- A dead-letter address.
- A mirror connection to the broker with the same number in the other region.
Cubes on top of a broker are its stored messages. Violet cubes are messages of this region. Cyan cubes are mirror copies. In the 2D view, click a broker to see its status, its number of stored messages and its mirror backlog.
Show in app: us-east-1 B1 3D · 2D, B2 3D · 2D, B3 3D · 2D; us-east-2 B1 3D · 2D, B2 3D · 2D, B3 3D · 2D. Related: cluster, mirror, NLB, Artemis DLQ.
2.13Artemis dead-letter queue
A consumer rolls back when the API sends a 4xx or a 5xx response:
- 422: the API always rejects the payload (poison).
- 500: a transient error.
- 503: the database cannot do the write (or the read). For example, the writer is down or a promotion is in progress.
Each rollback is a failed delivery. The broker waits for redelivery-delay and then delivers the message again. After max-delivery-attempts failed deliveries, the broker moves the message to the dead-letter-address.
A 422 never succeeds, so the message must go to the dead-letter address. A 5xx usually succeeds on a later attempt. Thus redelivery-delay is important during a database failover. Refer to 3.20.
If no dead-letter address is set, Artemis discards the message. The message is then lost.
| Data | Value |
|---|---|
max-delivery-attempts | 10 default [14]. App: 3 Simulation |
redelivery-delay | 0 default [14]. App: 0 to 5 s, default 0 (slider Artemis redelivery-delay) Simulation |
| No dead-letter address set | The message is discarded [14] |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: consumers, brokers, API. Operation: 3.12, 3.15, 3.20.
2.14Artemis consumers ×10
Ten consumers compete for the messages on the queue. Each message goes to one consumer. Artemis is push: each consumer opens one Core connection through PrivateLink, the NLB pins the connection to one broker, and the broker sends messages down it. Refer to 1.7.
A consumer does its own work (Consumer work time). Then it sends an HTTP request through the ALB to the API in its region. The consumer keeps the message until the response comes back. A 2xx response causes an ack. A 4xx or 5xx response causes a rollback.
KEDA has an artemis-queue scaler. In this model, the number of replicas is fixed: 10 for each region by default, and 10 to 50 with the Artemis consumer count control under Capacity (per region). In the 2D view, click one consumer to see its pinned broker and its state: idle, processing or standby. A consumer that waits for the API shows teal.
| Data | Value |
|---|---|
| Delivery | Push, over Core [27] |
consumerWindowSize | 1 MiB default [27]. App: one message at a time Simulation |
| Replicas | 10 for each region (model assumption) |
| Calls | The API in its region (HTTP) |
| On 2xx | Ack: the broker deletes the message |
| On 4xx or 5xx | Rollback → redelivery → dead-letter address |
| Work time before the API call | App: 0.1 to 5 s, default 0.5 s Simulation |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: API, Artemis DLQ, cluster. Operation: 3.15.
2.15PrivateLink endpoint (consumer side)
The Artemis consumers get to the brokers in the same way as the pods. They use an interface endpoint in their VPC to the endpoint service. The app shows this endpoint on the consumer side to make the diagram clear. If the consumers and the pods are in the same VPC, it can be the same endpoint.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: NLB (consumer connections), consumers.
2.16NLB (consumer connections)
This part is the Network Load Balancer of the endpoint service, seen from the consumer side. Usually it is the same NLB that the pods use. The NLB hashes the TCP connection of each consumer to one broker, and the connection stays there. Thus the consumers can have an unequal spread. A broker can also have no consumers.
| Data | Value |
|---|---|
| Balances | TCP connections, not messages |
| Stickiness | Life of the connection |
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: PrivateLink endpoint (consumer side), NLB, consumers.
2.17Mirror connections B1, B2 and B3
Each broker mirrors to one broker in the other region: B1 to B1, B2 to B2 and B3 to B3. By default, the mirror copies sends and acks asynchronously. With sync=true, clients wait for the mirror.
When the target is not available, mirror events wait in the queue $ACTIVEMQ_ARTEMIS_MIRROR_<name>. The mirror copies only the messages that arrive after you configure the mirror.
| Data | Value |
|---|---|
| Default | Async [12] |
| Mirrored | Sends, acks, queue create and delete [1] |
| Documentation advice | Do not have active consumers on both sides [1] |
Show in app: B1 3D · 2D, B2 3D · 2D, B3 3D · 2D. Related: brokers. Operation: 3.9, 3.10.
Parts of the “Both regions (live)” view: API and Aurora
These parts are after the Artemis consumers. The 2D view shows live data for them when you click them.
2.18API
The API receives the HTTP request of each consumer through the ALB. It examines the payload. Then it stores the event in Aurora.
For an event, the API by default only writes. It sends INSERT … ON CONFLICT (event_id) DO NOTHING to the one writer. The us-east-2 API sends this write across the region, because the us-east-2 cluster is read-only. The unique key on event_id makes the write idempotent. If the row is already there, the write does nothing. The app calls this “dedupe at write”.
With Read before write on, the API first reads its local reader (e1-2 or e2-1). If the row is there, the API sends 200 and does not write. The app calls this “dedupe at read”. A reader can be behind the writer. Thus “not found” on a reader does not mean “not stored”.
| Response | Meaning | Consumer |
|---|---|---|
| 200 | Stored, or already stored (dedupe at read or at write) | Ack |
| 422 | The API always rejects this payload (poison) | Rollback; Artemis DLQ after 3 attempts |
| 500 | Transient error (Transient failures slider) | Rollback; a retry usually succeeds |
| 503 | The database is not available: the writer is down, a promotion is in progress, or (read before write) no instance is up in the region | Rollback; a retry after the failover succeeds |
The HPA sets replicas = ⌈current × metric ÷ target⌉. Here, the metric is in-flight + queued requests for each pod, and the target is 1. Thus desired pods = ⌈(in flight + queued) ÷ 1⌉, from 1 to 10 (the maximum is 10 to 50 with API max pods (HPA)). New pods are ready after 2 s. For scale-down, the HPA uses the highest recommendation of the last 10 s. It never removes a pod that has requests in flight.
The API also answers the queries of the read path. It gets them from the Query API through the ALB, and reads the reader of its region. These queries take request slots, and the HPA counts them.
The ALB sends each request to the next ready pod (round robin), and does not look at the load on that pod. Each pod does 2 requests at the same time. The other requests wait in the pod (counter API queued).
| Data | Value |
|---|---|
| Replicas | Set by you. App: HPA minimum 1, maximum 10 Simulation |
| HPA sync period | 15 s default [15]. App: 3 s Simulation |
| Scale-down stabilization | 300 s default [15]. App: 10 s Simulation |
| Scale-up stabilization | 0 s default: at once [15] |
| Tolerance | 10% default [15]. The app has no tolerance. Simulation |
| Each pod | App: 2 requests at the same time; ready 2 s after the HPA adds it Simulation |
| Work for each request | App: API time per request, 0.05 to 2 s, default 0.25 s Simulation |
| Reads | The local reader (e1-2 or e2-1), for each query of the read path. For a consumer call, only with Read before write on |
| Writes | The writer (e1-1 if no failover occurred) |
In the 2D view, click the API to see Ready pods, Starting, In flight, Queued requests and HPA desired.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: consumers, instance e1-1, instance e1-2, instance e2-1, cross-region write. Operation: 3.15, 3.16, 3.17, 3.18.
2.19Aurora cluster (primary and secondary)
Primary cluster (us-east-1). This is the only cluster that accepts writes. The writer e1-1 and the reader e1-2 share one cluster volume (six copies across three Availability Zones). Thus the reader is only milliseconds behind the writer. Nothing is copied: the reader reads the same storage. If the writer fails, Aurora promotes the reader. This is an in-region failover. The cluster endpoint follows the writer. The reader endpoint spreads the reads across the readers.
Secondary cluster (us-east-2). This cluster is a read-only copy of the primary. Global Database storage-level replication keeps it up to date, typically less than 1 s behind. It cannot accept writes. If us-east-1 is lost, Aurora can promote this cluster. This failover loses the rows that did not replicate yet (RPO). A planned switchover first waits until the secondary is up to date. Thus a switchover loses nothing.
| Data | Value |
|---|---|
| Instances in a cluster | One writer and up to 15 readers [21] [23] |
| Reader lag in the primary cluster | Usually much less than 100 ms [23]. App: 0.15 s Simulation |
| In-region failover | Typically less than 60 s, often less than 30 s [21]. App: 3 s Simulation |
| Secondary regions | Up to 10 for each global database, read-only [18] |
| Replication lag to the secondary | Typically less than 1 s [18]. App: slider, default 0.8 s Simulation |
| Promotion of the secondary | Failover (RPO more than 0, RTO minutes) or switchover (RPO 0) [19]. App: failover after 5 s Simulation |
| Capacity | Serverless v2, for each instance [16] |
In the 2D view, click a cluster to see Writer, Rows on the writer and, after a global failover, Rows lost at failover.
Show in app: primary 3D · 2D; secondary 3D · 2D. Related: e1-1, e1-2, e2-1, replication. Operation: 3.17, 3.20.
2.20Aurora instance e1-1
This instance is the writer at the start. Each INSERT from the two regions runs on it. Thus it is the busiest instance, and its ACU gauge increases first.
Its role label is live. If it fails (Kill writer), Aurora promotes e1-2 after 3 s. When you restart e1-1, it comes back as a reader. Switchover to e1-1 makes it the writer again.
| Data | Value |
|---|---|
| Capacity unit | ACU: 1 ACU ≈ 2 GiB of memory with matching CPU and network [16] |
| Range | 0.5 to 256 ACU; 0 ACU with auto-pause in recent versions [16] [17]. App: 0.5 ACU to the Serverless v2 max capacity slider Simulation |
| Scaling step | As small as 0.5 ACU, in seconds [16] |
| Parallel statements | App: approximately 2 for each ACU (slots = ACU × 2, rounded), 0.12 s for each statement Simulation |
In the 2D view, click an instance to see Role, Status, ACU now, Slots busy, Queued ops, Rows and, for a reader, Rows behind the writer. For the writer, you also see Writes per second: the average of the last 5 simulated seconds.
Show in app: 3D · 2D. Related: primary cluster, e1-2, replication, cross-region write. Operation: 3.17, 3.19, 3.20.
2.21Aurora instance e1-2
This reader gets each row from the shared cluster volume. It is only milliseconds behind the writer. It serves the queries of Center and Cube in us-east-1. With Read before write on, it also serves the reads of us-east-1: “Is this event stored?”.
It is the in-region failover target. If the writer fails, Aurora promotes it. A reader in promotion tier 0 or 1 keeps at least the capacity of the writer, so that it can take over. Readers in tiers 2 to 15 scale on their own load [16]. The app does not model promotion tiers. Simulation
| Data | Value |
|---|---|
| Lag behind the writer | Usually much less than 100 ms [23]. App: 0.15 s Simulation |
| Promotion tiers | 0 (first) to 15 [16] [21] |
| Capacity | Same rules as e1-1 |
Show in app: 3D · 2D. Related: primary cluster, e1-1, API. Operation: 3.17, 3.20.
2.22Aurora instance e2-1
This reader gets each row by Global Database replication. It serves the queries of Center and Cube in us-east-2. With Read before write on, it also serves the reads of us-east-2. It is behind the writer by the replication lag. Thus a row that the writer stored a moment ago is possibly not on it yet. For this reason, the API cannot remove duplicates with a read only.
It is the global failover target. If us-east-1 is lost and Promote us-east-2 if us-east-1 is lost is on, Aurora promotes it to writer after 5 s. The rows that did not replicate to it are lost.
The Kill writer button stops only the writer. To stop this reader, use the console: __sim.killDb('e2-1'). A stopped reader has no effect on the writes of the consumers. The queries of Center and Cube in us-east-2 then get 503, because the region has no instance to read from. With Read before write on, the consumer calls in us-east-2 also get 503.
| Data | Value |
|---|---|
| Lag behind the writer | Typically less than 1 s [18]. App: Replication lag to us-east-2, default 0.8 s Simulation |
| Writes | None until promoted [18] |
| Capacity | Same rules as e1-1 |
Show in app: 3D · 2D. Related: secondary cluster, replication, API. Operation: 3.18, 3.20.
2.23Global Database replication
Aurora copies the changes of the primary to the secondary region at the storage layer. It uses dedicated infrastructure, not the database engine. Thus the writer has almost no extra load.
The replication is asynchronous. A commit completes before the change gets to us-east-2. A failover loses the changes that are still in transit. This loss is the RPO. Aurora PostgreSQL can limit it with rds.global_db_rpo. This parameter stops commits while all secondaries are more behind than the limit.
In the app, the writer sends each committed row to each other instance that is up. A row becomes visible on an instance when its cyan particle arrives.
| Data | Value |
|---|---|
| Lag | Typically less than 1 s [18]. App: slider, default 0.8 s Simulation |
| Metric | AuroraGlobalDBRPOLag [19] |
| RPO limit | rds.global_db_rpo, 20 s minimum, not set by default [19] |
| Switchover | Waits for the secondary to be up to date: no data loss [19] |
Show in app: 3D · 2D. Related: primary cluster, secondary cluster, e1-1, e2-1. Operation: 3.18, 3.20.
2.24Cross-region write
The secondary cluster is read-only. Thus the us-east-2 API connects to the writer in us-east-1 for each INSERT. Each write pays for a cross-region round trip. In the app, a cross-region SQL operation travels for 0.6 s, and a local one for 0.25 s. Simulation
Connect through the global writer endpoint. It follows the primary after a switchover or a failover, so the connection string does not change. The alternative is write forwarding on the secondary: the us-east-2 reader sends the writes to the writer for you.
The counter cross-region writes counts the rows that an API stored on a writer in the other region. After a global failover, the writer is e2-1, and the us-east-1 API then writes across the region.
| Data | Value |
|---|---|
| Reason | Only the primary cluster accepts writes [18] |
| Endpoint | Global writer endpoint |
| Write forwarding | Aurora PostgreSQL 14.9+, 15.4+, 16+. Off by default. apg_write_forward.consistency_mode default SESSION [20] |
Show in app: 3D · 2D. Related: API, e1-1, secondary cluster. Operation: 3.17.
Parts of the “Inside Artemis” view
This view is a close-up of the us-east-1 cluster. It shows a fixed example: six pod connections pinned 3 / 2 / 1 and ten consumer connections pinned 5 / 5 / 0. The live simulation does not change this view.
2.25Pods that send Avro (Artemis producers)
Each pod opens a long-lived Core connection. The coloured cap shows the broker that the NLB pinned the connection to. In this example, six connections are pinned 3 / 2 / 1.
Show in app: 3D · 2D. Related: NLB, pinned TCP connections.
2.26Interface endpoint (ENIs)
The ENIs have private IP addresses in the client VPC. Traffic goes to the endpoint service in the broker VPC. The traffic does not go through the internet.
2.27Network Load Balancer (endpoint service)
The NLB selects a broker one time for each TCP connection with a flow hash. Then it sends all bytes of that connection to the same broker. A busy connection and an idle connection have the same weight. The NLB never examines individual messages.
| Data | Value |
|---|---|
| Hash inputs | Protocol, source and destination IP address and port, TCP sequence number [8] |
| Stickiness | Life of the connection [8] |
| TCP idle timeout | 350 s default [8] |
Show in app: 3D · 2D. Related: clients, pinned TCP connections, broker 1. Operation: 3.6.
2.28Pinned TCP connections
The colour of a line shows its broker: B1 is light blue, B2 is pink and B3 is yellow. Each message from a client goes along its connection to the same broker. A broker with more connections receives more messages. The NLB does not correct this imbalance.
Show in app: 3D · 2D. Related: NLB, cluster connections.
2.29Broker 1, broker 2 and broker 3
Each broker stores only the messages that the cluster routes to it. In this example, broker 1 and broker 2 each have 5 consumer connections.
Broker 3 has no consumer connections. With ON_DEMAND load balancing, broker 3 sends each message that arrives on it over the cluster bridge. The message goes to a broker that has consumers.
Show in app: B1 3D · 2D, B2 3D · 2D, B3 3D · 2D. Related: address, queue, cluster connections, mirror connections.
2.30Address use1.address (MULTICAST)
Producers send to the address, not to a queue. MULTICAST routing puts a copy of each message in each queue that is bound to the address (publish-subscribe). With one bound queue, that queue gets every message.
2.31Queue use1.queue
This queue is bound to the address. The consumers that are connected to this broker compete for its messages. Each message goes to one of them.
Show in app: 3D · 2D. Related: address, dead-letter address, consumers.
2.32Dead-letter address (for example “DLQ”)
Two address settings control this address: dead-letter-address and max-delivery-attempts (default 10). A message that rolls back that number of times moves to the dead-letter address.
If no dead-letter address is set, Artemis discards the message.
2.33Mirror queue $ACTIVEMQ_ARTEMIS_MIRROR_<name>
The broker first writes each send and each ack to this queue. Then the broker sends them asynchronously to the paired broker in us-east-2. If the target is down, mirror events collect in this queue. The broker sends them when the target is available again.
Show in app: 3D · 2D. Related: mirror connections, us-east-2 brokers. Operation: 3.9.
2.34Cluster connections (core bridges)
Brokers send messages to each other over internal bridges. With message-load-balancing=ON_DEMAND (default), each message that arrives goes round-robin to the brokers whose queue for that address has consumers. The broker that receives the message is included.
The parameter redistribution-delay controls the movement of stored messages when a queue loses its consumers. The default is −1, which means off.
| Data | Value |
|---|---|
message-load-balancing | ON_DEMAND (default) [13] |
redistribution-delay | −1 default, which means never [13] |
Show in app: 3D · 2D. Related: broker 3, pinned TCP connections. Operation: 3.8.
2.3510 consumers (fixed replicas)
The broker pushes to each consumer over its Core connection. In this example, the NLB pinned the ten consumers to broker 1 and broker 2 (5 + 5). Broker 3 has no consumers. This imbalance is the reason that ON_DEMAND load balancing and redistribution are important.
2.36Interface endpoint (consumer VPC)
The 10 consumers also connect through PrivateLink. Their connections go to the same endpoint service. Thus the same NLB pins each consumer connection to one broker.
Show in app: 3D · 2D. Related: NLB (consumer connections), consumers.
2.37NLB (consumer connections)
This part is the same Network Load Balancer, shown on the consumer side. It hashed the 10 consumer connections to broker 1 and broker 2 (5 + 5) and none to broker 3. Nothing rebalances these connections until the consumers reconnect.
| Data | Value |
|---|---|
| Balances | TCP connections, not messages |
Show in app: 3D · 2D. Related: interface endpoint (consumer VPC), consumers, broker 3, NLB.
2.38us-east-2 brokers (mirror targets)
Each us-east-1 broker mirrors to one us-east-2 broker, and the reverse. The copies keep their names. Thus us-east-2 holds use1.address → use1.queue as a standby copy, next to its own use2.queue.
The us-east-2 consumers read only use2.queue. They attach to use1.queue only after a failover. Configure the address and queue of the other region on each broker. Then a copy always has a queue to go to.
Show in app: 3D · 2D. Related: mirror connections, mirror queue. Operation: 3.10.
2.39AMQP mirror broker connections
The mirror copies sends, acks and queue creation under the same names. A use1.* name in us-east-1 stays use1.* in us-east-2. There is one connection for each pair of brokers.
By default, the mirror is async: the send of the client completes before the copy gets to us-east-2. With sync=true, clients wait for the mirror. Clients then also stop when the mirror is disconnected.
| Data | Value |
|---|---|
| Mirrors | Sends, acks, address and queue create and delete [1] |
| Default | Async [12] |
| Messages from before the mirror | Not copied when you first enable the mirror [1] |
Show in app: 3D · 2D. Related: mirror queue, us-east-2 brokers. Operation: 3.9.
Parts of the DLQ lane
These parts are on the outer edge of each region in the “Both regions (live)” view.
2.40DLQ processor
The processor is a consumer on the dead-letter queue, so the broker pushes to it over Core, as for the other consumers. It takes a message, writes it to S3 with PutObject, and acks it only after S3 sends 200.
Each broker keeps its own dead-lettered messages, and one connection goes to one broker. Thus a real processor must have redistribution-delay on the DLQ address or one connection to each broker. Simulation The app reads from each broker that is up, and it draws the connection as a straight line. A message on a broker that is down stays there until the broker restarts.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: Artemis DLQ, S3 bucket.
2.41S3 bucket dlq-archive
The bucket keeps each message from the Artemis DLQ as one object. You can read a message and send it again later.
The key comes from the message id, for example dlq/artemis/us-east-1/<id>. A second PUT of the same message overwrites the object. It does not make a second object.
S3 is strongly consistent: after a 200, a read gets the new object. Objects stay in their region if you do not set replication. Simulation The two buckets do not replicate. S3 fails only when its region has an outage.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: DLQ processor.
Load balancer in front of the API
This part is between the API and its callers (the consumers and the Query API) in each region, in the “Both regions (live)” view.
2.42Application Load Balancer (ALB)
The ALB selects a target for each HTTP request, not for each connection. Thus a keep-alive connection does not tie a caller to one API pod. The NLB is different: it pins a full connection to one broker.
The routing algorithm is round robin, the ALB default. The ALB sends each request to the next healthy pod in the sequence. It does not look at the load on that pod. A slow pod gets its full share, and the extra requests wait in that pod. A new pod gets its full share when it is healthy.
If a pod stops before its deregistration completes, the consumer gets a 5xx response. This rollback uses one delivery attempt of the message. Let pods drain before they stop. If not, a deployment can send healthy messages to the Artemis DLQ.
| Data | Value |
|---|---|
| Routing algorithm | Round robin (default). Least outstanding requests and weighted random are also available. [25] |
| Deregistration delay | 300 s default [25] |
Simulation The targets are the pods that are ready. Each pod serves 2 requests at a time, and the other requests wait in the pod. The ALB adds no latency and no errors of its own.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: Query API, Artemis consumers, API.
Parts of the read path
These parts are on the outer side of each region, in the “Both regions (live)” view. They only read. Set Show to Ingestion only to hide them. Simulation The diagram puts one of each part in each region. This is a model choice.
2.43Center
Center is one of the two UIs. It is a separate deployment from Cube, so it has its own release and its own failures. It sends queries to the Query API in its region. It does not write.
Simulation The rate is Center queries, 0 to 5 queries/s in each region, default 0.5. At the default rates, each region sends 1 query each second, and Center and Cube take turns. A UI holds a maximum of 20 open queries. Above this limit, it skips the refresh. The label shows the number of rows that the UI saw in its last answer.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: Query API.
2.44Cube (analytics)
Cube is the analytics UI. It is a separate deployment from Center, so it has its own release and its own failures. It sends queries to the same Query API. It does not write.
Simulation The rate is Cube queries (analytics), 0 to 3 queries/s in each region, default 0.5. An analytics query takes 4 times the time of a Center query on the reader, and costs 4 times the ACUs. These two ratios are a model choice. They are not measurements.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: Query API.
2.45Query API
Both UIs get their data from the Query API. The Query API has no database connection. It calls the existing API through the ALB of its region. The existing API then reads the reader of its region.
The Query API scales like the existing API. An HPA sets from 1 pod to the maximum, from the in-flight and queued queries for each pod. A pod holds its request slot until the existing API answers. Thus the maximum also limits the number of reads that get to the existing API at one time.
Reads and ingestion use the same pods of the existing API. A query takes a request slot, and the HPA of the existing API counts it. Set the API maximum for both. Scenarios 40 and 41 in 6.3 compare the two.
Simulation Each pod serves 2 queries at a time, and the other queries wait in the pod. A new pod is ready after 2 s. Each query goes to the next ready pod. If no reader is up in the region, the API reads the writer if the writer is in that region. If no instance is up, the query gets a 503 response.
Show in app: us-east-1 3D · 2D; us-east-2 3D · 2D. Related: ALB.
3Operation
This section tells how the system operates. Each subsection agrees with one step of the guided tour in the app. Each subsection also tells what the tour step does to the simulation.
A tour step can change simulation settings. When the step ends, the app sets these settings back to their values from before the step.
3.1One event, end to end (tour step 1)
An event leaves the producer and goes to the SNS topic of its region. SNS puts the event in the SQS queue of that region. A pod converts the event to Avro. The pod sends the Avro message through PrivateLink to one of three Artemis brokers. One of 10 consumers takes the message and calls the API of its region. The API stores the event in Aurora PostgreSQL: one writer in us-east-1, and a reader in each region.
Use the colours to follow the message. Amber is the event. Violet is the Avro message. Teal is the HTTP call to the API. Light blue is SQL to Aurora. Cyan is a copy between the regions. Green is a processed message.
Simulation in this step: publish rate 1.2 events/s, clock not paused.
3.2SNS fan-out: each subscription gets a copy (tour step 2)
SNS does not give an event to only one queue. SNS sends a copy to each subscription. Our queue and the queue of the other team each get one copy. The two copies do not affect each other.
SNS retries delivery to SQS: 100,015 attempts over 23 days. A subscription can also have its own DLQ for deliveries that fail.
SNS can retry only while it operates. A region outage also stops the SNS topic in that region. Simulation A publish that is in transit gets an error, and the producer tries again. A message that SNS accepted before the outage waits, and SNS delivers it when the region is available again.
Simulation in this step: publish rate 0.8 events/s.
3.3One topic or a topic per region (tour step 3)
One topic in us-east-1 can have subscribed queues in the two regions. SNS supports cross-region delivery to SQS. Then each event goes to us-east-1 and to us-east-2. One event then gives two Avro messages.
With a topic in each region, the producer publishes to the topic of its own region. Each event then goes to one region. Look at the counter deliveries / event in the Simulate panel. Use the Fan-out switch to compare the two modes.
One topic with queues in the two regions causes two times the processing for each event. Make sure that this is the behaviour that you want.
Simulation in this step: Fan-out set to 1 topic → both regions, publish rate 1.2 events/s.
3.4Receive, convert, send, then delete (tour step 4)
A pod pulls a message: it asks SQS with ReceiveMessage, an HTTP request, and the answer carries the message. In the app, the hollow ring is the ask. SQS does not delete the message. The message becomes invisible for the visibility timeout. In the app, the cube of the message becomes dim.
The pod wraps the SQS body in an Avro record. Avro is a compact binary format without field names. Thus a reader needs the schema of the writer, usually from a schema registry. In the Confluent wire format, a record starts with a 0x00 magic byte and a 4-byte schema id.
The pod sends the record to Artemis over Core. The pod deletes the SQS message only after the broker confirms the send. If the pod stops between the send and the delete, the message becomes visible again. A pod then sends it again. For this reason, the pipeline is at-least-once.
Simulation in this step: publish rate 2.5 events/s.
3.5KEDA adds pods when the queue grows (tour step 5)
The aws-sqs-queue scaler counts the visible and the in-flight messages. It targets queueLength messages for each pod. The default queueLength is 5. For example, 30 messages give 6 pods.
In this step, a burst of 30 events arrives. The pod grid grows. Then the grid becomes smaller when the queue is empty.
Real KEDA polls every 30 s by default. The simulation polls every 2 s so that you can see the change. The simulation keeps the number of pods between 1 and 10. Simulation
aws-sqs-queue scaler: pods = ceil(messages ÷ 5), from 1 to 10 in the app. The KEDA default maximum is 100 replicas.Simulation in this step: publish rate 0.6 events/s and one burst of 30 events.
3.6The NLB balances connections, not messages (tour step 6)
PrivateLink puts a Network Load Balancer in front of the brokers. The NLB hashes each new TCP connection to one broker. The connection stays on that broker for its full life.
In the example, six pods have connections pinned 3 / 2 / 1. Thus half of all messages arrive at broker 1. The consumers also connect through PrivateLink, and the NLB pins them in the same way: 5 / 5 / 0. Broker 3 has no consumers.
The NLB never divides the messages of one connection. An even spread of messages across the brokers comes from the Artemis cluster (3.8).
Simulation in this step: the “Inside Artemis” view shows six pod connections in the colours of their brokers.
3.7Address → queue → one consumer (tour step 7)
Pods in us-east-1 send to the address use1.address, not to a queue. The routing type of the address is MULTICAST. Thus Artemis puts a copy of each message in each queue that is bound to the address.
Only use1.queue is bound, so it gets every message. us-east-2 has its own use2.address → use2.queue. The consumers on use1.queue compete, and each message goes to one consumer. The broker pushes the message over the Core connection of the consumer, with no ask for each message. In step 4, a pod asks each time. Refer to 1.7.
Simulation in this step: an animated Avro message goes from a pod to use1.address, then to use1.queue, then to one consumer.
3.8The cluster spreads messages across brokers (tour step 8)
Each broker stores only its own messages. Three brokers give more capacity, not three copies.
The parameter message-load-balancing=ON_DEMAND is the default. With it, a broker sends each message that arrives round-robin to each broker whose queue has consumers. The broker that receives the message is included. This rule, not the NLB, spreads the messages equally.
In the example, the NLB pinned all consumers to broker 1 and broker 2. Thus messages alternate between broker 1 and broker 2. A message that arrives on broker 3 always crosses a bridge, which adds one hop.
A stored message on a broker that loses its consumers moves only if redistribution-delay is 0 or more. The default is −1, which means never.
Simulation in this step: an animated message arrives on broker 3, crosses the bridge to broker 1 and goes to a consumer.
3.9Mirror: an async standby copy in the other region (tour step 9)
Broker 1 writes each send to its mirror queue. Then it sends the event asynchronously to broker 1 in us-east-2. The copy keeps its names. It goes into use1.address → use1.queue on us-east-2, next to use2.queue. No consumer reads it there.
When a consumer in us-east-1 acks the message, the broker also mirrors the ack. The ack removes the copy in us-east-2.
The send of the client completes before the copy gets to us-east-2. If us-east-1 fails in this gap, us-east-2 does not have the message. The message stays in us-east-1 until recovery.
With sync=true, clients wait for the mirror. This adds latency. It also blocks clients while the mirror is disconnected.
Simulation in this step: the animation shows the send, the copy to us-east-2 and the mirrored ack that deletes the copy. Use the Mirror lag slider to change the delay of the mirror (default 0.7 s Simulation).
To see the pipeline with no copies, set Mirrored queues on to off in the Simulate panel. The tour keeps it on. Scenarios 44 to 51 in 6.3 show what changes.
3.10Per-region queue names keep the copies on standby (tour step 10)
Each region consumes only its own queue. us-east-1 reads use1.queue, and us-east-2 reads use2.queue. The cyan copies stay in the queue of the other region, and nobody reads them. The mirrored ack deletes them. The duplicates counter stays at 0.
use1.*, use2.*). The copy keeps the name of its source region. Thus it lands in a queue that the local consumers do not read.The names are the key. Suppose that the two regions use one address and queue name. Then each copy goes into the queue that the consumers of the other region read. An idle consumer takes the copy before the ack arrives. Each message is then processed two times.
Do not use one queue name with active consumers in the two regions. The mirror then causes duplicate processing. The Artemis documentation also tells you not to have active consumers on both sides.
use1.queue, and stage 4 does not occur.To see the race, set Artemis consumers to Same queue name, both regions consume.
Simulation in this step: Fan-out Topic per region, Artemis consumers Per-region queues, publish rate 2 events/s.
3.11A message that fails each time goes to the SQS DLQ (tour step 11)
In this step, 20% of events have a payload that the pod cannot convert to Avro. These events show red-orange. The step also sends one such event at the start.
A pod that fails does not call DeleteMessage. Thus the message becomes visible again after the visibility timeout, and a pod receives it again. When ReceiveCount is more than maxReceiveCount, SQS moves the message to the DLQ. The value of maxReceiveCount is 3 in the app. Refer to Figure 5 for the redrive path.
Set the visibility timeout to more than the worst-case time of a pod. This time includes the Avro conversion and the Artemis send. If the timeout is too short, healthy messages become visible again, and pods receive them two times.
Simulation in this step:
- Poison: Avro can’t be built: 20%
- Publish rate: 1.5 events/s
- Fan-out: Topic per region
- One poison event (Send 1 poison → SQS DLQ)
3.12Downstream failures: redelivery, then the dead-letter address (tour step 12)
In this step, 20% of events are events that the API always rejects with 422. These events show red-orange in the brokers. The step also sends one such event at the start.
Each 4xx or 5xx response is a rollback, and the broker delivers the message again. After max-delivery-attempts, the broker moves the message to the dead-letter address. The default is 10. The value in the app is 3.
Examine your address-settings. If no dead-letter-address is set, Artemis discards the message.
redelivery-delay before each new delivery.Simulation in this step:
- DLQ processor on: off, so that the messages stay in the DLQ
- Poison: API always rejects (422): 20%
- Publish rate: 1.5 events/s
- Fan-out: Topic per region
- Artemis consumers: Per-region queues
- One poison event (Send 1 poison → Artemis DLQ)
3.13A broker fails: connections move, stored messages stay (tour step 13)
Broker 1 in us-east-1 stops. Its clients reconnect, and the NLB pins them to the brokers that are still up. The connection lines move.
The messages that broker 1 already stores stay on it until it starts again. Their mirror copies in us-east-2 are the redundancy. Mirror events for broker 1 wait in a queue. They go when broker 1 is back.
When broker 1 starts again, the current connections stay where they are. Thus broker 1 gets no consumers until the clients reconnect or redistribution is on. Use the Chaos buttons to do this yourself.
Simulation in this step: publish rate 2.5 events/s, Fan-out Topic per region, Artemis consumers Per-region queues. The step stops broker 1 in us-east-1. When the step ends, the app restarts broker 1 and reconnects the clients.
3.14us-east-1 fails: us-east-2 takes over the standby copy (tour step 14)
us-east-1 is down. The producer publishes to the topic in us-east-2. The us-east-2 consumers also attach to use1.queue, the standby copy. They process the messages that us-east-1 did not complete. The event log shows these changes. The database also fails over. 3.20 tells about that part.
An async mirror has two edge conditions:
- A message that us-east-1 accepted just before the failure possibly has no copy yet. The message stays on the disk of us-east-1 until recovery.
- us-east-1 processed a message, but the ack did not get to us-east-2. Then us-east-2 processes the message again.
A failover can cause duplicate processing. Make sure that the API removes duplicates with the event id (refer to 3.18).
When this step ends, us-east-1 starts again, and the consumers fail back. A switchover then moves the Aurora writer back to e1-1.
Simulation in this step: publish rate 2 events/s, Fan-out Topic per region, Artemis consumers Per-region queues, Artemis redelivery-delay 2 s. After 1.5 s, the step starts a us-east-1 region outage. When the step ends, the app ends the outage and starts a switchover to e1-1.
3.15Consumers call the API; the ack waits for its answer (tour step 15)
A consumer does not complete a message alone. After its own work, it sends an HTTP request (teal) through the ALB to an API pod in its region. The ALB selects the next pod with round robin, for each request. The consumer keeps the message until the response comes back.
- 2xx (green): the consumer acks, and Artemis deletes the message.
- 4xx or 5xx (red): the consumer rolls back. The broker delivers the message again. After
max-delivery-attempts(3 here), the broker moves it to the dead-letter address.
In this step, one event that the API always rejects with 422 is sent at the start. Follow it into the Artemis DLQ. 2.18 gives all API responses.
Send the ack only after a 2xx response. If the consumer acks before the API answers, a failed write loses the event.
Simulation in this step:
- Publish rate: 1.5 events/s
- Fan-out: Topic per region
- Artemis consumers: Per-region queues
- One poison event (Send 1 poison → Artemis DLQ)
3.16The HPA adds API pods when requests collect (tour step 16)
The API starts with 1 pod in each region. At each poll, the HorizontalPodAutoscaler calculates desired = ⌈current × metric ÷ target⌉. Here, the metric is in-flight + queued requests for each pod, and the target is 1. Thus 6 waiting requests give 6 pods (maximum 10).
In this step, a burst of 40 events arrives, and each request takes 0.6 s. The API grid grows. A new pod is ready 2 s after the HPA adds it. Scale-down occurs only when the demand stays lower for 10 s. The two regions scale independently.
Real HPA syncs every 15 s and waits 300 s before it scales down. The simulation uses 3 s and 10 s so that you can see the change. Simulation
More API pods send more statements to the one writer. Set maxReplicas to a value that the database can serve.
Simulation in this step: publish rate 1 event/s, API time per request 0.6 s, Fan-out Topic per region, Artemis consumers Per-region queues, and one burst of 40 events.
3.17One writer for the two regions (tour step 17)
The database is an Aurora PostgreSQL Global Database. The primary cluster in us-east-1 has the writer e1-1 and the reader e1-2. The secondary cluster in us-east-2 has one reader, e2-1. The secondary cluster is read-only.
Each API pod writes to the one writer (light blue): INSERT … ON CONFLICT (event_id) DO NOTHING. The readers get each row by replication (cyan). They serve the reads of their region. Thus each us-east-2 write crosses to us-east-1 and pays for a round trip. Look at the counter cross-region writes in the Simulate panel.
The alternative in Aurora is write forwarding. The us-east-2 reader then sends the writes to the writer for you. The app does not model write forwarding. Simulation
Refer to Figure 1 and to the table in 1.4 for the paths. 2.24 gives the data for the cross-region write.
Simulation in this step: publish rate 2 events/s, Fan-out Topic per region, Artemis consumers Per-region queues.
3.18Replica lag: the read can miss, the unique key cannot (tour step 18)
In this step, the two regions consume the same queue name (as in 3.10). Thus most events arrive two times, one time in each region. The replication to us-east-2 is now 3 s behind (usually less than 1 s). The step also sets Read before write to on. This switch is off by default.
The API first reads its local reader. If the row is there, the API sends 200 and does not write. This is dedupe at read. But e2-1 is behind the writer. Thus “not found” does not mean “not stored”.
The write catches the remaining duplicates: INSERT … ON CONFLICT (event_id) DO NOTHING on the writer. This is dedupe at write. The counter rows stored stays equal to the unique events. The read is only a shortcut. The unique key makes the API idempotent.
Do not remove duplicates with a read on a replica only. A replica can be behind the writer, and two deliveries in different regions can both miss the row. Put a unique key on event_id and use ON CONFLICT DO NOTHING on the writer.
Scenario 17 in 6.3 shows the result: 120 events, 120 duplicate deliveries, 120 rows. 35 duplicates stop at the read and 85 stop at the write.
Simulation in this step: publish rate 2 events/s, Fan-out Topic per region, Artemis consumers Same queue name, both regions consume, Replication lag to us-east-2 3 s, Read before write on.
3.19Serverless v2: capacity follows the load in 0.5-ACU steps (tour step 19)
Each Aurora instance has its own capacity in ACUs. 1 ACU is approximately 2 GiB of memory with matching CPU and network. Aurora scales each instance between your minimum and your maximum. The steps are as small as 0.5 ACU, and they occur in seconds. Aurora does not drop connections when it scales.
In the app, the range is 0.5 to 8 ACU by default. Serverless v2 max capacity can set the maximum to 32 ACU. An instance runs approximately 2 statements at the same time for each ACU. The real range is up to 256 ACU, and down to 0 with auto-pause in recent versions.
In this step, the rate is 8 events/s. The ACU gauge of the writer increases from 0.5, because each write from the two regions goes to it. The readers stay small. If an instance gets to its maximum, its statements wait and become slower. Each API request then takes more time. To see this, set Serverless v2 max capacity to 1 ACU.
The tour hides the read path in this step. With the read path on, the readers do not stay small: the queries of Center and Cube add load on them, and a Cube query costs 4 times a Center query.
The ACUs of an instance set two things in the app: the number of statements that it runs at the same time, and how much each statement slows down at high load. The app does not model the cost of an ACU, the memory cache or the connection limit.
| App rule (each 1 s) | Value |
|---|---|
| Cost of one statement | Write 0.45 ACU-seconds (ACU_WRITE), read 0.15 ACU-seconds (ACU_READ) |
| Utilization | (cost of the statements that started in the last second + queued statements × 0.15) ÷ current ACU |
| Scale up | Utilization more than 70% (ACU_UP): ACU + 50%, minimum + 0.5, rounded to 0.5, up to the maximum |
| Scale down | Utilization less than 30% (ACU_DOWN) for 3 polls: − 0.5 ACU for each poll, down to 0.5 |
| Parallel slots | ACU × 2, rounded, minimum 1 |
| Statement time | 0.12 s (DB_OP) × utilization, from 1 to 4 times (DB_SLOW) |
The app rule is a simple model. Real Serverless v2 scales on CPU, memory and network load. A reader in promotion tier 0 or 1 also keeps at least the capacity of the writer. Simulation
The event log shows a change of ACU only when the integer part changes, for example “Aurora e1-1 (writer): 1.5 → 2.5 ACU (utilisation 96%)”. The counter ACU e1-1 / e1-2 / e2-1 shows the current values.
Simulation in this step: publish rate 8 events/s, Fan-out Topic per region, Artemis consumers Per-region queues.
3.20us-east-1 fails and takes the writer with it (tour step 20)
us-east-1 is down again. This time e1-1 and e1-2 also stop. The us-east-2 API cannot write: each INSERT fails with 503. Thus the us-east-2 consumers roll back and try again.
After 5 s, Aurora promotes the secondary: e2-1 becomes the writer. This is a global failover. A real global failover takes minutes. The redelivery delay is 2 s here, so the retries continue for longer than the promotion. At 0 s, the retries use all 3 attempts, and the messages go to the dead-letter address.
The replication lag is 2 s here. Thus the rows that the old writer committed in its last 2 s did not get to us-east-2. These rows are lost (counter rows lost (RPO)), unless their event is delivered again. This is the RPO. A planned switchover waits until the secondary is up to date, and loses nothing. Use a switchover to fail back.
When this step ends, us-east-1 comes back. Its instances come back as readers. Then a switchover moves the writer back to e1-1.
A global failover loses the committed rows that did not replicate yet. The consumer possibly acked these events already. Monitor AuroraGlobalDBRPOLag. Think about rds.global_db_rpo to limit the loss.
With redelivery-delay 0, three fast 503 responses during a failover send a good message to the dead-letter address. Set redelivery-delay so that the retries continue for longer than a failover.
| Event | How to start it in the app | New writer | Time (app / real) | Rows lost |
|---|---|---|---|---|
| In-region failover | Kill writer | The other instance in the cluster of the writer (e1-2) | 3 s / typically less than 60 s | None (shared cluster storage) |
| Writer down, no replica in its cluster | Console: __sim.killDb('e1-2'), then __sim.killDb('e1-1') | None. e1-1 stays the writer, and writes continue when you restart it. | — | None, but each write gets 503 |
| Global failover | Region outage (us-east-1) with Promote us-east-2 if us-east-1 is lost on | e2-1 | 5 s / minutes | The rows that did not replicate (RPO) |
| Region outage, no global failover | Region outage (us-east-1) with the switch off | None until us-east-1 is back | — | None, but each write gets 503 |
| Switchover | Switchover to e1-1 | e1-1 | 3 s / minutes | None (it waits for sync) |
Simulation in this step: publish rate 3 events/s, Fan-out Topic per region, Artemis consumers Per-region queues, Promote us-east-2 if us-east-1 is lost on, Artemis redelivery-delay 2 s, Replication lag to us-east-2 2 s. The step waits until the writer has 6 rows (10 s maximum), then 1.5 s more. Then it starts a us-east-1 region outage. When the step ends, the app ends the outage and starts a switchover to e1-1.
3.21The DLQ processor copies messages to S3 (tour step 21)
A DLQ only holds messages. The Artemis DLQ of each region has a processor that moves its messages to the S3 bucket of the region. The processor writes the object first. It acks the message only after S3 sends 200.
| What occurs | Result |
|---|---|
| The region has an outage before the PUT gets to S3 | S3 has no object. The message stays in the DLQ. |
| S3 stored the object, but the ack is lost | The message stays in the DLQ. The next try overwrites the same key. The S3 overwrites counter increases. |
| The broker that holds a DLQ message is down | The message stays on that broker until the broker restarts. The other messages continue. |
| You set DLQ processor on to off | A copy that is in progress completes. Then new messages stay in the DLQ. |
The SQS DLQs have no processor. In real SQS, their messages do not stay for all time. On a standard queue, expiry starts at the original enqueue time, not at the move to the DLQ. Set the DLQ retention to more than the source retention. Simulation The app does not expire messages.
Simulation in this step: the processor starts off, and it comes on after 14 s. Poison: API always rejects (422) is 30%, and the publish rate is 2 events/s.
3.22Two UIs read through a Query API (tour step 22)
The steps before this one are about ingestion, so the tour hides the read path in them. This step shows it. Center and Cube only read. They are two separate deployments in each region.
- A UI sends a query to the Query API of its region.
- The Query API sends the query through the ALB to a pod of the existing API.
- The API pod reads the reader of its region.
- The answer goes back on the same path.
The Query API has its own HPA, from 1 to 5 pods by default. Reads and ingestion use the same pods of the existing API, and its HPA counts both. A Cube query costs the reader more than a Center query, so examine the ACUs of the reader. Each UI shows the rows that it can see. In us-east-2, this number can be less than the rows on the writer, by the replication lag.
Simulation in this step: publish rate 1 event/s, 1 query/s in each region. Center and Cube take turns.
In the scenario results, heavy reads increase the API pods and the ACUs of the reader. They have almost no effect on the time until the last event finishes (scenarios 34 and 39, and 40 and 41 in 6.3). Timings in the app are illustrative, so load test a real system.
3.23Configuration checklist (tour step 23)
Use this checklist to examine the configuration of a real pipeline.
| Area | Check |
|---|---|
| SQS |
|
| Pods |
|
| Fan-out |
|
| NLB |
|
| Artemis |
|
| DLQ processor |
|
| Mirror |
|
| Autoscaler |
|
| ALB |
|
| API |
|
| Read path |
|
| HPA |
|
| Aurora |
|
| Database failover |
|
Simulation in this step: publish rate 1.2 events/s.
4Procedures
4.1Open the app pages
| Page | Deployed path | Local path (npm run dev) |
|---|---|---|
| 3D view | / | http://localhost:5173/ |
| 2D view | /2d | http://localhost:5173/2d.html |
| Manual | /manual | http://localhost:5173/manual.html |
- Open the 3D page at
/. - To open the 2D page, click Open 2D version ↗ in the title card.
- To go back to the 3D page, click Open 3D version ↗ in the title card of the 2D page.
- To open this manual, click Manual in the title card.
Each page runs its own simulation. A change on one page does not change the other page.
The deployed path /2d.html redirects to /2d. An unknown path shows a “Page not found” page with links to the two views.
4.2Select a view
- Find the view buttons at the top of the page.
- To see the live simulation, click Both regions (live).
- To see a close-up of the us-east-1 cluster, click Inside Artemis.
A click on a view button stops the guided tour.
4.3Examine a part
- Make sure that Explore is selected.
- Move the pointer onto a part. A tooltip shows the name and the type of the part.
- Click the part. The info card shows the description, the data and the related parts.
- To go to a related part, click its button in the info card.
- To close the info card, click × or push Esc.
- In the 2D view, click one pod, one consumer or one broker to see its live data.
4.4Use the guided tour
The tour has 23 steps. Section 3 gives the text of each step. Steps 15 to 20 are about the API and Aurora. Step 21 is about the DLQ processor. Step 22 is about the read path.
The tour hides the read path in each step except step 22, so that the other steps show only ingestion. The Show control changes during the tour. When the tour ends, it goes back to your setting.
Many tour steps change the simulation settings and the counters. Do not compare counters from the tour with scenario results.
- Click Guided tour. The tour starts at step 1.
- Read the text of the step. Look at the callouts in the scene.
- To go to the next step, click Next → or push the → key.
- To go to the previous step, click ← Back or push the ← key.
- To play the animation of the step again, click ↻ Replay.
- To go to a different step, click its mark in the progress bar.
- To stop the tour, click Explore or push Esc.
4.5Move the 3D camera
- To turn the model, drag with the left mouse button.
- To zoom, turn the scroll wheel.
- To pan, drag with the right mouse button.
- On a touch screen, drag with one finger to turn. Pinch with two fingers to zoom.
- To move the parts apart, open Display options. Then move the Exploded view slider.
On a narrow screen, Display options starts closed. Tap its header to open it.
4.6Pan and zoom the 2D diagram
- To pan, drag the diagram.
- To zoom, turn the scroll wheel or pinch with two fingers.
- To fit the diagram to the window, double-click the diagram.
4.7Use URL parameters
You can give each state of the app as a link. Add the parameters after ?, and join them with &.
| Parameter | Effect | Pages |
|---|---|---|
view=system, view=artemis | Opens the view. | 3D, 2D |
mode=guided&step=1 … step=23 | Opens the tour at the step. step alone also starts the tour. | 3D, 2D |
pin=<part id> | Opens with the info card of the part. Add view=artemis for a part of the “Inside Artemis” view. | 3D, 2D |
panel=0 | Starts with the Simulate panel collapsed. | 3D, 2D |
explode=0.6 | Opens the exploded view (0 to 1). | 3D only |
Examples: /?view=artemis&pin=nlb, /2d?mode=guided&step=10, /?panel=0&explode=0.6, /2d?pin=aurora-e1-1, /?pin=e2-api.
4.8Use the Simulate panel
The Simulate panel is on the right side of the page. On a narrow screen, the panel starts collapsed. Tap its header to open it.
Control reference
| Section | Control | Range and default | Function |
|---|---|---|---|
| Show | Components | Ingestion + UIs (default), Ingestion only | Ingestion only hides Center, Cube and the Query API, and stops their queries. The tour sets this control for each step, and sets it back when the tour ends. |
| Traffic | Publish rate | 0 to 8 events/s, step 0.1. Default 1.2. | Sets the number of events for each second from the producer. |
| Traffic | Publish 1 | Button | Publishes one event. |
| Traffic | Burst 25 | Button | Publishes 25 events in approximately 1 s. |
| Traffic | Burst 250, Burst 500, Burst 1000 | Buttons | Publishes 250, 500 or 1000 events in 1.5 s. SQS holds the spike. KEDA scales to its maximum (10 pods by default). |
| Capacity (per region) | KEDA max pods (SQS) | 10, 20, 30, 40 or 50. Default 10. | Sets the KEDA maxReplicaCount of the SQS consumer pods in each region. KEDA scales between 1 and this maximum. When you decrease it, each pod above the maximum stops after it completes its message. |
| Capacity (per region) | Artemis consumer count | 10, 20, 30, 40 or 50. Default 10. | Sets the number of Artemis consumers in each region. This number is fixed: nothing scales the consumers. New consumers connect through the NLB. When you decrease it, the extra consumers do not take new messages. Each one stops after it completes its message. |
| Capacity (per region) | API max pods (HPA) | 10, 20, 30, 40 or 50. Default 10. | Sets the HPA maxReplicas of the API Deployment in each region. The HPA scales between 1 and this maximum. When you decrease it, each pod above the maximum stops after its requests complete. |
| Capacity (per region) | Query API max pods (HPA) | 5, 10, 20, 30, 40 or 50. Default 5. | Sets the HPA maxReplicas of the Query API. The HPA scales between 1 and this value. If you lower it, pods above it finish their queries and then stop. |
| Clock | Speed | 0.25× to 3×, step 0.25. Default 1×. | Sets the speed of the simulation clock. |
| Clock | Pause / Resume | Button, or the Space key | Stops or starts the simulation clock. A pause also stops the animations of the guided tour. |
| Clock | Reset | Button | Clears the simulation, sets all controls to their defaults and clears the event log. |
| Topology | Fan-out | 1 topic → both regions, Topic per region (default) | Sets if each event goes to one region or to the two regions. |
| Topology | Artemis consumers | Per-region queues (use1 / use2), copies standby (default), Same queue name, both regions consume, Same queue name, us-east-1 only | Sets the queue names and the regions that consume. The copies stay on standby, or the other region consumes them, or only us-east-1 consumes. |
| Topology | Mirrored queues on (copy to the other region) | Switch. Default on. | Off: new messages have no copy in the other region. A message that already has a copy keeps it until its ack arrives. The tour sets this switch to on, and sets it back when the tour ends. |
| Failures | Send 1 poison → SQS DLQ | Button | Publishes one event that a pod cannot convert to Avro. |
| Failures | Send 1 poison → Artemis DLQ | Button | Publishes one event that the API always rejects with 422. |
| Failures | Poison: Avro can’t be built | 0 to 50% of events, step 5%. Default 0%. | These events fail on each receive. They go to the SQS DLQ after maxReceiveCount (3). |
| Failures | Poison: API always rejects (422) | 0 to 50% of events, step 5%. Default 0%. | The API sends 422 for these events on each delivery. They go to the Artemis DLQ after max-delivery-attempts (3). |
| Failures | Transient failures | 0 to 50% of attempts, step 5%. Default 0%. | Each attempt fails at random with this probability: at the Avro conversion in a pod, and as an API 500. The retry usually succeeds. |
| Failures | Artemis redelivery-delay | 0 to 5 s, step 0.5. Default 0 s. | Sets the time before the broker delivers a rolled-back message again. A higher value lets the retries continue for longer than a database failover. |
| Failures | Consumer work time (before the API call) | 0.1 to 5 s for each message, step 0.1. Default 0.5 s. | Sets the time that a consumer works before it calls the API. A long time makes a backlog in Artemis. |
| Failures | Mirror lag | 0.2 to 4 s, step 0.1. Default 0.7 s. | Sets the delay before a send or an ack gets to the other region. |
| Failures | Redistribution on (delay 0) | Switch. Default off. | On: redistribution-delay 0. Off: −1. On moves stored messages off a broker that has no consumers. |
| DLQ processing | DLQ processor on (copy messages to S3) | Switch. Default on. | On: the processor moves Artemis DLQ messages to S3. Off: messages stay in the Artemis DLQ. Set it to on again and the DLQ drains. The SQS DLQ has no processor. |
| Read path (per region) | Center queries | 0 to 5 /s, step 0.1. Default 0.5 /s. | Sets the query rate of each Center deployment. |
| Read path (per region) | Cube queries (analytics) | 0 to 3 /s, step 0.1. Default 0.5 /s. | Sets the query rate of each Cube deployment. Each query costs the reader more than a Center query. |
| API (HPA) | API time per request | 0.05 to 2 s, step 0.05. Default 0.25 s. | Sets the work time in an API pod for each request. A higher value keeps more requests in flight, and the HPA adds pods (1 to the API max pods (HPA) value in each region). |
| Aurora | Replication lag to us-east-2 | 0.1 to 5 s, step 0.1. Default 0.8 s. | Sets the Global Database lag from the writer to the other region. A higher value gives more dedupe at write and more rows lost at a global failover. |
| Aurora | Serverless v2 max capacity | 1 to 32 ACU, step 0.5. Default 8 ACU. (Minimum: 0.5 ACU, fixed.) | Sets the maximum ACUs of each instance. At the maximum, statements wait and become slower. |
| Aurora | Read before write (dedupe on the local reader) | Switch. Default off. | Off: the API only writes, to the writer. On: the API first reads the reader of its region. If the row is there, the API does not write (dedupe at read). |
| Aurora | Promote us-east-2 if us-east-1 is lost (global failover) | Switch. Default on. | On: a us-east-1 outage promotes e2-1 to writer after 5 s. Off: each write gets 503 until us-east-1 is back. |
| Aurora | Kill writer (e1-1) / Restart e1-… | Button | Stops the current writer. Aurora promotes the other instance in the same cluster after 3 s (in-region failover). While an instance is down, the button shows Restart and its id. Click it to start that instance again as a reader. |
| Aurora | Switchover to e1-1 | Button. Not available while e1-1 is the writer. | Starts a planned switchover that moves the writer back to e1-1 after 3 s, with no data loss. During a promotion, the switchover waits until the promotion completes. |
| Load balancing | Pods open a new connection per message | Switch. Default off. | On: each send gets its own NLB hash. Arrivals spread across the brokers, but each message needs a new connect and handshake. |
| Chaos · us-east-1 | Kill B1, Kill B2, Kill B3 | Buttons | Stops the broker in us-east-1. The button then shows Restart B1 (B2, B3). Click it again to start the broker. |
| Chaos · us-east-1 | Reconnect clients | Button | Reconnects all pods and consumers in us-east-1. The NLB hashes them again across the healthy brokers. |
| Chaos · us-east-1 | Region outage (us-east-1) | Switch. Default off. | Stops all of us-east-1, or starts it again. The outage also stops SNS and SQS in us-east-1, the us-east-1 API, e1-1 and e1-2. |
Change the traffic
- Set the Publish rate slider to the rate that you want.
- To add one event, click Publish 1.
- To add a burst of events, click Burst 25.
- Look at the counters pods us-east-1 and pods us-east-2. KEDA adds pods when the queue grows.
Change the capacity
- Under Capacity (per region), find the four controls KEDA max pods (SQS), Artemis consumer count, API max pods (HPA) and Query API max pods (HPA).
- For each control, click 10, 20, 30, 40 or 50. Query API max pods (HPA) also has 5, its default. The value applies to each region.
- To make a load, click Burst 1000.
- Look at the pod grid, the consumer grid, the API grid and the Query API grid. Each grid shows more, smaller cells for a higher value.
- Look at the counters pods us-east-1, API pods us-east-1 and in SQS.
- To go back to the default capacity, click Reset.
A decrease does not stop work. A pod, consumer or API pod above the new value completes its message or request first. Then it stops.
Change the topology
- Under Fan-out, click 1 topic → both regions or Topic per region.
- Under Artemis consumers, click one of the three options.
- Look at the counters deliveries / event and duplicates.
Cause failures
- To follow one poison message, click Send 1 poison → SQS DLQ or Send 1 poison → Artemis DLQ.
- To keep messages in the Artemis DLQ, set DLQ processor on to off. To drain them to S3, set it to on.
- Look at the event log. It shows each failed attempt.
- To make a percentage of events poison, set one of the two Poison sliders.
- To add random failures, set the Transient failures slider.
- To make a backlog in Artemis, set Consumer work time (before the API call) to a high value, for example 4 s.
- To change the delay of the mirror, set the Mirror lag slider.
- To move stranded messages to brokers with consumers, set Redistribution on.
- To delay each redelivery, set the Artemis redelivery-delay slider.
Show or hide the read path
- Open the Simulate panel. Show is the first section.
- To see only the ingestion pipeline, click Ingestion only.
- To see the UIs and the Query API, click Ingestion + UIs. This is the default.
- To change the load, set Center queries and Cube queries (analytics).
- Examine the counters queries sent, Center answered, Cube answered, queries failed and queries skipped.
The counters API 2xx, API 4xx and API 5xx count consumer calls only. At the default rates, each region sends 1 query each second, and Center and Cube take turns. If you set different rates, they do not take turns.
Scale the API
- Set API time per request to a high value, for example 0.8 s.
- Click Burst 25.
- Look at the counters API pods us-east-1, API pods us-east-2 and API queued.
- Look at the API pods. The ALB uses round robin, so each ready pod gets the same number of requests, also when it is busy.
- Look at the event log. It shows each HPA decision, for example “HPA us-east-1: 6 in flight ÷ 1 → 1 → 6 pods”.
- Wait approximately 15 s after the burst. The HPA removes the pods that it does not need.
The counters API pods show only ready pods. A new pod is ready 2 s after the HPA adds it. In the 2D view, a pod that starts shows hatched.
Change the Aurora settings
- To increase the replica lag, set Replication lag to us-east-2 to a high value, for example 3 s.
- To make the API read before it writes, set Read before write (dedupe on the local reader) to on.
- To see duplicates removed, set Artemis consumers to Same queue name, both regions consume.
- Look at the counters dedupe at read, dedupe at write and rows stored.
- To limit the capacity, set Serverless v2 max capacity to a low value, for example 1 ACU.
- Set Publish rate to 8 events/s.
- Look at the counter ACU e1-1 / e1-2 / e2-1. The writer stops at the maximum, and the API pods increase.
Stop the writer and fail back
An instance that you stop stays down until you restart it. While the writer is down or a promotion is in progress, each write gets 503. Messages can then go to the Artemis DLQ, and the counters change.
- Click Kill writer (e1-1). The log shows the in-region failover to
e1-2. - Look at the counter writer. It shows promoting… for 3 s, then us-east-1 (e1-2).
- To start
e1-1again, click Restart e1-1. It comes back as a reader. - To move the writer back to
e1-1, click Switchover to e1-1. - Look at the counter rows lost (RPO). It stays at 0: an in-region failover and a switchover lose nothing.
To prevent dead-lettered messages during the failover, set Artemis redelivery-delay to 3 s or more before step 1. Compare scenarios 21 and 22 in 6.3.
Compare NLB balancing with cluster balancing
- Click Reset.
- Wait approximately 30 s.
- Write down the counters us-east-1 arrivals via NLB B1/B2/B3 and stored by cluster B1/B2/B3.
- Click Reset again.
- Set Pods open a new connection per message to on.
- Wait approximately 30 s.
- Compare the two counters with the values from step 3.
Result: with long-lived connections, the arrivals are not equal. With a new connection for each message, the arrivals are almost equal. The stored messages are equal in the two cases.
Stop a broker or a region
A broker that you stop stays down until you start it again. Its stored messages stay on it and the counters show them.
- Click Kill B1. The button changes to Restart B1.
- Look at the connection lines. They move to the other brokers.
- To start the broker again, click Restart B1.
- To send connections to the restarted broker again, click Reconnect clients.
A region outage also stops the writer e1-1. With Promote us-east-2 if us-east-1 is lost (global failover) on, the global failover loses the rows that did not replicate to e2-1. In a real system, these rows are lost although their events were possibly acked. The failover can also cause duplicate processing.
- To stop all of us-east-1, set Region outage (us-east-1) to on.
- Look at the counters writer, API 5xx and rows lost (RPO). After 5 s, the writer is us-east-2 (e2-1).
- To start us-east-1 again, set Region outage (us-east-1) to off.
e1-1ande1-2come back as readers. - To move the writer back to us-east-1, click Switchover to e1-1. The app does not do this automatically.
Control the clock
- Set the Speed slider to make the simulation faster or slower.
- To stop the clock, click Pause. The button changes to Resume.
- To start the clock again, click Resume.
- To start again from zero with the default settings, click Reset.
Live counters
| Counter | Meaning |
|---|---|
| published | Events that the producer published. |
| unique processed | Events that consumers processed one or more times (a 2xx response, then an ack). |
| deliveries / event | Successful processings divided by unique processed events. |
| duplicates | Processings of an event after its first processing. |
| in SQS | Messages in the SQS queues of the two regions. |
| SQS DLQ | Messages that SQS moved to a DLQ. |
| in Artemis | Messages stored on all brokers, mirror copies included. |
| moved to Artemis DLQ | Messages that a broker moved to the dead-letter address. This total does not decrease when the processor removes a message. |
| waiting in Artemis DLQ | Messages that are in an Artemis DLQ now. |
| objects in S3 | Objects in the S3 buckets of the two regions. |
| S3 overwrites | PUTs to a key that had an object. Each one is a retry after a lost ack. |
| mirror copies sent | Messages that brokers copied to the other region. It stops when you set Mirrored queues on to off. |
| mirror backlog | Mirror events that wait because their target is down. |
| us-east-1 arrivals via NLB B1/B2/B3 | Messages that pods sent to each us-east-1 broker. |
| stored by cluster B1/B2/B3 | Messages that the us-east-1 cluster put on each broker, mirror copies included. |
| pods us-east-1, pods us-east-2 | Active pods in each region. |
| publish failed | Publishes that failed because the region of the topic was down. |
| API pods us-east-1, API pods us-east-2 | Ready API pods in each region. Pods that start are not included. |
| API queued | Requests that wait: in a pod that is full, or at the ALB when no pod is ready. Queries of the read path are included. |
| API 2xx, API 4xx, API 5xx | API responses to consumer calls: stored or already stored; 422 (poison); 500 (transient) or 503 (database not available). |
| rows stored | Rows on the writer. With no row loss, it is equal to unique processed. |
| Query API pods us-east-1, Query API pods us-east-2 | Ready Query API pods in each region. |
| queries sent | Queries that Center and Cube sent, in the two regions. |
| Center answered, Cube answered | Queries of each UI that got a 2xx answer. |
| queries failed | Queries that got a 5xx answer, or that the region lost in an outage. |
| queries skipped | Refreshes that a UI did not send, because it had 20 open queries. |
| dedupe at read | Requests that found their row on the local reader and did not write (only with Read before write on). |
| writes/s on the writer | Write statements that the current writer finished for each second, averaged over 5 simulated seconds. ON CONFLICT DO NOTHING statements count. The writer also shows this value on the diagram. After a promotion, the new writer starts from 0. |
| dedupe at write | Writes that found their row already on the writer: ON CONFLICT DO NOTHING. |
| cross-region writes | Rows that an API stored on a writer in the other region. |
| rows lost (RPO) | Committed rows that a global failover lost, because they did not replicate to e2-1. |
| ACU e1-1 / e1-2 / e2-1 | Current capacity of each instance, for example 2.0 / 0.5 / 0.5. |
| writer | The region and the id of the current writer, for example us-east-1 (e1-1). During a promotion: promoting…. |
Event log
The event log shows the last 50 entries. Each entry starts with the simulation time. Yellow entries are warnings, light red entries are failures and green entries are recoveries.
Entries for the API and Aurora:
- HPA decisions, for example “HPA us-east-2: 4 in flight ÷ 1 → 1 → 4 pods (new pods ready in 2s)”.
- ACU changes, only when the integer part changes, for example “Aurora e1-1 (writer): 0.5 → 1.0 ACU (utilisation 120%)”.
- 503 bursts, one entry for each second, for example “API us-east-2: 6× 503 in the last second (writer promotion in progress); consumers roll back”.
- Read path: “Read path ON” and “Read path OFF”, “HPA Query API us-east-1: …” for its pods, and failed queries, one entry for each second, for example “Read path us-east-2: 2 queries failed in the last second (no Aurora instance up in us-east-2 to read from)”.
- Dedupe, failover, promotion and lost rows, for example “Aurora: 3 committed rows had not replicated to e2-1 → lost (RPO)”.
4.9Run a scenario by hand
- Open the 3D page or the 2D page in Explore mode.
- Click Reset.
- Set the controls that the table gives for the scenario.
- Wait approximately 60 s.
- Read the counters and the event log. Compare them with the column “What to look for”.
The script npm run scenarios uses a publish rate of 2 events/s and a seeded random generator. Your numbers by hand are not the same as the script numbers. Scenarios 15 to 24 use the API and Aurora. Scenarios 25 and 26 use the capacity controls. Some of them use console commands (4.11).
| # | Scenario | Settings after Reset | What to look for |
|---|---|---|---|
| 1 | Baseline | No change: Topic per region, Per-region queues. | 1.00 deliveries / event. Cyan copies appear in the other region, and acks delete them. |
| 2 | One topic → both regions | Fan-out: 1 topic → both regions. | 2.00 deliveries / event. Each region processes its own copy of each event. |
| 3 | Same queue name, both consume | Artemis consumers: Same queue name, both regions consume. | Approximately 2 deliveries / event. The other region takes the mirror copy before the ack arrives. |
| 4 | Same queue name, us-east-1 only | Artemis consumers: Same queue name, us-east-1 only. | 1.00 deliveries / event. The us-east-2 consumers are idle. |
| 5 | One topic, same name, both consume | The settings of scenarios 2 and 3 together. | Up to approximately 4 deliveries / event. |
| 6 | Poison at Avro | Click Send 1 poison → SQS DLQ, or set Poison: Avro can’t be built to 10%. | A red-orange cube is received 3 times. Then it moves to the SQS DLQ. |
| 7 | Poison at the API | Click Send 1 poison → Artemis DLQ, or set Poison: API always rejects (422) to 10%. | The API sends 422 3 times. Then the message moves to the Artemis DLQ. |
| 8 | Transient failures | Transient failures: 20%. | Most retries succeed. The DLQs stay almost empty. |
| 9 | Broker fails under load |
| Messages on B1 stay stranded. B1 has no consumers until you click Reconnect clients. |
| 10 | Broker failure with redistribution | Do scenario 9 with Redistribution on. | Stranded messages move to brokers that have consumers. |
| 11 | Region failover |
| The producer moves to us-east-2. The us-east-2 consumers take over use1.queue. One duplicate or a few duplicates occur at the switch. The Aurora writer moves to e2-1. |
| 12 | Failover with redistribution | Do scenario 11 with Redistribution on. | The seeded script gives the same result as scenario 11. |
| 13 | Outage with one topic | Set Fan-out to 1 topic → both regions. Then do scenario 11. | Publishes fail while us-east-1 is down. The topic is a single point of failure. |
| 14 | NLB compared with cluster balancing | Do the procedure in Compare NLB balancing with cluster balancing. | Arrivals are not equal with long-lived connections. Stored messages are equal in the two cases. |
| 15 | High rate: the HPA and the writer ACUs | Publish rate: 8. | The API pods increase in the two regions. The writer ACUs increase to the maximum. The readers stay small. |
| 16 | Bursts | Click Burst 25 three times, wait 20 s, then do it again. | The API pods increase at once and decrease approximately 10 s after each burst. |
| 17 | Dedupe at read and at write |
| dedupe at read and dedupe at write both increase. rows stored stays equal to unique processed. |
| 18 | Region outage with global failover | Publish rate 6. Then do scenario 11. | The us-east-2 API gets 503 for 5 s. Then e2-1 is the writer. rows lost (RPO) increases. |
| 19 | Region outage without global failover | Set Promote us-east-2 if us-east-1 is lost to off. Then do scenario 18. | Each write gets 503 during the outage. More messages go to the Artemis DLQ. No rows are lost. |
| 20 | In-region failover and switchover | Do the procedure in Stop the writer and fail back. | Writes get 503 for 3 s. e1-2 becomes the writer. After the switchover, e1-1 is the writer again. No rows are lost. |
| 21 | Writer down with no replica |
| Each write gets 503 while the writer is down. Some messages use all 3 attempts and go to the Artemis DLQ. |
| 22 | Same, with a redelivery delay | Set Artemis redelivery-delay to 3 s. Then do scenario 21. | The retries continue for longer than the outage. No message goes to the Artemis DLQ. |
| 23 | Reader e2-1 down | Type __sim.killDb('e2-1'). After 5 s, type __sim.restartDb('e2-1'). | No effect on the requests, because the API only writes by default. |
| 24 | Switchover during a failover | Click Kill writer, then Restart e1-1, then Switchover to e1-1, each 1 s after the other. | The log shows “Switchover queued”. The switchover starts when the promotion of e1-2 completes. Writes get 503 for approximately 6 s: two promotions, one after the other. |
| 25 | Spike at the default capacity | Set Publish rate to 0. Click Burst 1000. | SQS holds the spike. 10 pods, 10 consumers and up to 10 API pods in each region process it. The drain takes some minutes. |
| 26 | Spike at the full capacity | Set the three Capacity (per region) controls to 50. Set Publish rate to 0. Click Burst 1000. | The pods and the API pods increase to approximately 50 in each region. The drain is approximately 3 times faster than in scenario 25. The writer ACUs increase to the maximum. |
| 27 | Processor on | Set the two poison sliders to 10%. | Artemis DLQ cubes move to S3. waiting in Artemis DLQ goes back to 0. SQS DLQ cubes stay. |
| 28 | Processor off | Set the two poison sliders to 10%. Set DLQ processor on to off. | waiting in Artemis DLQ increases. objects in S3 stays 0. |
| 29 | Processor on late | Do the scenario before this one. After 30 s, set DLQ processor on to on. | The Artemis DLQ drains to S3. |
| 30 | Outage while the processor runs | Set the two poison sliders to 20%. Set Region outage to on for 10 s. | Processing stops in us-east-1 during the outage. After it, the DLQ drains. |
| 31 | Broker failure while the processor runs | Set Poison: API always rejects (422) to 20%. Click Kill B1. After 10 s, click Restart B1. | Messages on B1 wait. After the restart, they go to S3. |
| 32 | Read path, default rates | Use the default settings. | Queries go from each UI to the reader of its region. All queries get an answer. |
| 33 | Heavy reads | Set Center queries to 5 /s and Cube queries to 3 /s. | The Query API goes to 5 pods. The existing API adds pods. The ACUs of the readers increase. Ingestion continues. |
| 34 | Heavy reads, slow API | Do scenario 33. Set API time per request to 2 s. | queries skipped increases. The queries take more time to drain. The last event finishes approximately at the same time as in scenario 39. |
| 35 | Outage with reads | Set Region outage to on for 10 s. | The UIs in us-east-1 show region down. The UIs in us-east-2 continue. |
| 36 | No reader in us-east-2 | In the console, type __sim.killDb('e2-1'). After 5 s, type __sim.restartDb('e2-1'). | The UIs in us-east-2 show query failed. Ingestion does not stop. |
| 37 | No reader in us-east-1 | In the console, type __sim.killDb('e1-2'). After 5 s, type __sim.restartDb('e1-2'). | The queries go to the writer. Only a query that is on the reader at that moment fails. |
| 38 | Hide the read path | After 30 s, click Ingestion only. | The queries stop. Ingestion does not change. |
| 39 | Slow API, no reads | Click Ingestion only. Set API time per request to 2 s. | Use this result to compare with scenario 34. |
| 40 | High rate, slow API, heavy reads | Set Publish rate to 6 events/s and API time per request to 2 s. Set the query rates of scenario 33. | The backlog stays in Artemis. Compare with scenario 41. |
| 41 | High rate, slow API, no reads | Do scenario 40. Click Ingestion only. | Use this result to compare with scenario 40. |
| 42 | Heavy reads, larger Query API | Do scenario 33. Set Query API max pods (HPA) to 20. | Fewer queries are skipped than in scenario 33. The queries drain sooner. |
| 43 | Lower the maximum | Do scenario 42. After 30 s, set Query API max pods (HPA) to 5. | Pods above 5 finish their queries, then stop. |
| 44 | Mirroring off | Set Mirrored queues on to off. | The cyan lines go away. mirror copies sent stops. Processing does not change. |
| 45, 46 | Short outage, mirroring on and off | Set Region outage to on for 10 s, with the switch on, then with it off. | With it off, the log shows “No failover”. The messages of us-east-1 wait on its brokers, then finish. |
| 47 | Shared queue name, no copies | Set Artemis consumers to Same queue name, both regions consume. Set the switch to off. | deliveries / event is 1.00. Compare scenario 3. |
| 48 | Turn it off while it runs | After 30 s, set the switch to off. | Existing copies go away when their acks arrive. |
| 49 | Turn it on, then an outage | Start with the switch off. After 30 s, set it to on. After 10 s, set Region outage to on for 10 s. | Only messages from after the switch have a copy. |
| 50, 51 | Region lost for good | Set Consumer work time to 4 s and Publish rate to 3. After 20 s, set Region outage to on. Do not set it to off. Do this with the switch on, then off. | With it off, fewer events finish: the messages stay on the lost brokers. |
4.10Run all scenarios with npm
- Open a terminal in the project folder
event-pipeline-v2-3d. - If this is the first run, type
npm install. - Type
npm run scenarios. - To change the traffic time of each scenario, type
npm run scenarios -- --seconds 120. - To run the simulation in Node.js without a browser, type
npm run scenarios -- --node. The numbers are the same. - Compare the table with 6.3.
The script opens the 2D page in a headless browser. It runs each scenario for 60 simulated seconds of traffic (default). Then it runs until nothing moves, for a maximum of 10 simulated minutes. The random generator has a fixed seed, so the numbers are the same each time.
The base settings of the script are: publish rate 2 events/s, Topic per region, Per-region queues, mirror lag 0.7 s, consumer work time 0.5 s, API time 0.25 s, replication lag 0.8 s, maximum 8 ACU, global failover on, Read before write off, redelivery delay 0, and capacity 10 / 10 / 10 in each region (KEDA max pods, Artemis consumers, API max pods). The column drain time (s) is the simulated time from the first publish until nothing moves or waits. The column stuck in Artemis counts messages that stay in Artemis after all other work stops. A stranded event counts two times: the original message and its mirror copy.
The script also examines these conditions after each scenario. The column check shows ok if they are all true:
- Each row belongs to a processed event. (Exception: a row of an event that went to the dead-letter address after its 200 was lost.)
- Each processed event has a row, unless a global failover lost it.
- The rows are not more than the unique events.
- At the end, nothing moves or waits.
- Each query that a UI sent got an answer or failed, and no query stays open.
- If a region stays down at the end (scenarios 50 and 51), the check does not need that region to be idle, because its SQS queue keeps its messages.
Scenarios 1 to 31, 39, 41 and 44 to 51 run with the read path off. The other scenarios turn it on.
The column acked, no row counts processed events that have no row in Aurora. Only a global failover (RPO) causes this.
4.11Control the simulation from the console
- Open the developer tools of the browser.
- Select the Console tab.
- To change settings, type
__sim.set({ rate: 5, consume: 'sharedBoth' }). - To stop broker 2 in us-east-1, type
__sim.kill('e1', 1). - To read the counters, type
__sim.counts(). - To make the HPA add API pods, type
__sim.set({ apiTime: 0.8 }). - To turn on the read before write, type
__sim.set({ readCheck: true }). - To see more dedupe at write, type
__sim.set({ replLag: 3, consume: 'sharedBoth' }). - To stop the writer, type
__sim.killDb('e1-1'). To start it again, type__sim.restartDb('e1-1'). - To move the writer back to
e1-1, type__sim.switchover(). - To turn off the mirrored queues, type
__sim.set({ mirroring: false }). - To hide the read path, type
__sim.set({ readPath: false }). To change its load, type__sim.set({ centerRate: 1, cubeRate: 1, qapiMax: 10 }). - To read the API and Aurora values, type
__sim.counts().acu,__sim.counts().rowsLostor__sim.db.writer.
The broker index starts at 0. Thus __sim.kill('e1', 1) stops broker 2. Other functions are publish(), burst(n), restart(region, k), reconnect(region), outage(region, true|false) and reset().
The instance ids for killDb and restartDb are e1-1, e1-2 and e2-1. restartDb does nothing while the region of the instance is down. The settings for set() are the keys in 6.2, for example apiTime, replLag, acuMax, readCheck, dbFailover and redeliveryDelay.
__sim.counts() also gives these API and Aurora values: apiPodsE1, apiPodsE2, apiQueued, apiOk, api4xx, api5xx, rows, writesPerSec, dedupRead, dedupWrite, crossWrites, rowsLost, acu (text, for example '2.0 / 0.5 / 0.5') and writer (text, for example 'us-east-1 (e1-1)'). For the read path, it gives queries, queriesOk, queriesFailed, queriesSkipped, centerOk, cubeOk, qapiPodsE1 and qapiPodsE2.
4.12Run the screenshot checks
The checks start their own Vite server on a free port. They open the app in headless Chromium.
npm run shoot deletes the folder shots/ and writes it again. npm run shoot:2d does the same with shots-2d/. Copy old screenshots before you start a check.
3D check
- Type
npm run shoot. - To check only some tour steps, type
npm run shoot -- --steps 3,4. - To skip the phone screenshots, add
--no-mobile. To skip the view screenshots, add--no-views. - Read the last line. “OK: no errors or broken references” tells you that the check passed.
- If the check fails, examine
shots/report.json. It lists console errors, broken tour or part references and blank frames.
2D check
- Type
npm run shoot:2d. - To check only some tour steps, type
npm run shoot:2d -- --steps 3,4. - Read the last line. “OK: no errors” tells you that the check passed.
- Examine the screenshots in
shots-2d/.
4.13Build and preview
- Type
npm run build. Vite writesdist/withindex.html,2d.html,manual.htmlandassets/. - To serve the build with the Cloudflare runtime, type
npm run preview:cf. This command builds the site and startswrangler dev, with the same routing as production. - Open the URL that wrangler shows.
- Make sure that
/,/2dand/manualopen.
For work on the code, type npm run dev. The dev server reloads the pages when you save a file.
4.14Deploy to Cloudflare
The app deploys as a Cloudflare Worker with static assets. The code in worker/index.js handles only /api/*, for the notes. Each reader has one Durable Object, with their email from Cloudflare Access as its key. The file wrangler.jsonc holds the configuration.
npm run deploy replaces the live Worker. Run the checks in 4.12 before you deploy.
Deploy from your computer
- Type
npx wrangler login. Do this one time only. - Type
npm run deploy. The command builds the site and startswrangler deploy. - Open
https://event-pipeline-v2.<your-subdomain>.workers.dev/. - Make sure that
/,/2dand/manualopen.
Make the notes sync
The API answers 401 if Cloudflare Access does not protect it. Notes then stay in the browser of the reader. You must do this procedure in the Cloudflare dashboard, because wrangler deploy does not change Access.
- In Cloudflare Zero Trust, add a self-hosted Access application for
event-pipeline-v2.tudum.workers.dev. - Set the path of the application to
api/. The app and the manual stay public. - Add a policy that lets in your account. Do not use the email domain
proton.mein a policy, because other people have that domain. - Type
curl -I https://event-pipeline-v2.tudum.workers.dev/api/annotations. A 302 response tocloudflareaccess.comtells you that Access protects the API. - Open the manual. Click ✎ Notes, then Sign in to sync. After the login, the page comes back to the manual.
To pin the API to this one Access application, add its AUD tag to vars in wrangler.jsonc as ACCESS_AUD. On npm run preview:cf, the API uses one local reader and needs no Access.
Optional: to use a custom domain, remove the comment marks from routes in wrangler.jsonc. The domain must be on your Cloudflare account.
To change the name of the Worker and its URL, change name in wrangler.jsonc.
5Fault isolation
Use this table when the app or a real pipeline shows a problem. Find the symptom, then do the action. Rows with “real system” apply only to a real pipeline.
| Symptom | Possible cause | Action |
|---|---|---|
| deliveries / event is 2.00. The duplicates counter increases for each event. | Fan-out is 1 topic → both regions. | Set Fan-out to Topic per region. In a real system, make sure that two times the processing is intended. |
| deliveries / event is approximately 2 with a topic per region. Cyan copies are consumed. | Artemis consumers is Same queue name, both regions consume. | Use per-region names (use1.*, use2.*). Do not have active consumers on the two sides. |
| A red-orange cube is received 3 times. Then the SQS DLQ counter increases. | Poison message: the pod cannot convert the payload to Avro. | Examine the message in the SQS DLQ. Correct the payload or the converter. |
| Healthy messages are received two times (real system). | The visibility timeout is shorter than the worst-case pod time. | Set the visibility timeout to more than the Avro conversion time plus the Artemis send time. |
| The moved to Artemis DLQ counter increases. The log shows “API 422 (poison: payload rejected), consumer rolled back; redelivery”. | The API always rejects the message (poison), or there are many transient failures (API 500). | Examine the messages at the dead-letter address. Correct the API or the message. |
| waiting in Artemis DLQ increases, and objects in S3 does not change. | DLQ processor on is off, or the region or the broker that holds the messages is down. | Set DLQ processor on to on. Restart the broker or end the outage. |
| Poison messages disappear, and no DLQ gets them (real system). | No dead-letter-address in address-settings. Artemis discards the messages. | Set a dead-letter-address. |
| The log shows transient failures. The DLQs stay almost empty. | Transient failures. The retries succeed. | No action is necessary. |
| The counter us-east-1 arrivals via NLB is not equal across brokers, for example 52 / 8 / 0. | The NLB pins each long-lived connection to one broker. | This is normal. Examine stored by cluster: ON_DEMAND makes it equal. To spread arrivals, use a new connection for each message. This adds a handshake for each message. |
| A broker has no consumers, for example 5 / 5 / 0. | The NLB hash of the consumer connections. | ON_DEMAND sends new arrivals to brokers with consumers. Set redistribution-delay to 0 or more for stored messages. Reconnect the clients to hash them again. |
| After a broker restart, in Artemis does not decrease. The broker has no consumers. | The connections stay pinned to the other brokers. The stored messages are stranded. | Click Reconnect clients, or set Redistribution on. |
| The cyan mirror lines are not visible. mirror copies sent does not increase. | Mirrored queues on is off. | Set it to on. Only new messages get a copy. |
| The log shows “No failover for use1.queue: mirroring is off”. in Artemis does not decrease during an outage. | Mirroring is off, so the other region has no copy. The messages wait on the brokers of the region that is down. | End the outage. They then finish. If the region does not come back, they are lost to the pipeline (scenarios 50 and 51). In a real system, keep mirroring on, or accept this risk. |
| The mirror backlog counter increases. | The target broker or the target region is down. | Start the target again. The log then shows “target back, … queued mirror events sent”. |
| The publish failed counter increases. | One-topic mode and us-east-1 is down. The topic is a single point of failure. | Use a topic per region. Start us-east-1 again. |
| Duplicates occur after a region failover. | us-east-1 processed the message, but the ack did not get to us-east-2 before the outage. | Remove duplicates in the API with the event id: a unique key and ON CONFLICT DO NOTHING. |
| The duplicates counter increases, but rows stored stays equal to unique processed. | Normal. The API removes the duplicates: dedupe at write (and dedupe at read, if Read before write is on). | No action is necessary. In a real system, make sure that each event has a unique event_id. |
| Many API 5xx after a us-east-1 outage starts. The log shows “503 in the last second (writer promotion in progress)” or “(writer e1-1 is down)”. | The writer was in us-east-1. Writes get 503 until e2-1 is promoted (5 s in the app, minutes in a real system). If Promote us-east-2 if us-east-1 is lost is off, writes get 503 until us-east-1 is back. | Wait for the promotion. Set Artemis redelivery-delay so that the retries continue for longer than the promotion. If the switch is off, end the outage. |
| The moved to Artemis DLQ counter increases during a writer failover. The log shows “failed 3× (last: API 503) → dead-letter address”. | redelivery-delay is 0. Three fast 503 responses use all delivery attempts before the new writer is ready. | Set Artemis redelivery-delay to 3 s or more (scenario 22). In a real system, set redelivery-delay and max-delivery-attempts to cover the failover time. |
| The rows lost (RPO) counter increases after a us-east-1 outage. | Global failover. The old writer committed these rows, but they did not replicate to e2-1 before the outage. | In the app, set a lower Replication lag to us-east-2. In a real system, monitor AuroraGlobalDBRPOLag and think about rds.global_db_rpo. Use a switchover, not a failover, for planned moves. |
| The writer stays us-east-2 (e2-1) after us-east-1 is back. | The app does not fail back automatically after a global failover. | Click Switchover to e1-1. |
| The log shows “Switchover: e1-1 is down; restart it first”. | e1-1 is down, for example after Kill writer. | Click Restart e1-1. Then click Switchover to e1-1. |
| Each write gets 503, and the counter writer shows us-east-1 (e1-1) DOWN. The log shows “DOWN with no replica in its cluster”. | The writer stopped, and no other instance in its cluster is up. No failover can occur. | Restart the writer (__sim.restartDb('e1-1')). Then restart the reader. |
| The us-east-2 API sends 503. The log shows “no Aurora instance up in us-east-2 to read from”. | Read before write is on, and e2-1 is down. | Restart e2-1 (__sim.restartDb('e2-1')), or set Read before write to off. |
| Center, Cube and the Query API are not visible. | Show is Ingestion only, or a tour step hides them. | Click Ingestion + UIs, or stop the tour. |
| Center and Cube show “query failed”. The log shows “no Aurora instance up in us-east-2 to read from”. | No Aurora instance is up in that region. | Restart the instance, for example __sim.restartDb('e2-1'). The writes of the consumers do not stop. |
| Center and Cube show “region down”. queries failed increases one time, then stops. | The region has an outage. Its UIs stop, and its open queries fail. The app has no failover for the UIs. | End the outage. The UIs in the other region continue. |
| queries skipped increases. | A UI has 20 open queries, because the query rate is too high for the Query API or the API. | Decrease the query rates, or increase Query API max pods (HPA). |
| The Query API status shows “queued” in yellow. | All its ready pods are full, or new pods are starting. | Wait for the HPA. If it stays at the maximum, increase Query API max pods (HPA). |
| The API pods or the ACUs of a reader increase, but the publish rate is low. | The queries of the read path take API slots and reader capacity. A Cube query costs 4 times a Center query. | This is normal. Decrease the query rates or click Ingestion only to compare. |
| Center in us-east-2 sees fewer rows than the writer. | Replica lag. The rows did not replicate to e2-1 yet. | This is normal. In a real system, show how old the data is. |
| The writer ACU stays at the maximum. API queued and the API pods increase. Requests are slow. | The Serverless v2 max capacity is too low for the write load. All writes from the two regions go to the one writer. | Increase Serverless v2 max capacity, or decrease the publish rate. In a real system, set the ACU maximum for bursts and limit maxReplicas of the HPA. |
| The API pods do not decrease after the load stops. | Scale-down stabilization. The HPA uses the highest recommendation of the last 10 s (300 s by default in a real system). It does not remove a pod with requests in flight. | This is normal. Wait. |
| dedupe at write increases although Read before write is on. The log shows “e2-1 was stale”. | Replica lag. The reader did not have the row yet, so the read did not find it. | This is normal: the unique key on the writer removes the duplicate. Do not remove duplicates with a read only. |
| A reader shows “lag: N rows” or hollow row cubes (2D view). | Replica lag. The rows are on the writer, but did not replicate to this reader yet. | This is normal. A higher Replication lag to us-east-2 increases the lag of e2-1. |
| The log shows “its row was already committed: an earlier 200 was lost”. | A write committed, but its response was lost with the region. Then all retries failed. | The event is stored. If you send the message again from the dead-letter address, the unique key removes the duplicate. |
| Each us-east-2 request takes longer than a us-east-1 request (real system). | Each us-east-2 write crosses to the writer in us-east-1 and pays for a round trip. | This is the cost of one writer. Think about write forwarding on the secondary cluster. |
| A message is not in us-east-2 after a us-east-1 failure (real system). | The async mirror did not copy the message before the failure. | Wait for the recovery of us-east-1. The message stays on its disk. Use sync=true if the added latency is permitted. |
| The number of pods changes slowly (real system). | KEDA polls every 30 s and has a cooldown period of 300 s by default. | This is normal. Change the polling interval or the cooldown period if necessary. |
| Idle client connections close (real system). | The NLB TCP idle timeout is 350 s. | Keep Core pings (clientFailureCheckPeriod, 30 s default [28]) below 350 s. |
| All us-east-2 consumers are idle. | Artemis consumers is Same queue name, us-east-1 only. In per-region mode, they read only use2.queue. | This is normal for these settings. |
/manual or /2d shows “Page not found”. | The page is not in the build. | Make sure that vite.config.js lists the page in the build inputs. Build again. |
| The Simulate panel is not visible on a phone. | On a narrow screen, or with panel=0, the panel starts collapsed. | Tap the header of the panel. |
| The notes panel shows “Not signed in” or “Saved on this device”. | You have no Access session, so the notes are only in this browser. | Click Sign in to sync. Your local notes upload after the login. |
| Sign in to sync shows “Sign-in did not work”. | Access does not protect /api/*, or it did not let your account in. | Do the procedure in Make the notes sync. |
| Counters change, but you did not touch a control. | A tour step changes the simulation. | Stop the tour. Click Reset. |
npm run shoot shows “NOT OK”. | A console error, a broken reference or a blank frame. | Examine shots/report.json. Correct the error and run the check again. |
6Reference data
6.1Documented values
These values come from the AWS, KEDA, Kubernetes, PostgreSQL and Artemis documentation. The numbers in brackets refer to Section 8.
| Quantity | Value | Source |
|---|---|---|
| SQS visibility timeout | 30 s default (range 0 to 43,200 s) | [6] |
| SQS retention | 4 days default, 14 days maximum | [6] |
| SQS DLQ | Same account, region and queue type as the source queue | [6] |
| Message expiry in an SQS DLQ | Standard queue: from the original enqueue time. FIFO queue: from the move to the DLQ | [6] |
| S3 consistency | Strong read-after-write, also for overwrites; updates to one key are atomic | [24] |
| ALB routing | Round robin by default; each request is routed independently | [25] |
| ALB deregistration delay | 300 s default | [25] |
| SQS redrive | When ReceiveCount is more than maxReceiveCount | [6] |
| SNS → SQS retries | 100,015 attempts over 23 days (fixed policy) | [4] |
| SNS subscription DLQ | For each subscription; same account and region as the subscription | [4] |
| NLB target selection (TCP) | Flow hash of protocol, source and destination IP and port, TCP sequence number. One connection goes to one target for its life. | [8] |
| NLB TCP idle timeout | 350 s default (range 60 to 6000 s) | [8] |
| NLB cross-zone load balancing | Off by default | [8] |
KEDA aws-sqs-queue queueLength | 5 messages for each pod (default). Counts visible and in-flight messages. | [9] |
| KEDA polling interval, cooldown period, maximum replicas | 30 s, 300 s, 100 replicas (defaults) | [9] |
| KEDA Artemis scaler | artemis-queue. Reads MessageCount through Jolokia. queueLength default 10. | [9] |
Artemis max-delivery-attempts | 10 (default) | [14] |
Artemis redistribution-delay | −1 (default, never) | [13] |
Artemis message-load-balancing | ON_DEMAND (default) | [13] |
| Artemis mirror | Async by default; sync=true is optional | [12] |
| Confluent wire format | 0x00 magic byte, then a 4-byte schema id | [10] [11] |
Artemis redelivery-delay | 0 (default) | [14] |
| HPA sync period | 15 s default (--horizontal-pod-autoscaler-sync-period) | [15] |
| HPA rule | desiredReplicas = ceil[currentReplicas × (currentMetricValue ÷ desiredMetricValue)] | [15] |
| HPA stabilization window | Scale-up 0 s, scale-down 300 s (defaults) | [15] |
| HPA tolerance | 10% (default) | [15] |
| Serverless v2 ACU | Approximately 2 GiB of memory, with matching CPU and network | [16] |
| Serverless v2 scaling step | As small as 0.5 ACU; larger steps at higher capacity | [16] |
| Serverless v2 range | 0.5 to 128, 0.5 to 256 or 0 to 256 ACU, by engine and platform version; 0 ACU = auto-pause | [16] [17] |
| Aurora readers in a cluster | Up to 15 | [21] [23] |
| Reader lag in a cluster | Usually much less than 100 ms | [23] |
| In-region failover | Typically less than 60 s, often less than 30 s | [21] |
| Global Database secondaries | Up to 10 regions, read-only | [18] |
| Global Database replication lag | Typically less than 1 s | [18] |
| Global failover | RPO typically seconds, RTO minutes; can lose write transactions that did not replicate | [19] |
| Global switchover | RPO 0 (it waits for the secondary to synchronize) | [19] |
rds.global_db_rpo | 20 s to 2,147,483,647 s; not set (−1) by default | [19] |
| Write forwarding (Aurora PostgreSQL) | 14.9+, 15.4+, 16+; off by default; apg_write_forward.consistency_mode default SESSION | [20] |
INSERT … ON CONFLICT DO NOTHING | Does not insert the row if a conflict occurs. Needs a unique index or constraint (here on event_id) as the arbiter. | [22] |
6.2Simulation values
All values in this table are illustrative values of the app (src/content/sim.js). Simulation
| Item | Value | Documented value |
|---|---|---|
| ALB | Round robin over the ready pods. No latency or errors of its own | Round robin is the default |
| DLQ processor | 1 worker in each region, 1 message at a time, approximately 1.45 s for each message | Model assumption |
| Read path | One Center, one Cube and one Query API Deployment in each region. Query API: HPA from 1 to 5 pods (maximum 5 to 50 in the panel), with the same rules as the existing API. A UI holds a maximum of 20 open queries. A Cube query takes 4× the time and costs 4× the ACUs of a Center query | Model assumption |
Mirrored queues switch (mirroring) | On. Off stops new copies. Acks still go out for messages that have a copy | Model of a mirror broker connection that is off |
Query rates (centerRate, cubeRate) | 0.5 queries/s each, in each region (ranges 0 to 5 and 0 to 3). Together, 1 query/s, and they take turns | Model assumption |
Query API pods (QAPI_MIN, qapiMax) | 1 minimum, 5 maximum (5 to 50 in the panel). Same HPA poll, target, window, start time and requests for each pod as the API | Set by you |
| Writes per second | Average of the last 5 simulated seconds (WRITE_WINDOW) | — |
| Visibility timeout | 3 s | 30 s default |
maxReceiveCount | 3 | Set by you |
max-delivery-attempts | 3 | 10 default |
| KEDA polling interval | 2 s | 30 s default |
| KEDA target | 5 messages for each pod | 5 default |
| Pods for each region | 1 minimum, 10 maximum, no cooldown | Maximum 100, cooldown 300 s |
| Artemis consumers for each region | 10, fixed | Model assumption |
| Brokers for each region | 3 | Model assumption |
Default publish rate (rate) | 1.2 events/s (range 0 to 8) | — |
Default mirror lag (lag) | 0.7 s (range 0.2 to 4 s) | — |
Default consumer work time (procTime) | 0.5 s (range 0.1 to 5 s) | — |
Default redelivery delay (redeliveryDelay) | 0 s (range 0 to 5 s) | 0 default |
| SNS retry when the queue region is down | Every 2 s | 100,015 attempts over 23 days |
| SNS when its own region is down | No delivery. Accepted messages wait, then arrive after the outage | AWS documents storage across data centers, but not this case |
API pods for each region (HPA_MIN, HPA_MAX) | 1 minimum, 10 maximum | Set by you |
HPA poll (HPA_POLL) | 3 s | 15 s default |
HPA target (HPA_TARGET) | 1 request (in flight + queued) for each pod | Set by you |
HPA scale-down window (HPA_DOWN) | 10 s | 300 s default |
Time until a new API pod is ready (POD_START) | 2 s | — |
Requests at the same time for each API pod (API_CONC) | 2 | — |
Default API time (apiTime) | 0.25 s (range 0.05 to 2 s) | — |
Serverless v2 capacity (acuMin, acuMax) | 0.5 ACU minimum (fixed), 8 ACU maximum by default (range 1 to 32) | 0 or 0.5 to 256 ACU |
ACU poll (ACU_POLL) | 1 s | — |
ACU demand for each statement (ACU_WRITE, ACU_READ, ACU_SCAN) | Write 0.45 ACU-seconds, read 0.15 ACU-seconds, Cube query 0.6 ACU-seconds | — |
ACU thresholds (ACU_UP, ACU_DOWN, ACU_DOWN_POLLS) | Scale up above 70%; scale down below 30% after 3 polls | Real scaling uses CPU, memory and network load |
Statement time (DB_OP, DB_SLOW) | 0.12 s; up to 4 times longer above 100% utilization. A Cube query takes 4 times longer (SCAN_TIME) | — |
| Parallel statements for each instance | ACU × 2, rounded, minimum 1 | — |
Reader lag in the primary cluster (LOCAL_LAG) | 0.15 s | Much less than 100 ms |
Default replication lag to us-east-2 (replLag) | 0.8 s (range 0.1 to 5 s) | Typically less than 1 s |
In-region failover (DB_LOCAL_FAILOVER) | 3 s (switchover: also 3 s) | Typically less than 60 s |
Global failover (DB_PROMOTE) | 5 s | Minutes |
Global failover switch (dbFailover) | On | — |
Read before write (readCheck) | Off | — |
| Travel times |
| — |
| Random seed | 7 (results repeat after Reset) | — |
6.3Scenario results
The read path results are in their own table.
These tables are the output of npm run scenarios -- --node on 2026-11-02. Each scenario has 60 simulated seconds of traffic at 2 events/s, if the scenario does not set a different rate. The run is seeded, so the numbers repeat. These scenarios run with the read path off, so they show ingestion only. Simulation
Messages
| Scenario | Published | Publish failed | Unique done | Deliveries / event | Duplicates | SQS DLQ | Artemis DLQ | Stuck in Artemis | Arrivals B1/B2/B3 | Stored B1/B2/B3 |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 Baseline: per-region queues | 120 | 0 | 120 | 1.00 | 0 | 0 | 0 | 0 | 52 / 8 / 0 | 40 / 40 / 40 |
| 2 One topic → both regions | 120 | 0 | 120 | 2.00 | 120 | 0 | 0 | 0 | 38 / 31 / 51 | 80 / 80 / 80 |
| 3 Same queue name, both consume | 120 | 0 | 120 | 2.00 | 120 | 0 | 0 | 0 | 33 / 5 / 22 | 40 / 40 / 40 |
| 4 Same queue name, us-east-1 only | 120 | 0 | 120 | 1.00 | 0 | 0 | 0 | 0 | 52 / 8 / 0 | 40 / 40 / 40 |
| 5 One topic + same name, both consume | 120 | 0 | 120 | 3.56 | 307 | 0 | 0 | 0 | 38 / 0 / 82 | 80 / 80 / 80 |
| 6 Poison 10% at Avro | 120 | 0 | 107 | 1.00 | 0 | 13 | 0 | 0 | 33 / 22 / 0 | 37 / 35 / 35 |
| 7 Poison 10% at the API (422) | 120 | 0 | 110 | 1.00 | 0 | 0 | 10 | 0 | 30 / 8 / 22 | 40 / 40 / 40 |
| 8 Transient failures 20% (API 500) | 120 | 0 | 119 | 1.00 | 0 | 0 | 1 | 0 | 40 / 14 / 6 | 40 / 40 / 40 |
| 9 Slow consumers, kill B1 at 20 s, restart at 30 s | 180 | 0 | 178 | 1.00 | 0 | 0 | 0 | 4 | 28 / 26 / 36 | 36 / 72 / 72 |
| 10 Same as 9, with redistribution | 180 | 0 | 180 | 1.00 | 0 | 0 | 0 | 0 | 28 / 26 / 36 | 36 / 72 / 72 |
| 11 us-east-1 outage from 5 s to 15 s (global failover on) | 120 | 0 | 119 | 1.01 | 1 | 0 | 1 | 0 | 27 / 10 / 13 | 40 / 40 / 38 |
| 12 Same as 11, with redistribution | 120 | 0 | 119 | 1.01 | 1 | 0 | 1 | 0 | 27 / 10 / 13 | 40 / 40 / 38 |
| 13 us-east-1 outage, one topic | 120 | 20 | 100 | 2.00 | 100 | 0 | 1 | 0 | 44 / 0 / 56 | 68 / 66 / 65 |
| 14 New TCP connection for each message | 180 | 0 | 180 | 1.00 | 0 | 0 | 0 | 0 | 29 / 29 / 32 | 60 / 60 / 60 |
| 15 Rate 8 events/s: HPA and writer ACUs | 480 | 0 | 480 | 1.00 | 0 | 0 | 0 | 0 | 60 / 108 / 72 | 160 / 160 / 160 |
| 16 Bursts of 80 at 10 s and 30 s | 280 | 0 | 280 | 1.00 | 0 | 0 | 0 | 0 | 84 / 56 / 0 | 94 / 94 / 92 |
| 17 Same name, both consume, replica lag 2 s, read before write | 120 | 0 | 120 | 2.00 | 120 | 0 | 0 | 0 | 30 / 5 / 25 | 40 / 40 / 40 |
| 18 Rate 6, us-east-1 outage 20–30 s, global failover on | 360 | 0 | 354 | 1.01 | 3 | 0 | 8 | 0 | 89 / 22 / 38 | 119 / 118 / 118 |
| 19 Rate 6, us-east-1 outage 20–30 s, global failover off | 360 | 0 | 347 | 1.00 | 0 | 0 | 18 | 0 | 88 / 46 / 15 | 119 / 120 / 119 |
| 20 Kill e1-1 at 20 s, restart at 30 s, switchover at 35 s | 120 | 0 | 120 | 1.00 | 0 | 0 | 0 | 0 | 30 / 8 / 22 | 40 / 40 / 40 |
| 21 Writer down 20–26 s (no replica), redelivery delay 0 | 120 | 0 | 112 | 1.00 | 0 | 0 | 8 | 0 | 52 / 8 / 0 | 40 / 40 / 40 |
| 22 Same as 21, redelivery delay 3 s | 120 | 0 | 120 | 1.00 | 0 | 0 | 0 | 0 | 33 / 27 / 0 | 40 / 40 / 40 |
| 23 Kill e2-1 at 20 s, restart at 25 s | 120 | 0 | 120 | 1.00 | 0 | 0 | 0 | 0 | 52 / 8 / 0 | 40 / 40 / 40 |
| 24 Kill e1-1 at 20 s, restart at 21 s, switchover at 22 s | 120 | 0 | 112 | 1.00 | 0 | 0 | 8 | 0 | 52 / 8 / 0 | 40 / 40 / 40 |
| 25 Burst 1000 in 1.5 s, capacity 10 / 10 / 10 | 1000 | 0 | 1000 | 1.00 | 0 | 0 | 0 | 0 | 201 / 150 / 149 | 334 / 334 / 332 |
| 26 Burst 1000 in 1.5 s, capacity 50 / 50 / 50 | 1000 | 0 | 1000 | 1.00 | 0 | 0 | 0 | 0 | 161 / 219 / 120 | 334 / 334 / 332 |
API and Aurora
| Scenario | Rows in Aurora | Dedupe at read | Dedupe at write | API 4xx | API 5xx | Max API pods e1 / e2 | Max writer ACU | Rows lost | Acked, no row | Writer at the end | Drain time (s) | Check |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 Baseline: per-region queues | 120 | 0 | 0 | 0 | 0 | 2 / 3 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 2 One topic → both regions | 120 | 0 | 120 | 0 | 0 | 3 / 4 | 6 | 0 | 0 | us-east-1 (e1-1) | 67.6 | ok |
| 3 Same queue name, both consume | 120 | 0 | 120 | 0 | 0 | 3 / 5 | 7 | 0 | 0 | us-east-1 (e1-1) | 69.8 | ok |
| 4 Same queue name, us-east-1 only | 120 | 0 | 0 | 0 | 0 | 3 / 1 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 5 One topic + same name, both consume | 120 | 0 | 307 | 0 | 0 | 5 / 7 | 8 | 0 | 0 | us-east-1 (e1-1) | 71.8 | ok |
| 6 Poison 10% at Avro | 107 | 0 | 0 | 0 | 0 | 2 / 2 | 4 | 0 | 0 | us-east-1 (e1-1) | 70.9 | ok |
| 7 Poison 10% at the API (422) | 110 | 0 | 0 | 30 | 0 | 2 / 3 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 69.5 | ok |
| 8 Transient failures 20% (API 500) | 119 | 0 | 0 | 0 | 30 | 2 / 4 | 4 | 0 | 0 | us-east-1 (e1-1) | 70.7 | ok |
| 9 Slow consumers, kill B1 at 20 s, restart at 30 s | 178 | 0 | 0 | 0 | 0 | 2 / 3 | 4 | 0 | 0 | us-east-1 (e1-1) | 98.5 | ok |
| 10 Same as 9, with redistribution | 180 | 0 | 0 | 0 | 0 | 2 / 3 | 4 | 0 | 0 | us-east-1 (e1-1) | 98.5 | ok |
| 11 us-east-1 outage from 5 s to 15 s (global failover on) | 119 | 0 | 1 | 0 | 8 | 3 / 2 | 4 | 0 | 0 | us-east-2 (e2-1) | 66.2 | ok |
| 12 Same as 11, with redistribution | 119 | 0 | 1 | 0 | 8 | 3 / 2 | 4 | 0 | 0 | us-east-2 (e2-1) | 66.2 | ok |
| 13 us-east-1 outage, one topic | 100 | 0 | 100 | 0 | 8 | 4 / 3 | 6 | 0 | 0 | us-east-2 (e2-1) | 67.7 | ok |
| 14 New TCP connection for each message | 180 | 0 | 0 | 0 | 0 | 3 / 4 | 4 | 0 | 0 | us-east-1 (e1-1) | 69.3 | ok |
| 15 Rate 8 events/s: HPA and writer ACUs | 480 | 0 | 0 | 0 | 0 | 5 / 6 | 8 | 0 | 0 | us-east-1 (e1-1) | 107.9 | ok |
| 16 Bursts of 80 at 10 s and 30 s | 280 | 0 | 0 | 0 | 0 | 5 / 8 | 8 | 0 | 0 | us-east-1 (e1-1) | 72.2 | ok |
| 17 Same name, both consume, replica lag 2 s, read before write | 120 | 35 | 85 | 0 | 0 | 4 / 4 | 6 | 0 | 0 | us-east-1 (e1-1) | 69.6 | ok |
| 18 Rate 6, us-east-1 outage 20–30 s, global failover on | 352 | 0 | 3 | 0 | 28 | 8 / 5 | 8 | 3 | 2 | us-east-2 (e2-1) | 90 | ok |
| 19 Rate 6, us-east-1 outage 20–30 s, global failover off | 347 | 0 | 1 | 0 | 58 | 7 / 6 | 8 | 0 | 0 | us-east-1 (e1-1) | 103.3 | ok |
| 20 Kill e1-1 at 20 s, restart at 30 s, switchover at 35 s | 120 | 0 | 0 | 0 | 25 | 4 / 5 | 4 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 21 Writer down 20–26 s (no replica), redelivery delay 0 | 112 | 0 | 0 | 0 | 37 | 3 / 5 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 22 Same as 21, redelivery delay 3 s | 120 | 0 | 0 | 0 | 22 | 3 / 6 | 4 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 23 Kill e2-1 at 20 s, restart at 25 s | 120 | 0 | 0 | 0 | 0 | 2 / 3 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 24 Kill e1-1 at 20 s, restart at 21 s, switchover at 22 s | 112 | 0 | 0 | 0 | 37 | 3 / 5 | 2.5 | 0 | 0 | us-east-1 (e1-1) | 68.7 | ok |
| 25 Burst 1000 in 1.5 s, capacity 10 / 10 / 10 | 1000 | 0 | 0 | 0 | 0 | 8 / 9 | 8 | 0 | 0 | us-east-1 (e1-1) | 213.1 | ok |
| 26 Burst 1000 in 1.5 s, capacity 50 / 50 / 50 | 1000 | 0 | 0 | 0 | 0 | 48 / 50 | 8 | 0 | 0 | us-east-1 (e1-1) | 63.7 | ok |
- Scenario 1: the arrivals are not equal (52 / 8 / 0), but the stored messages are equal (40 / 40 / 40). The NLB pins connections, and ON_DEMAND spreads the messages.
- Scenario 9: 4 messages stay stranded on broker 1. This is two events, each with its mirror copy.
- Scenario 11: the one duplicate is the failover edge condition. us-east-1 processed the message, but its ack did not get to us-east-2 before the outage. One message goes to the Artemis DLQ: three fast 503 responses during the promotion. At the end, the writer is
e2-1, because the script does not do a switchover. - Scenario 13: 20 publishes fail. With one topic, the region of the topic is a single point of failure.
- Scenarios 2, 3 and 5: each duplicate is removed at the write. The rows stay equal to the unique events.
- Scenario 15: the writer gets to 8 ACU, the maximum. The API increases to 5 pods in us-east-1 and 6 pods in us-east-2.
- Scenario 17: 35 duplicates stop at the read and 85 stop at the write. The read on a reader that is 2 s behind misses most duplicates.
- Scenario 18: the global failover loses 3 rows (RPO). A later delivery of one of these events wrote its row again. The other 2 events have no row at the end, although a consumer acked them (acked, no row 2).
- Scenario 19: with no global failover, each write gets 503 for the full outage. 18 messages go to the Artemis DLQ, but no rows are lost.
- Scenarios 21 and 22: with redelivery delay 0, 8 messages go to the Artemis DLQ. With 3 s, no message does.
- Scenario 23: with the read path off, the write-only API does not use
e2-1. Thus a stopped reader has no effect on the requests. - Scenarios 25 and 26: the same spike of 1000 events. With capacity 10 / 10 / 10, the drain takes 212.7 s. With 50 / 50 / 50, it takes 68 s. At 50, the writer is at its maximum of 8 ACU, so Aurora is the next limit.
Artemis DLQ processing
These scenarios ran on 2026-11-02. With the processor on, each message that went to the Artemis DLQ is in S3 at the end. With it off, each one is still in the DLQ. Messages in the SQS DLQ always stay.
| Scenario | SQS DLQ | Moved to Artemis DLQ | Waiting in Artemis DLQ | Objects in S3 | S3 overwrites | Drain time (s) |
|---|---|---|---|---|---|---|
| 27 Poison 10% + 10%, processor on | 9 | 8 | 0 | 8 | 0 | 72.2 |
| 28 Poison 10% + 10%, processor off | 9 | 8 | 8 | 0 | 0 | 72.2 |
| 29 Same, processor off until 30 s | 9 | 8 | 0 | 8 | 0 | 72.2 |
| 30 Poison 20% + 20%, us-east-1 outage 20–30 s | 22 | 32 | 0 | 32 | 0 | 72.1 |
| 31 Poison 20% at the API, kill B1 at 20 s, restart at 30 s | 0 | 28 | 0 | 28 | 0 | 73.4 |
Read path
These rows are the output of the same command on 2026-11-03. Scenarios 1 to 31 run with the read path off, so the tables above stay the same. All 12 rows show ok in the column check. Last event done is the time until the last event finishes. Drain also includes the queries that wait in the Query API.
| Scenario | Unique done | Rows | Queries sent | Answered | Failed | Skipped | Max Query API pods e1/e2 | Max API pods e1/e2 | Max e2-1 ACU | Last event done (s) | Drain (s) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 32 Read path on, default rates | 120 | 120 | 120 | 120 | 0 | 0 | 4 / 4 | 3 / 4 | 1.5 | 67.4 | 68.2 |
| 33 Read path, heavy: Center 5/s, Cube 3/s per region | 120 | 120 | 471 | 471 | 0 | 489 | 5 / 5 | 8 / 8 | 4 | 68 | 91.1 |
| 34 Read path, heavy, API time 2 s: the UIs hit their open-query limit | 120 | 120 | 280 | 280 | 0 | 680 | 5 / 5 | 10 / 10 | 3 | 69.7 | 121.6 |
| 35 Read path, us-east-1 outage 20–30s | 117 | 115 | 110 | 106 | 4 | 0 | 4 / 4 | 4 / 5 | 4 | 67.6 | 68.4 |
| 36 Read path, kill us-east-2 reader @20s, restart @25s (nothing left to read there) | 120 | 120 | 120 | 115 | 5 | 0 | 4 / 4 | 3 / 4 | 1.5 | 67.4 | 68.2 |
| 37 Read path, kill us-east-1 reader @20s, restart @25s (reads fall back to the writer) | 120 | 120 | 120 | 120 | 0 | 0 | 4 / 4 | 3 / 4 | 1.5 | 67.4 | 68.2 |
| 38 Read path on until @30s, then ingestion only | 120 | 120 | 60 | 60 | 0 | 0 | 4 / 3 | 3 / 3 | 1.5 | 68 | 68.7 |
| 39 API time 2 s, ingestion only (compare with 34) | 120 | 120 | 0 | 0 | 0 | 0 | 1 / 1 | 4 / 5 | 0.5 | 69.2 | 69.9 |
| 40 Rate 6, API time 2 s, heavy reads | 360 | 360 | 281 | 281 | 0 | 679 | 5 / 5 | 10 / 10 | 4 | 135 | 135.8 |
| 41 Rate 6, API time 2 s, ingestion only (compare with 40) | 360 | 360 | 0 | 0 | 0 | 0 | 1 / 1 | 8 / 8 | 0.5 | 134.7 | 135.5 |
| 42 Same reads as 33, Query API max 20 | 120 | 120 | 725 | 725 | 0 | 235 | 20 / 20 | 10 / 10 | 6 | 68 | 68.7 |
| 43 Same, Query API max 20, lowered to 5 @30s (pods above it drain, then stop) | 120 | 120 | 546 | 546 | 0 | 414 | 20 / 20 | 10 / 10 | 6 | 67.4 | 84.8 |
Compare scenarios 34 and 39, and scenarios 40 and 41. The reads increase the API pods and the ACUs of the reader. They have almost no effect on the time until the last event finishes.
Mirrored queues
These rows are the output of the same command on 2026-11-03. All 8 rows show ok in the column check. In scenarios 50 and 51, us-east-1 does not come back. Stuck in Artemis then counts the messages on its brokers. With mirroring on, this count includes copies of messages that us-east-2 already processed.
| Scenario | Published | Unique done | Deliveries / event | Duplicates | Mirror copies | Stuck in Artemis | Rows | Rows lost | Last event done (s) |
|---|---|---|---|---|---|---|---|---|---|
| 44 Mirroring off | 120 | 120 | 1.00 | 0 | 0 | 0 | 120 | 0 | 68 |
| 45 us-east-1 outage 20–30s, mirroring on | 120 | 117 | 1.01 | 1 | 133 | 0 | 116 | 1 | 68.2 |
| 46 us-east-1 outage 20–30s, mirroring off (compare with 45) | 120 | 116 | 1.00 | 0 | 0 | 0 | 115 | 1 | 67.6 |
| 47 Same queue name, both regions consume, mirroring off (compare with 3) | 120 | 120 | 1.00 | 0 | 0 | 0 | 120 | 0 | 68 |
| 48 Mirroring on until @30s, then off | 120 | 120 | 1.00 | 0 | 44 | 0 | 120 | 0 | 68 |
| 49 Mirroring off until @30s, then on, us-east-1 outage @40–50s | 120 | 119 | 1.03 | 3 | 88 | 0 | 117 | 3 | 67.4 |
| 50 Slow consumers, us-east-1 lost for good @20s, mirroring on | 180 | 169 | 1.00 | 0 | 169 | 16 | 169 | 0 | 163.2 |
| 51 Slow consumers, us-east-1 lost for good @20s, mirroring off (compare with 50) | 180 | 161 | 1.00 | 0 | 0 | 8 | 161 | 0 | 157.6 |
Compare scenarios 45 and 46: a short outage has almost the same result, because the messages of us-east-1 wait and then finish. Compare scenarios 50 and 51: if the region does not come back, mirroring saves the messages that were in Artemis (169 and 161 events done). It does not save the messages that were in the SQS queue of that region. Compare scenarios 3 and 47: with no copies, one shared queue name gives no duplicates.
7Glossary
| Term | Meaning in this manual |
|---|---|
| Ack | Acknowledgement. A consumer sends it to tell the broker that it processed a message. |
| ACU | Aurora capacity unit. The capacity unit of Serverless v2: approximately 2 GiB of memory with matching CPU and network. |
| Address (Artemis) | The destination to which producers send. Queues are bound to an address. |
| ALB | Application Load Balancer. A layer 7 AWS load balancer. It selects a target for each HTTP request. |
| AMQP | Advanced Message Queuing Protocol. The mirror connections use AMQP. The pods and consumers use Core. |
| API | The HTTP service after the Artemis consumers. It stores each event in Aurora. Each region has one API Deployment with 1 to 10 pods (maximum 10 to 50 in the panel). |
| Artemis | Apache ActiveMQ Artemis, the message broker software. |
| At-least-once | Each message is delivered one or more times. Duplicates are possible. |
| Aurora | Amazon Aurora PostgreSQL, the database of the model. |
| Avro | Apache Avro, a compact binary format. A reader needs the schema of the writer. |
| Broker | One Artemis server. Each region has three brokers: B1, B2 and B3. |
| Center, Cube | The two UIs of the read path. Each is a separate deployment. Cube is for analytics. |
| Cluster connection, core bridge | The internal link that brokers in one cluster use to send messages to each other. |
| Consumer | An Artemis client that receives messages from a queue. Each region has 10. |
| Core | The protocol and client API of Artemis. The pods and consumers use it over TCP. |
| Cross-region delivery | An SNS topic in one region that sends to an SQS queue in a different region. |
| Cross-region write | A write from the us-east-2 API to the writer in us-east-1. It pays for a cross-region round trip. |
| Dedupe at read | The API finds the row on its local reader and does not write. Only with Read before write on. |
| Dedupe at write | The write INSERT … ON CONFLICT (event_id) DO NOTHING finds the row already on the writer and inserts nothing. |
| DLA | Dead-letter address. The Artemis address that gets messages after max-delivery-attempts. The app calls it “Artemis DLQ”. |
| DLQ | Dead-letter queue. A queue for messages that fail too many times. |
| DLQ processor | A Deployment that moves messages from the Artemis DLQ to S3. It acks a message only after S3 sends 200. |
| Downstream | The system that processes the message after the consumer receives it. In this model, the API. |
| Duplicate | A second or later processing of the same event. |
| ENI | Elastic network interface. An interface endpoint is a set of ENIs. |
| Endpoint service | The PrivateLink service in the broker VPC. An NLB is in front of it. |
| Event | The item that the producer publishes. Each event has an event id. |
| Event id | The unique identifier of an event. The API uses it as the unique key (event_id) to remove duplicates. |
| Failover, failback | Failover: the other region takes over the standby copy. Failback: the original region consumes its own queue again. |
| Failover (Aurora) | An unplanned change of the writer. In-region failover: Aurora promotes a reader in the same cluster, with no data loss. Global failover: Aurora promotes the secondary cluster. A global failover can lose the rows that did not replicate (RPO). |
| Fan-out | SNS sends one copy of each message to each subscription. |
| Flow hash | The NLB calculation that selects one target for one TCP connection. |
| Global Database | An Aurora feature that replicates one primary cluster to read-only secondary clusters in other regions, at the storage layer. |
| Global writer endpoint | The Global Database endpoint that always points to the current writer, also after a switchover or a failover. |
| HPA | HorizontalPodAutoscaler. The Kubernetes resource that sets the number of API pods from a metric: ⌈current × metric ÷ target⌉. |
| HTTP status 2xx, 4xx, 5xx | The API response classes. 2xx: success (200). 4xx: the request is not correct (422: the API always rejects the payload). 5xx: a server error (500: transient; 503: the database is not available). |
| Idempotent write | A write that gives the same result if you do it one time or many times. Here: INSERT … ON CONFLICT (event_id) DO NOTHING. |
| In flight | An SQS message that a pod received but did not delete. It is invisible. |
| In flight (API) | A request that an API pod started but did not answer yet. Each pod has up to 2 in the app. |
| Interface endpoint | The PrivateLink endpoint in the client VPC. |
| Jolokia | An HTTP interface to JMX data. The KEDA Artemis scaler reads the message count through it. |
| KEDA | Kubernetes Event-driven Autoscaling. It sets the number of pods from the queue length. |
maxReceiveCount | The SQS redrive limit. A message with a higher ReceiveCount goes to the DLQ. |
max-delivery-attempts | The Artemis redelivery limit. Default 10. |
| Message | One stored copy of an event in an SQS queue or in Artemis. |
| Mirror | An Artemis AMQP broker connection that copies sends and acks to a broker in the other region. “Dual mirror” means in the two directions. |
| Mirror backlog | Mirror events that wait because the target is down. |
| MULTICAST | An Artemis routing type. Each bound queue gets a copy of each message. |
| NLB | Network Load Balancer. An AWS layer 4 (TCP) load balancer. |
ON CONFLICT … DO NOTHING | A PostgreSQL INSERT clause. If a row with the same unique key exists, the insert does nothing and causes no error. It needs a unique index or constraint. |
| ON_DEMAND | The default value of message-load-balancing. Messages go round-robin to brokers whose queue has consumers. |
| Pod | A Kubernetes pod that receives from SQS, converts to Avro and sends to Artemis. |
| Poison message | A message that fails on each attempt. |
| Pull | The client asks for a message, and the answer carries it. SQS works in this way. |
| Push | The broker sends a message to a connected client with no ask for each message. Artemis works in this way. |
| Primary cluster | The Aurora cluster that holds the writer. Here: e1-aurora in us-east-1, with e1-1 and e1-2. |
| PrivateLink | An AWS service for private connections between VPCs. |
| Producer | The client that publishes events to SNS. |
| Promotion tier | The priority of an Aurora reader for a failover, from 0 (first) to 15. A Serverless v2 reader in tier 0 or 1 keeps at least the capacity of the writer. |
queueLength | The KEDA target number of messages for each pod. |
| Query API | The API between the two UIs and the existing API. It only reads. Each region has one Deployment with 1 to 5 pods (HPA; maximum 5 to 50 in the panel). |
| Raw message delivery | An SNS setting that removes the SNS JSON envelope. |
| Read before write | A panel switch (readCheck, default off). On: the API reads its local reader before it writes. |
| Read path | Center, Cube, the Query API and their queries. Set Show to hide it. |
| Reader | An Aurora instance that serves only reads: e1-2 and e2-1 at the start. |
redelivery-delay | The Artemis time before a rolled-back message is delivered again. Default 0. |
Redistribution, redistribution-delay | Artemis moves stored messages off a broker whose queue has no consumers. −1 means never. |
| Redrive | The SQS move of a message to its DLQ. |
| Region | An AWS region: us-east-1 (N. Virginia) or us-east-2 (Ohio). In part ids, e1 and e2. |
| Replica lag | The time from a commit on the writer until the row is visible on a reader. In the same cluster: milliseconds. To the secondary region: typically less than 1 s. |
| Rollback | A consumer tells the broker that processing failed. The broker then delivers the message again. |
| RPO | Recovery point objective. The quantity of committed data that a failover can lose. Here: the rows that did not replicate to e2-1. |
| RTO | Recovery time objective. The time until the system accepts writes again after a failure. |
| S3 | Amazon Simple Storage Service, an object store. Each region has one bucket for messages from the Artemis DLQ. |
| ScaledObject | The KEDA resource that connects a scaler to a workload. |
| Schema registry | A service that stores Avro schemas. A record carries the schema id. |
| Secondary cluster | A read-only Aurora cluster in another region of the Global Database. Here: e2-aurora in us-east-2, with e2-1. |
| Serverless v2 | An Aurora capacity mode. Each instance scales between a minimum and a maximum number of ACUs, in steps as small as 0.5 ACU. |
| SNS | Amazon Simple Notification Service. A publish-subscribe service. |
| SQS | Amazon Simple Queue Service. A message queue service. |
| Stabilization window | The HPA time before a scale-down. The HPA uses the highest recommendation in this window. Default 300 s; 10 s in the app. |
| Standby copy | The mirror copy of the queue of the other region. Nobody reads it until a failover. |
| Switchover | A planned change of the Aurora writer. It waits until the target is up to date, so it loses no data (RPO 0). |
sync=true | A mirror option. The client waits until the mirror has the copy. |
| Transient failure | A failure that occurs at random on one attempt. A retry usually succeeds. |
use1.*, use2.* | The address and queue names of us-east-1 and us-east-2. |
| Visibility timeout | The time for which SQS hides a received message. |
| VPC | Virtual Private Cloud. A private network in AWS. |
| Write forwarding | An Aurora Global Database option. A reader in a secondary cluster sends the writes that it receives to the writer. |
| Writer | The one Aurora instance that accepts writes: e1-1 at the start. |
8Sources
- Apache ActiveMQ Artemis. AMQP broker connections and mirror. artemis.apache.org
- The same page, earlier location. activemq.apache.org
- Amazon SNS. Cross-region delivery. docs.aws.amazon.com
- Amazon SNS. Raw message delivery; delivery retries; dead-letter queues.
- Amazon SQS. Standard queues. docs.aws.amazon.com
- Amazon SQS. Dead-letter queues; visibility timeout; SetQueueAttributes.
- AWS PrivateLink. Endpoint services. docs.aws.amazon.com
- Elastic Load Balancing. NLB introduction; Network Load Balancers; how Elastic Load Balancing works.
- KEDA. aws-sqs-queue scaler; ScaledObject specification; Artemis scaler.
- Apache Avro 1.12 specification. avro.apache.org
- Confluent Schema Registry. Wire format. docs.confluent.io
- Apache ActiveMQ Artemis. Address model (MULTICAST, ANYCAST) and mirror notes. artemis.apache.org
- Apache ActiveMQ Artemis. Clusters: load balancing and redistribution. artemis.apache.org
- Apache ActiveMQ Artemis. Address settings: dead-letter address and
max-delivery-attempts. activemq.apache.org - Kubernetes. Horizontal Pod Autoscaling: sync period, algorithm, stabilization window and tolerance. kubernetes.io (examined 2026-10-23)
- Amazon Aurora. How Aurora Serverless v2 works: ACU, 0.5-ACU steps, capacity ranges and promotion tiers. docs.aws.amazon.com (examined 2026-10-23)
- Amazon Aurora. Serverless v2 auto-pause (scaling to 0 ACU). docs.aws.amazon.com (linked from [16])
- Amazon Aurora. Using Amazon Aurora Global Database: storage-level replication, lag less than 1 s, read-only secondaries, up to 10. docs.aws.amazon.com (examined 2026-10-23)
- Amazon Aurora. Global Database switchover and failover: RPO, RTO and
rds.global_db_rpo. docs.aws.amazon.com (examined 2026-10-23) - Amazon Aurora. Write forwarding in an Aurora PostgreSQL global database. docs.aws.amazon.com (examined 2026-10-23)
- Amazon Aurora. High availability: in-region failover and promotion tiers. docs.aws.amazon.com (examined 2026-10-23)
- PostgreSQL.
INSERT … ON CONFLICT. postgresql.org (examined 2026-10-23) - Amazon Aurora. Replication with Amazon Aurora: replica lag usually much less than 100 ms. docs.aws.amazon.com (examined 2026-10-23)
- Amazon S3. Data consistency model.
- Elastic Load Balancing. Target group attributes for an Application Load Balancer.
- Amazon SQS. ReceiveMessage.
- Apache ActiveMQ Artemis. Flow control; Core.
- Apache ActiveMQ Artemis. Detecting dead connections.
Project files: research/notes.md (research notes and numbers), README.md (scenarios, controls, deploy), CONTRACT.md (design of the API and Aurora parts), src/content/topic.js (tour, panel, legend), src/content/info.js (parts), src/content/sim.js (simulation rules and timings) and scripts/scenarios.mjs (scenario list).