[ad_1]

At re:Invent we introduced Aurora DSQL, and since then I’ve had many conversations with builders about what this implies for database engineering. What’s significantly fascinating isn’t simply the know-how itself, however the journey that acquired us right here. I’ve been eager to dive deeper into this story, to share not simply the what, however the how and why behind DSQL’s growth. Then, a couple of weeks in the past, at our inner developer convention — DevCon — I watched a chat from two of our senior principal engineers (PEs) on constructing DSQL (a mission that began 100% in JVM and completed 100% Rust). After the presentation, I requested Niko Matsakis and Marc Bowes in the event that they’d be keen to work with me to show their insights right into a deeper exploration of DSQL’s growth. They not solely agreed, however provided to assist clarify a number of the extra technically complicated components of the story.
In the weblog that follows, Niko and Marc present deep technical insights on Rust and the way we’ve used it to construct DSQL. It’s an fascinating story on the pursuit of engineering effectivity and why it’s so necessary to query previous choices – even when they’ve labored very effectively previously.
Before we get into it, a fast however necessary word. This was (and continues to be) an formidable mission that requires an amazing quantity of experience in every little thing from storage to regulate airplane engineering. Throughout this write-up we have included the learnings and knowledge of most of the Principal and Sr. Principal Engineers that introduced DSQL to life. I hope you get pleasure from studying this as a lot as I’ve.
Special due to: Marc Brooker, Marc Bowes, Niko Matsakis, James Morle, Mike Hershey, Zak van der Merwe, Gourav Roy, Matthys Strydom.
A quick timeline of purpose-built databases at AWS
Since the early days of AWS, the wants of our clients have grown extra various — and in lots of circumstances, extra pressing. What began with a push to make conventional relational databases simpler to handle with the launch of Amazon RDS in 2009 rapidly expanded right into a portfolio of purpose-built choices: DynamoDB for internet-scale NoSQL workloads, Redshift for quick analytical queries over large datasets, Aurora for these seeking to escape the fee and complexity of legacy business engines with out sacrificing efficiency. These weren’t simply incremental steps—they have been solutions to actual constraints our clients have been hitting in manufacturing. And time after time, what unlocked the correct resolution wasn’t a flash of genius, however listening intently and constructing iteratively, usually with the client within the loop.
Of course, velocity and scale aren’t the one forces at play. In-memory caching with ElastiCache emerged from builders needing to squeeze extra from their relational databases. Neptune got here later, as graph-based workloads and relationship-heavy purposes pushed the boundaries of conventional database approaches. What’s exceptional trying again isn’t simply how the portfolio grew, however the way it grew in tandem with new computing patterns—serverless, edge, real-time analytics. Behind every launch was a crew keen to experiment, problem prior assumptions, and work in shut collaboration with product groups throughout Amazon. That’s the half that’s tougher to see from the skin: innovation nearly by no means occurs in a single day. It nearly all the time comes from taking incremental steps ahead. Building on successes and studying from (however not fearing) failures.
While every database service we’ve launched has solved vital issues for our clients, we saved encountering a persistent problem: how do you construct a relational database that requires no infrastructure administration and which scales routinely with load? One that mixes the familiarity and energy of SQL with real serverless scalability, seamless multi-region deployment, and 0 operational overhead? Our earlier makes an attempt had every moved us nearer to this objective. Aurora introduced cloud-optimized storage and simplified operations, Aurora Serverless automated vertical scaling, however we knew we would have liked to go additional. This wasn’t nearly including options or enhancing efficiency – it was about essentially rethinking what a cloud database may very well be.
Which brings us to Aurora DSQL.
Aurora DSQL
The objective with Aurora DSQL’s design is to interrupt up the database into bite-sized chunks with clear interfaces and express contracts. Each part follows the Unix mantra—do one factor, and do it effectively—however working collectively they’re able to provide all of the options customers count on from a database (transactions, sturdiness, queries, isolation, consistency, restoration, concurrency, efficiency, logging, and so forth).
At a high-level, that is DSQL’s structure.

We had already labored out how you can deal with reads in 2021—what we didn’t have was a great way to scale writes horizontally. The typical resolution for scaling out writes to a database is two-phase commit (2PC). Each journal can be liable for a subset of the rows, identical to storage. This all works nice as long as transactions are solely modifying close by rows. But it will get actually difficult when your transaction has to replace rows throughout a number of journals. You find yourself in a fancy dance of checks and locks, adopted by an atomic commit. Sure, the blissful path works nice in concept, however actuality is messier. You must account for timeouts, keep liveness, deal with rollbacks, and work out what occurs when your coordinator fails — the operational complexity compounds rapidly. For DSQL, we felt we would have liked a brand new method – a solution to keep availability and latency even beneath duress.
Scaling the Journal layer
Instead of pre-assigning rows to particular journals, we made the architectural resolution to write down your entire commit right into a single journal, irrespective of what number of rows it modifies. This solved each the atomic and sturdy necessities of ACID. The excellent news? This made scaling the write path easy. The problem? It made the learn path considerably extra complicated. If you need to know the newest worth for a specific row, you now must examine all of the journals, as a result of any one in every of them might need a modification. Storage due to this fact wanted to keep up connections to each journal as a result of updates might come from anyplace. As we added extra journals to extend transactions per second, we might inevitably hit community bandwidth limitations.
The resolution was the Crossbar, which separates the scaling of the learn path and write path. It provides a subscription API to storage, permitting storage nodes to subscribe to keys in a particular vary. When transactions come by, the Crossbar routes the updates to the subscribed nodes. Conceptually, it’s fairly easy, however difficult to implement effectively. Each journal is ordered by transaction time, and the Crossbar has to observe every journal to create the overall order.

Adding to the complexity, every layer has to supply a excessive diploma of fan out (we need to be environment friendly with our {hardware}), however in the actual world, subscribers can fall behind for any variety of causes, so you find yourself with a bunch of buffering necessities. These issues made us anxious about rubbish assortment, particularly GC pauses.
The actuality of distributed programs hit us onerous right here – when that you must learn from each journal to supply whole ordering, the likelihood of any host encountering tail latency occasions approaches 1 surprisingly rapidly – one thing Marc Brooker has spent a while writing about.
To validate our considerations, we ran simulation testing of the system – particularly modeling how our crossbar structure would carry out when scaling up the variety of hosts, whereas accounting for infrequent 1-second stalls. The outcomes have been sobering: with 40 hosts, as a substitute of attaining the anticipated million TPS within the crossbar simulation, we have been solely hitting about 6,000 TPS. Even worse, our tail latency had exploded from an appropriate 1 second to a catastrophic 10 seconds. This wasn’t simply an edge case – it was elementary to our structure. Every transaction needed to learn from a number of hosts, which meant that as we scaled up, the chance of encountering not less than one GC pause throughout a transaction approached 100%. In different phrases, at scale, almost each transaction can be affected by the worst-case latency of any single host within the system.
Short time period ache, long run achieve
We discovered ourselves at a crossroads. The considerations about rubbish assortment, throughput, and stalls weren’t theoretical – they have been very actual issues we would have liked to unravel. We had choices: we might dive deep into JVM optimization and attempt to decrease rubbish creation (a path a lot of our engineers knew effectively), we might contemplate C or C++ (and lose out on reminiscence security), or we might discover Rust. We selected Rust. The language provided us predictable efficiency with out rubbish assortment overhead, reminiscence security with out sacrificing management, and zero-cost abstractions that permit us write high-level code that compiled all the way down to environment friendly machine directions.
The resolution to change programming languages isn’t one thing to take evenly. It’s usually a one-way door — when you’ve acquired a big codebase, it’s extraordinarily troublesome to alter course. These choices could make or break a mission. Not solely does it impression your quick crew, however it influences how groups collaborate, share finest practices, and transfer between tasks.
Rather than deal with the complicated Crossbar implementation, we selected to start out with the Adjudicator – a comparatively easy part that sits in entrance of the journal and ensures just one transaction wins when there are conflicts. This was our crew’s first foray into Rust, and we picked the Adjudicator for a couple of causes: it was much less complicated than the Crossbar, we already had a Rust consumer for the journal, and we had an present JVM (Kotlin) implementation to match in opposition to. This is the form of pragmatic alternative that has served us effectively for over twenty years – begin small, be taught quick, and regulate course primarily based on knowledge.
We assigned two engineers to the mission. They had by no means written C, C++, or Rust earlier than. And sure, there have been loads of battles with the compiler. The Rust neighborhood has a saying, “with Rust you have the hangover first.” We definitely felt that ache. We acquired used to the compiler telling us “no” so much.

But after a couple of weeks, it compiled and the outcomes stunned us. The code was 10x sooner than our rigorously tuned Kotlin implementation – regardless of no try to make it sooner. To put this in perspective, we had spent years incrementally enhancing the Kotlin model from 2,000 to three,000 transactions per second (TPS). The Rust model, written by Java builders who have been new to the language, clocked 30,000 TPS.
This was a type of moments that essentially shifts your pondering. Suddenly, the couple of weeks spent studying Rust now not appeared like a giant deal, compared with how lengthy it’d have taken us to get the identical outcomes on the JVM. We stopped asking, “Should we be using Rust?” and began asking “Where else could Rust help us solve our problems?”
Our conclusion was to rewrite our knowledge airplane totally in Rust. We determined to maintain the management airplane in Kotlin. This appeared like the most effective of each worlds: high-level logic in a high-level, rubbish collected language, do the latency delicate components in Rust. This logic didn’t transform fairly proper, however we’ll get to that later within the story.
It’s simpler to repair one onerous drawback then by no means write a reminiscence security bug
Making the choice to make use of Rust for the info airplane was just the start. We had determined, after fairly a little bit of inner dialogue, to construct on PostgreSQL (which we’ll simply name Postgres from right here on). The modularity and extensibility of Postgres allowed us to make use of it for question processing (i.e., the parser and planner), whereas changing replication, concurrency management, sturdiness, storage, the best way transaction classes are managed.
But now we had to determine how you can go about making modifications to a mission that began in 1986, with over 1,000,000 strains of C code, 1000’s of contributors, and steady lively growth. The simple path would have been to onerous fork it, however that may have meant lacking out on new options and efficiency enhancements. We’d seen this film earlier than – forks that begin with the most effective intentions however slowly drift into upkeep nightmares.
Extension factors appeared like the plain reply. Postgres was designed from the start to be an extensible database system. These extension factors are a part of Postgres’ public API, permitting you to switch conduct with out altering core code. Our extension code might run in the identical course of as Postgres however reside in separate recordsdata and packages, making it a lot simpler to keep up as Postgres advanced. Rather than creating a tough fork that may drift farther from upstream with every change, we might construct on high of Postgres whereas nonetheless benefiting from its ongoing growth and enhancements.
The query was, can we write these extensions in C or Rust? Initially, the crew felt C was a better option. We already needed to learn and perceive C to work with Postgres, and it could provide a decrease impedance mismatch. As the work progressed although, we realized a vital flaw on this pondering. The Postgres C code is dependable: it’s been totally battled examined through the years. But our extensions have been freshly written, and each new line of C code was an opportunity so as to add some form of reminiscence security bug, like a use-after-free or buffer overrun. The “a-ha!” second got here throughout a code assessment once we discovered a number of reminiscence issues of safety in a seemingly easy knowledge construction implementation. With Rust, we might have simply grabbed a confirmed, memory-safe implementation from Crates.io.
Interestingly, the Android crew printed analysis final September that confirmed our pondering. Their knowledge confirmed that the overwhelming majority of latest bugs come from new code. This strengthened our perception that to forestall reminiscence issues of safety, we would have liked to cease introducing memory-unsafe code altogether.

We determined to pivot and write the extensions in Rust. Given that the Rust code is interacting intently with Postgres APIs, it could appear to be utilizing Rust wouldn’t provide a lot of a reminiscence security benefit, however that turned out to not be true. The crew was capable of create abstractions that implement secure patterns of reminiscence entry. For instance, in C code it’s frequent to have two fields that must be used collectively safely, like a char* and a len discipline. You find yourself counting on conventions or feedback to elucidate the connection between these fields and warn programmers to not entry the string past len. In Rust, that is wrapped up behind a single String sort that encapsulates the security. We discovered many examples within the Postgres codebase the place header recordsdata needed to clarify how you can use a struct safely. With our Rust abstractions, we might encode these guidelines into the sort system, making it unattainable to interrupt the invariants. Writing these abstractions needed to be finished very rigorously, however the remainder of the code might use them to keep away from errors.
It’s a reminder that choices about scalability, safety, and resilience ought to be prioritized – even after they’re troublesome. The funding in studying a brand new language is minuscule in comparison with the long-term price of addressing reminiscence security vulnerabilities.
About the management airplane
Writing the management airplane in Kotlin appeared like the plain alternative once we began. After all, providers like Amazon’s Aurora and RDS had confirmed that JVM languages have been a stable alternative for management planes. The advantages we noticed with Rust within the knowledge airplane – throughput, latency, reminiscence security – weren’t as vital right here. We additionally wanted inner libraries that weren’t but obtainable in Rust, and we had engineers that have been already productive in Kotlin. It was a sensible resolution primarily based on what we knew on the time. It additionally turned out to be the fallacious one.
At first, issues went effectively. We had each the info and management planes working as anticipated in isolation. However, as soon as we began integrating them collectively, we began hitting issues. DSQL’s management airplane does much more than CRUD operations, it’s the mind behind our hands-free operations and scaling, detecting when clusters get sizzling and orchestrating topology modifications. To make all this work, the management airplane has to share some quantity of logic with the info airplane. Best follow can be to create a shared library to keep away from “repeating ourselves”. But we couldn’t do this, as a result of we have been utilizing completely different languages, which meant that generally the Kotlin and Rust variations of the code have been barely completely different. We additionally couldn’t share testing platforms, which meant the crew needed to depend on documentation and whiteboard classes to remain aligned. And each misunderstanding, even a small one, led to a pricey debug-fix-deploy cycles. We had a tough resolution to make. Do we spend the time rewriting our simulation instruments to work with each Rust and Kotlin? Or can we rewrite the management airplane in Rust?
The resolution wasn’t as troublesome this time round. Rather a lot had modified in a 12 months. Rust’s 2021 version had addressed most of the ache factors and paper cuts we’d encountered early on. Our inner library help had expanded significantly – in some circumstances, such because the AWS Authentication Runtime consumer, the Rust implementations have been outperforming their Java counterparts. We’d additionally moved many integration considerations to API Gateway and Lambda, simplifying our structure.
But maybe most shocking was the crew’s response. Rather than resistance to Rust, we noticed enthusiasm. Our Kotlin builders weren’t asking “do we have to?” They have been asking “when can we start?” They’d watched their colleagues working with Rust and wished to be a part of it.
Numerous this enthusiasm got here from how we approached studying and growth. Marc Brooker had written what we now name “The DSQL Book” – an inner information that walks builders by every little thing from philosophy to design choices, together with the onerous decisions we needed to defer. The crew devoted time every week to studying classes on distributed computing, paper opinions, and deep architectural discussions. We introduced in Rust consultants like Niko who, true to our working backwards method, helped us assume by thorny issues earlier than we wrote a single line of code. These investments didn’t simply construct technical data – they gave the crew confidence that they might deal with complicated issues in a brand new language.
When we took every little thing under consideration, the selection was clear. It was Rust. We wanted the management and knowledge planes working collectively in simulation, and we couldn’t afford to keep up vital enterprise logic in two completely different languages. We had noticed vital throughput efficiency within the crossbar, and as soon as we had your entire system written in Rust tail latencies have been remarkably constant. Our p99 latencies tracked very near our p50 medians, that means even our slowest operations maintained predictable, production-grade efficiency.
It’s a lot extra than simply writing code
Rust turned out to be an important match for DSQL. It gave us the management we would have liked to keep away from tail latency within the core components of the system, the pliability to combine with a C codebase like Postgres, and the high-level productiveness we would have liked to face up our management airplane. We even wound up utilizing Rust (by way of WebAssembly) to energy our inner ops internet web page.
We assumed Rust can be decrease productiveness than a language like Java, however that turned out to be an phantasm. There was undoubtedly a studying curve, however as soon as the crew was ramped up, they moved simply as quick as they ever had.
This doesn’t imply that Rust is correct for each mission. Modern Java implementations like JDK21 provide nice efficiency that’s greater than sufficient for a lot of providers. The key’s to make these choices the identical method you make different architectural decisions: primarily based in your particular necessities, your crew’s capabilities, and your operational surroundings. If you’re constructing a service the place tail latency is vital, Rust is likely to be the correct alternative. But for those who’re the one crew utilizing Rust in a corporation standardized on Java, that you must rigorously weigh that isolation price. What issues is empowering your groups to make these decisions thoughtfully, and supporting them as they be taught, take dangers, and sometimes have to revisit previous choices. That’s the way you construct for the long run.
Now, go construct!
Recommended studying
If you’d wish to be taught extra about DSQL and the pondering behind it, Marc Brooker has written an in-depth set of posts referred to as DSQL Vignettes:
