<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roopesh V S</title>
    <description>The latest articles on DEV Community by Roopesh V S (@roopeshvs).</description>
    <link>https://dev.to/roopeshvs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4006697%2F9daa57e3-cb45-4418-96c1-50728c855700.png</url>
      <title>DEV Community: Roopesh V S</title>
      <link>https://dev.to/roopeshvs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roopeshvs"/>
    <language>en</language>
    <item>
      <title>How RDS Optimized Writes doubles MySQL write throughput</title>
      <dc:creator>Roopesh V S</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:38:04 +0000</pubDate>
      <link>https://dev.to/roopeshvs/how-rds-optimized-writes-doubles-mysql-write-throughput-5cko</link>
      <guid>https://dev.to/roopeshvs/how-rds-optimized-writes-doubles-mysql-write-throughput-5cko</guid>
      <description>&lt;p&gt;AWS markets &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html" rel="noopener noreferrer"&gt;RDS Optimized Writes&lt;/a&gt; as a free toggle that delivers &lt;em&gt;"up to two times higher write transaction throughput"&lt;/em&gt; on RDS for MySQL and MariaDB. Same instance class, same engine, same workload. Flip a parameter and write throughput can roughly double, at no additional charge.&lt;/p&gt;

&lt;p&gt;That is a tall claim for what looks like a configuration knob. Where does the 2x come from? The short answer: AWS is letting MySQL skip a long-standing safety mechanism called the &lt;em&gt;InnoDB doublewrite buffer&lt;/em&gt;, and the underlying hardware now provides the guarantee the doublewrite buffer was protecting against.&lt;/p&gt;

&lt;p&gt;The rest of this post is the long answer. It walks through what torn pages are, why MySQL has a doublewrite buffer in the first place, why removing it can be worth up to 2x on write-bound workloads, and what specifically changed at the hardware layer that makes this safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: torn pages
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl97bbuwgs2ezk16i0h4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl97bbuwgs2ezk16i0h4x.png" alt="A 16 KiB InnoDB page split across four 4 KiB sectors. Power loss partway through writing leaves two new sectors and two old sectors, producing a torn page." width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A 16 KiB InnoDB page is written as four 4 KiB sector writes. Lose power partway through and you get a page that's half-new, half-old. That is a torn page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;InnoDB stores all of its data in 16 KiB pages by default. Disks and filesystems, however, work in smaller units, typically 4 KiB sectors. When MySQL writes a 16 KiB page to disk, the operating system breaks it into four 4 KiB sector writes underneath. If something goes wrong partway through (a power outage, a kernel panic, a disk failure), the page on disk ends up with a mix of new and old bytes. This is called a &lt;em&gt;torn page&lt;/em&gt; or &lt;em&gt;partial page write&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A torn page is unrecoverable from MySQL's redo log alone. The redo log records &lt;em&gt;changes&lt;/em&gt; to pages (for example, "in page X, at offset Y, set the value to Z"), not the full page contents. Replaying those changes assumes the page itself is intact to begin with. If the page is half-new and half-old, the redo log has nothing clean to apply against, and recovery fails.&lt;/p&gt;

&lt;p&gt;Databases solve this two ways. PostgreSQL writes full page copies into its write-ahead log on first modify after a checkpoint (the &lt;code&gt;full_page_writes&lt;/code&gt; setting). MySQL takes a different route: it keeps a redundant copy of every dirty page in a dedicated on-disk area called the doublewrite buffer. The trade-offs differ; the goal of surviving torn pages is the same. &lt;a href="https://www.percona.com/blog/a-tale-of-two-databases-how-postgresql-and-mysql-handle-torn-pages/" rel="noopener noreferrer"&gt;Percona has a great side-by-side&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  MySQL's solution: the doublewrite buffer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezhte1lw7qf4nrfny5nz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fezhte1lw7qf4nrfny5nz.png" alt="Dirty page in buffer pool, write and fsync to doublewrite area, write and fsync to tablespace location. Two physical writes per dirty page." width="799" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The doublewrite buffer write path: each dirty page is written to a reserved on-disk area first, fsynced, then written to its tablespace location, fsynced again.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html" rel="noopener noreferrer"&gt;official MySQL definition&lt;/a&gt; sums it up:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The doublewrite buffer is a storage area where InnoDB writes pages flushed from the buffer pool before writing the pages to their proper positions in the InnoDB data files. If there is an operating system, storage subsystem, or unexpected mysqld process exit in the middle of a page write, InnoDB can find a good copy of the page from the doublewrite buffer during crash recovery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every dirty page that InnoDB flushes goes through four steps: write the page into the doublewrite area on disk, &lt;code&gt;fsync()&lt;/code&gt; the doublewrite area, write the page to its actual tablespace location, &lt;code&gt;fsync()&lt;/code&gt; the tablespace.&lt;/p&gt;

&lt;p&gt;On crash, InnoDB compares both copies during recovery. If the tablespace copy is torn but the doublewrite copy is intact, InnoDB rewrites the page from the doublewrite copy. If the tablespace copy is intact, the doublewrite copy is discarded. &lt;a href="https://www.percona.com/blog/innodb-double-write/" rel="noopener noreferrer"&gt;Percona's explainer on the recovery mechanic&lt;/a&gt; goes deeper.&lt;/p&gt;

&lt;p&gt;Despite the name, the latency overhead on a well-tuned MySQL is not 2x. The MySQL manual is explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Although data is written twice, the doublewrite buffer does not require twice as much I/O overhead or twice as many I/O operations. Data is written to the doublewrite buffer in a large sequential chunk, with a single fsync() call to the operating system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On a healthy system, the steady-state cost is roughly 5–10%. MySQL 8.0.20 also made the doublewrite path scale better by moving the buffer out of the system tablespace (&lt;code&gt;ibdata1&lt;/code&gt;) and into &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-doublewrite-buffer.html" rel="noopener noreferrer"&gt;dedicated files&lt;/a&gt; like &lt;code&gt;#ib_16384_0.dblwr&lt;/code&gt;, with one set per buffer pool instance. That removed a long-standing concurrency bottleneck.&lt;/p&gt;

&lt;p&gt;The doublewrite buffer is controlled by a single variable, on by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;VARIABLES&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'innodb_doublewrite'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;span class="c1"&gt;-- | Variable_name      | Value |&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;span class="c1"&gt;-- | innodb_doublewrite | ON    |   ← stock MySQL&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since MySQL 8.0.30, the parameter accepts a richer set of values: &lt;code&gt;ON&lt;/code&gt; (the default, equivalent to &lt;code&gt;DETECT_AND_RECOVER&lt;/code&gt;), &lt;code&gt;DETECT_ONLY&lt;/code&gt; (writes metadata, doesn't recover full pages), and &lt;code&gt;OFF&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "2x", then? The write amplification angle
&lt;/h2&gt;

&lt;p&gt;There is a contradiction lurking here: the MySQL manual says the doublewrite buffer adds modest I/O overhead, yet AWS markets removing it as a 2x win. Both are true; they are talking about different things.&lt;/p&gt;

&lt;p&gt;The "modest overhead" framing measures &lt;em&gt;latency on a system with I/O headroom&lt;/em&gt;. The buffer's writes are sequential and batched, so they amortise into a small per-transaction cost.&lt;/p&gt;

&lt;p&gt;The "up to 2x" framing measures &lt;em&gt;throughput on a system that's running out of I/O headroom&lt;/em&gt;. The doublewrite buffer doubles the &lt;em&gt;volume&lt;/em&gt; of page data hitting durable storage; every dirty page lands on disk twice. On write-bound workloads, the binding constraint isn't latency, it's bytes-per-second to storage. Halve the bytes and you can serve roughly twice the writes before saturating the volume.&lt;/p&gt;

&lt;p&gt;That is exactly the regime AWS's benchmark targets. The &lt;a href="https://aws.amazon.com/blogs/database/improve-application-performance-on-amazon-rds-for-mysql-and-mariadb-instances-and-mysql-multi-az-db-clusters-with-optimized-writes/" rel="noopener noreferrer"&gt;AWS Database Blog deep dive&lt;/a&gt; describes a sysbench write-only workload on a &lt;code&gt;db.r6g.8xlarge&lt;/code&gt; with 50,000 provisioned IOPS in a Multi-AZ setup, deliberately write-bound. Mixed or read-heavy workloads see far less benefit because they were never bottlenecked on the second write to begin with.&lt;/p&gt;

&lt;p&gt;Percona has separately measured how badly a misconfigured doublewrite path can hurt. They show &lt;a href="https://www.percona.com/blog/improve-innodb-performance-write-bound-loads/" rel="noopener noreferrer"&gt;up to 55% write IOPS reduction&lt;/a&gt; on write-bound loads when the legacy single-buffer design becomes a contention point. The fix in stock MySQL is tuning &lt;code&gt;innodb_doublewrite_pages&lt;/code&gt; upward. AWS's fix is to remove the buffer entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the doublewrite buffer is redundant
&lt;/h2&gt;

&lt;p&gt;If the doublewrite buffer exists to protect against torn pages, then any storage layer that already guarantees atomic 16 KiB writes makes the buffer redundant. This is not a new idea, and MySQL has long known how to skip the buffer when it is safe to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fusion-io with NVMFS.&lt;/strong&gt; The MySQL manual says it directly: &lt;em&gt;"If the doublewrite buffer is located on a Fusion-io device that supports atomic writes, the doublewrite buffer is automatically disabled and data file writes are performed using Fusion-io atomic writes instead."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZFS with &lt;code&gt;recordsize=16K&lt;/code&gt;.&lt;/strong&gt; ZFS's copy-on-write semantics mean a 16 KiB write either completes to a new block or never publishes. There is no in-place overwrite to be interrupted. &lt;a href="https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html" rel="noopener noreferrer"&gt;OpenZFS tuning docs&lt;/a&gt; recommend &lt;code&gt;recordsize=16K&lt;/code&gt; precisely for InnoDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MariaDB's &lt;code&gt;innodb_use_atomic_writes&lt;/code&gt;.&lt;/strong&gt; Since 10.2, MariaDB auto-detects compatible hardware at startup and disables the doublewrite buffer when atomic writes are available (&lt;a href="https://mariadb.com/kb/en/atomic-write-support/" rel="noopener noreferrer"&gt;MariaDB KB&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is consistent: when the storage stack can atomically commit at least an InnoDB page's worth of data, the application-level safety net becomes redundant. The atomicity moves from software to hardware, and the doublewrite traffic disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter AWS Nitro
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3fdf4tn9tq80pt3i8cu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3fdf4tn9tq80pt3i8cu.png" alt="Dirty page in buffer pool, single atomic 16 KiB write to tablespace location backed by AWS Nitro. Every dirty page is written once." width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;With Optimized Writes on, the doublewrite step disappears entirely. The page travels straight from the buffer pool to its final location in one atomic 16 KiB write.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/ec2/nitro/" rel="noopener noreferrer"&gt;AWS Nitro&lt;/a&gt; is the hardware platform underneath modern EC2 (and therefore RDS) instances. It combines dedicated hardware, lightweight firmware, and a stripped-down hypervisor that handles networking, storage, and security functions outside the main host CPU.&lt;/p&gt;

&lt;p&gt;For Optimized Writes, the relevant property is that the Nitro storage path guarantees atomic 16 KiB writes. From the &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html" rel="noopener noreferrer"&gt;AWS RDS documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These databases run on DB instance classes that use the AWS Nitro System. Because of the hardware configuration in these systems, the database can write 16-KiB pages directly to data files reliably and durably in one step. The AWS Nitro System makes RDS Optimized Writes possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When Optimized Writes is on, RDS sets the underlying MySQL variable &lt;code&gt;innodb_doublewrite&lt;/code&gt; to &lt;code&gt;FALSE&lt;/code&gt; (&lt;code&gt;0&lt;/code&gt;). That is the same knob you would flip locally, just with hardware that backs the safety guarantee. ACID is preserved; atomicity simply moves from the doublewrite buffer to the Nitro layer.&lt;/p&gt;

&lt;p&gt;The result: every dirty page goes to durable storage once instead of twice. On the same write-bound workload that saturates the doublewrite path, that frees up roughly half the write bandwidth, which is where AWS's up-to-2x throughput claim comes from.&lt;/p&gt;

&lt;p&gt;You can verify Optimized Writes is active on your instance the same way you would verify the doublewrite buffer locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;VARIABLES&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'innodb_doublewrite'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;span class="c1"&gt;-- | Variable_name      | Value |&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;span class="c1"&gt;-- | innodb_doublewrite | OFF   |   ← Optimized Writes active&lt;/span&gt;
&lt;span class="c1"&gt;-- +--------------------+-------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Turning it on in practice
&lt;/h2&gt;

&lt;p&gt;Optimized Writes is controlled by a single RDS parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;rds.optimized_writes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;AUTO   # Turn on when version × instance class supports it (default)&lt;/span&gt;
&lt;span class="py"&gt;rds.optimized_writes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;OFF    # Force off; falls back to the doublewrite buffer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For new instances, the feature is on by default on any combination that supports it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt;: version 8.0.30 and later (8.0.x and 8.4). Supported instance classes cover the modern M and R families: &lt;code&gt;db.m5/m6i/m6g/m6gd/m7g/m7i/m8g/m8gd&lt;/code&gt;, &lt;code&gt;db.r5/r5b/r5d/r6g/r6gd/r6i/r7g/r7i/r8g/r8gd&lt;/code&gt;, plus &lt;code&gt;db.x2idn&lt;/code&gt;/&lt;code&gt;db.x2iedn&lt;/code&gt;. The &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes.html" rel="noopener noreferrer"&gt;docs page&lt;/a&gt; has the canonical list; AWS adds new instance families over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MariaDB&lt;/strong&gt;: 10.6.10+, 10.11.4+, 11.4.3+, or 11.8+. Same shape of instance class coverage, currently without the 8th-generation Graviton families (&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-optimized-writes-mariadb.html" rel="noopener noreferrer"&gt;MariaDB-specific docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No additional charge.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Existing instances created before the feature launched (November 27, 2022 for MySQL, March 7, 2023 for MariaDB) have an incompatible underlying file system layout and cannot be flipped in place. The migration path is an &lt;a href="https://aws.amazon.com/about-aws/whats-new/2023/10/enable-amazon-rds-optimized-writes-blue-green-deployments/" rel="noopener noreferrer"&gt;RDS Blue/Green Deployment&lt;/a&gt; with the &lt;em&gt;"Enable Optimized Writes on green database"&lt;/em&gt; and &lt;em&gt;"Upgrade storage file system configuration"&lt;/em&gt; options ticked. Cut over once the green environment is in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catches
&lt;/h2&gt;

&lt;p&gt;A few things worth keeping in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Up to 2x" is a ceiling, not a floor.&lt;/strong&gt; AWS says it directly: &lt;em&gt;"the amount of benefit that can be achieved depends on the type of workload."&lt;/em&gt; Read-heavy or mixed workloads will see a fraction of the gain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS only, not Aurora.&lt;/strong&gt; Aurora has its own purpose-built storage layer that doesn't use the InnoDB doublewrite buffer at all, so there's nothing to optimise here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No PostgreSQL equivalent.&lt;/strong&gt; Postgres handles torn pages via full-page writes in WAL, which is a different architecture entirely. RDS for PostgreSQL has a separate feature called &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PostgreSQL.optimizedreads.html" rel="noopener noreferrer"&gt;Optimized Reads&lt;/a&gt;, which is unrelated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing instances need migration.&lt;/strong&gt; The on-disk file system layout differs from the pre-feature state, so a Blue/Green deployment is the only path forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot restore is constrained.&lt;/strong&gt; Optimized Writes can only restore &lt;em&gt;into&lt;/em&gt; an instance if the source snapshot was created from one that already supported it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The doublewrite buffer was a clever software workaround for a storage stack that could not promise atomic 16 KiB writes. AWS Nitro can promise that, end-to-end. So MySQL no longer needs the workaround, and write throughput approximately doubles on write-bound workloads, for free. The buffer still earns its keep on stock MySQL; on RDS, the hardware now does the job. 🍻&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mysql</category>
      <category>database</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
