This content originally appeared on DEV Community and was authored by Miguel Angel Muñoz Sanchez
In the previous article, we explored how we arrived at Amazon Aurora DSQL and why we need a database that allows consistent writes across multiple regions. At the end of that article, we posed the key question: How did AWS achieve a database with multi-regional writes without breaking ACID?
The answer lies in something that seems impossible: controlling time.
In this article, we will explore how AWS has enabled Aurora DSQL to achieve something that seemed impossible or at least very complex: allowing a Postgres database to support writes across multiple regions with consistency.
In this article, we will cover:
- The fundamental problem of time in distributed databases
- AWS’s solution: Global-scale time synchronization
- Aurora DSQL: Real distributed synchronization architecture
- Comparison with Aurora PostgreSQL
- Limitations and considerations
The Fundamental Problem: Time in Distributed Databases
Before understanding how Aurora DSQL solves the problem, we need to comprehend why time presents such complexity in distributed systems.
The CAP Theorem or Brewer’s Theorem
In 2000, Eric Brewer formulated the CAP Theorem, which establishes that in a distributed system, you can only guarantee two of these three properties:
- Consistency: All nodes see the same data at the same time
- Availability: The system continues functioning even when some nodes fail
- Partition tolerance: The system continues operating even when communication between nodes breaks
Any database, such as Aurora PostgreSQL or Aurora MySQL, prioritizes Consistency and Availability over partition tolerance. That’s why for writes in Aurora PostgreSQL, we have a primary node and the rest serve as secondary replicas, both within the same region and across multiple regions.
But Aurora DSQL requires all three properties to work together, which demands an entirely different approach.
The Temporal Ordering Problem
Imagine this sequence of events:
Region us-east-1 (10:00:00.020123): UPDATE users SET balance = 900 WHERE id = 123
Region us-west-2 (10:00:00.019548): UPDATE users SET balance = 200 WHERE id = 123
Region eu-west-1 (10:00:00.022020): UPDATE users SET balance = 500 WHERE id = 123
Region eu-south-2 (10:00:00.019386): UPDATE users SET balance = 300 WHERE id = 123
Which of these transactions should execute first?
These executions occur in 4 different regions within the exact second, using the same data, but with different values.
If we use a system like Aurora PostgreSQL with a primary node in the us-east-1 region, the correct data would come from this region, since we would need to add latency to the other executions, which would produce inconsistencies.
Beyond this problem, we assume that systems synchronize completely to the millisecond, but this doesn’t always happen. Time synchronization presents significant challenges.
Finally, we see an example of a single action, while a transaction involves several nested actions, which can create even bigger problems.
This represents the problem Aurora DSQL must solve: Create a global and consistent temporal order for all transactions, regardless of where they originate, and maintain real, synchronized time across regions.
AWS’s Solution: Global-Scale Time Synchronization
Traditionally, any data center has used NTP (Network Time Protocol) to synchronize with atomic clocks that achieve nanosecond-level precision. However, NTP presents critical limitations for systems requiring extreme precision, as it depends on connected networks and therefore suffers from network latency, potentially achieving only millisecond precision.
AWS needed much greater synchronization between regions if it wanted to build a system like Aurora DSQL.
Amazon Time Sync Service
AWS has invested heavily in synchronizing its infrastructures, creating the Amazon Time Sync Service for this purpose.
Amazon Time Sync Service uses a fleet of satellites with atomic clocks that connect with all AWS regions, providing the same time to all of them.
Atomic clocks are expensive, but satellite positioning systems like GPS require them.
They offer an additional advantage: when they send time data, we know precisely how far away the satellite is and therefore the signal latency with very high precision.
Unlike NTP services against ground-based atomic clocks, satellite-based latency remains stable and therefore provides much greater precision.
The precision achieved reaches nanosecond levels between regions, something unthinkable just a few years ago.
ClockBound: Measuring Time Precision
Complementing Time Sync Service, AWS has developed ClockBound, an open-source daemon and library that measures EC2 instance clock precision and enables determination of the real temporal order of events.
Although Amazon Time Sync Service provides exact time, minor synchronization deviations always exist. ClockBound quantifies these synchronization deviations and adds fundamental functionalities for Amazon DSQL:
Main functionalities:
-
Time intervals with uncertainty: Instead of an exact timestamp like
10:00:00.123456789
, ClockBound provides a range[10:00:00.123456785, 10:00:00.123456793]
that guarantees the real time falls within that interval. - Definitive temporal comparison: It can determine if event A definitely occurs before event B, or if they potentially happen concurrently.
- Concurrency detection: It identifies when two events may have co-occurred, which proves crucial for resolving transaction conflicts in DSQL.
- Wait optimization: It calculates the minimum time an application must wait to guarantee a globally unique timestamp.
With ClockBound, we can compare our time sequence more precisely:
Event us-east-1: [10:00:00.020120, 10:00:00.020126]
Event us-west-2: [10:00:00.019545, 10:00:00.019551]
Result: us-west-2 definitely occurred before us-east-1
Event eu-west-1: [10:00:00.022018, 10:00:00.022022]
Event eu-south-2: [10:00:00.019384, 10:00:00.019388]
Result: eu-south-2 definitely occurred before eu-west-1
Event us-east-1: [10:00:00.020120, 10:00:00.020126]
Event us-west-2: [10:00:00.019545, 10:00:00.019551]
Conflicting event: [10:00:00.019550, 10:00:00.020125]
Result: The conflicting event overlaps with both
As you can imagine, without ClockBound, Aurora DSQL probably wouldn’t exist.
By the way, although AWS developed ClockBound, it’s an open-source project that anyone can use.
Aurora DSQL: Real Distributed Synchronization Architecture
Now it’s time to start discussing how Aurora DSQL works and what components make it up.
Now that we understand how AWS has solved the time problem, we can explore how Aurora DSQL uses this technology to create a truly distributed database that maintains ACID properties at a global scale.
How Does DSQL Work?
Aurora DSQL isn’t simply an improved Aurora PostgreSQL. It represents an entirely new architecture that reimagines how a database should function to enable multi-region consistency.
Main components:
It’s important to understand that each layer scales horizontally, independently of other layers, and dynamically, all depending on the load we demand from our Aurora DSQL.
The horizontal scaling capability makes Aurora DSQL a completely serverless service.
Adjudicators:
Although Query Processor represents the first layer, let’s discuss the Adjudicator layer first.
The Adjudicator layer probably represents Aurora DSQL’s most innovative component.
These distributed processes implement consensus algorithms to ensure all regions agree on transaction order.
When a transaction needs to execute, the adjudicator layer checks if conflicts exist with any other recent transactions. For conflict detection, adjudicators use an optimized and distributed consensus algorithm that enables conflict detection.
Additionally, this layer scales horizontally, allowing multiple adjudicators for different database partitions, with each adjudicator handling a distinct space within our database.
In Aurora DSQL, all layers operate independently, allowing us to define different partition systems for each layer. Independent layer operation means adjudicators partition based on how we analyze conflicts between transactions, rather than how we store our data, which improves the task’s performance.
Aurora DSQL does not replicate adjudicators in each region; instead, the system distributes them across database spaces, allowing any adjudicator to change regions at any time.
Journal Layer:
The Journal records all transactions that Adjudicators approve.
The Journal resembles Aurora PostgreSQL’s WAL (Write-Ahead Log), but Aurora DSQL distributes it across multiple regions.
The Journal Layer allows each transaction approved by an adjudicator to write directly to the Journal.
The Journal Layer handles data durability, as Journal records remain immutable, distributed, and replicated in the regions we choose so that we can maintain reliable traceability of all transactions in the log with backup.
Storage:
Aurora DSQL’s storage layer extends the Aurora Storage concept but adds global distribution capabilities and temporal consistency.
One of Aurora DSQL Storage’s main advantages is that it doesn’t need to handle conflicts (adjudicator) or durability (journal), giving this layer more flexibility and enabling greater optimization.
Instead of basing the storage layer on synchronous data replication, Aurora DSQL bases storage distribution on data partitions, but adds sharding capabilities based not only on key partitions, but also on access time and regions, so the system distributes data more optimally while also replicating across different regions.
Traditional databases cannot achieve this.
Query Processor:
Now that we understand how the other layers work, we can discuss Query Processor.
Query Processor represents the layer that receives any Query and where Aurora DSQL receives SQL requests and converts them into distributed operations. Unlike Aurora PostgreSQL, no single “primary” node exists here.
As we saw before, our storage doesn’t partition in a standard way; data doesn’t replicate in the same storage, but is distributed across different storage partitions to improve performance.
The distributed storage approach makes read operations less efficient because we must determine the data’s location. The same happens with write operations since the adjudicator layer analyzes write operations before writing to the Journal and subsequently to the Storage layer.
Additionally, we’ve seen that all these layers distribute across different partitions that don’t need to match, meaning the adjudicator layer doesn’t partition the same way as the storage layer.
For this reason, Query Processor represents a fundamental layer, as it orchestrates all our queries, enabling it to know which partition contains data in storage for consultation or which adjudicator should handle any data write task.
Through query orchestration, the Query Processor layer minimizes latency by reducing tasks and loops that any transaction can generate.
Another advantage for maintaining consistency is that the Query Processor performs temporal ordering of reads and chooses the most optimal versioned data, so if we perform a read query on data that someone has modified after our read, but due to latency our write was faster, instead of returning the modified data, the system will give us the data version at the time of query execution.
Complete Diagram Example:
But how does this architecture improve on traditional architecture?
The architecture looks good, but does it work?
Strong Snapshot Isolation
Aurora DSQL uses a variation of the Optimistic Concurrency Control protocol instead of a traditional model. Optimistic Concurrency Control proves crucial for performance in a distributed environment.
The Optimistic Concurrency Control protocol essentially states that when executing a transaction that can compete with another, its commit can only proceed if it adheres to the defined isolation rules.
That’s why DSQL uses Strong Snapshot Isolation; this working mode allows multiple transactions to execute in parallel, following a series of rules:
- All transaction reads use the transaction start timestamp (Tstart).
- When committing data, the commit timestamp gets established (Tcommit).
- The transaction can execute the commit only if no other transaction has committed to the same key during the transaction start time (Tstart) and commit time (Tcommit).
- Writes execute using the Commit timestamp (Tcommit).
Strong Snapshot Isolation offers several advantages:
- We will never see data that doesn’t come after a commit.
- Reads remain repeatable and therefore cacheable.
- Reads come from a single point in time (logical).
- Conflicting writes get rejected; no writes get lost.
For this reason, the Query Processor and Adjudicator layers prove so important, as they handle the magic that makes this work.
Multi-Region Optimization
So far, what we’ve seen doesn’t seem very multi-regional, and we would still have latency problems. Still, we’ve seen a vital point: DSQL separates reads from writes and also has a fundamental component like Query Processor where writes queue before Commit execution.
Through write queuing, the entire transaction can execute locally, even with distributed data, since we only need the Adjudicator layer for the Commit execution, which we can perform even if the adjudicator operates in another region.
Local transaction execution significantly improves performance by avoiding unnecessary jumps between regions, resulting in a much lower absolute latency than expected.
FailOver Optimization
Another advantage is that the Adjudicator layer only stores recent transactions, making it easy to replicate in case of regional failure.
On the other hand, the Journal layer distributes and replicates data, ensuring that in case of failure, it replicates across multiple regions.
Finally, the storage layer, which always takes longer to replicate, has the capacity to redo all pending writes using the Journal layer.
Comparison with Aurora PostgreSQL or Traditional Databases
Elimination of the “Primary” Concept
In Aurora PostgreSQL, a primary node handles all writes.
In Aurora DSQL, we can initiate writes from any region, minimizing multi-region latencies.
Real Horizontal Scalability
Aurora DSQL can scale by adding more regions without performance degradation.
Faster FailOver
In Aurora PostgreSQL, if the primary region fails, one of the replicas in another region must become primary (1-2 minute process).
In Aurora DSQL, the failover process happens much faster because no primary exists, and the system distributes the load.
Horizontal Scaling
Aurora DSQL represents a database with completely decoupled layers, making horizontal scaling and descaling possible, which isn’t possible in Aurora PostgreSQL or traditional databases.
Additionally, it’s an entirely serverless database.
Limitations and Considerations
Aurora DSQL is a marvel, but it’s not suitable for everyone, so I don’t recommend it for everyone.
It’s an incredible database if your use case fits, but if you have a traditional use case, it’s probably not your database; it might even perform much worse than Aurora PostgreSQL.
It’s important to note that Aurora DSQL prioritizes consistency over extreme latency:
- Simple transactions: 10-50ms additional vs Aurora PostgreSQL.
- Complex transactions: Can be 2-5x slower than Aurora PostgreSQL.
- Reads: Not much variation since it uses local replicas.
Same-region Latency Comparison
If our workloads exist in a single region, Aurora DSQL will have much higher latency.
Aurora DSQL Pricing Model
Aurora DSQL uses a serverless pricing model that charges for:
Compute (Processing):
- Billing by Aurora Compute Units (ACUs) consumed.
- Automatic scaling based on workload.
- No provisioning required.
Storage:
- Charge per GB stored per month
- Automatic replication included in price
- Automatic backup included
I/O (Input/Output):
- Charge per million I/O requests
- Includes both reads and writes
Cost Comparison: Aurora DSQL vs Aurora PostgreSQL
Example scenario – Medium application:
Aurora PostgreSQL (Provisioned):
- Instance db.r6g.large: ~$200/month
- Storage (100GB): ~$10/month
- I/O (10M requests): ~$2/month
- Approximate total: ~$212/month (single region)
Aurora DSQL (Serverless):
- Compute (equivalent): ~$300-400/month
- Storage (100GB): ~$15-20/month
- I/O (10M requests): ~$3-5/month
- Multi-region included: No additional cost
- Approximate total: ~$320-425/month (multi-region)
Factors affecting cost:
- Traffic pattern: Aurora DSQL proves more efficient for variable loads
- Multi-region requirements: Aurora DSQL includes replication across multiple regions at no additional cost.
- Transaction complexity: Complex distributed transactions can increase cost
- Automatic scaling: Can be more economical for applications with traffic spikes
When is Aurora DSQL more cost-effective?
- Applications with highly variable traffic (serverless adapts automatically) As long as Latency allows
- Multi-region necessity.
Non-Recommended Use Cases
Aurora DSQL isn’t ideal for:
- Applications requiring ultra-low latency (<1ms)
- Primarily read workloads
- Systems with minimal budgets
- Applications that can tolerate eventual consistency
Aurora DSQL represents a new category of database that revolutionizes distributed and multi-region databases.
With this new database, AWS has opened the door to global applications that were previously technically impossible.
This content originally appeared on DEV Community and was authored by Miguel Angel Muñoz Sanchez