a curated list of database news from authoritative sources

January 22, 2026

CPU-bound Insert Benchmark vs Postgres on 24-core and 32-core servers

This has results for Postgres versions 12 through 18 with a CPU-bound Insert Benchmark on 24-core and 32-core servers. A report for MySQL on the same setup is here.

tl;dr

  • good news
    • there are small improvments
    • with the exception of get_actual_variable range I don't see new CPU overheads in Postgres 18
  • bad news
Builds, configuration and hardware

I compiled Postgre from source for versions 12.22, 13.22, 13.23, 14.19, 14.20, 15.14, 15.15, 16.10, 16.11, 17.6, 17.7, 18.0 and 18.1.

The servers are:
  • 24-core
    • the server has 24-cores, 2-sockets and 64G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. Intel HT is disabled.
    • the Postgres conf files are here for versions 1213141516 and 17. These are named conf.diff.cx10a_c24r64 (or x10a).
    • For 18.0 I tried 3 configuration files:
  • 32-core
    • the server has 32-cores and 128G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. AMD SMT is disabled.
    • the Postgres config files are here for versions 1213141516 and 17. These are named conf.diff.cx10a_c32r128 (or x10a).
    • I used several config files for Postgres 18
    The Benchmark

    The benchmark is explained here. It was run with 8 clients on the 24-core server and 12 clients on the 32-core server. The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 1800 seconds each.

    The benchmark steps are:

    • l.i0
      • insert X rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client. X is 250M on the 24-core server and 300M on the 32-core server.
    • l.x
      • create 3 secondary indexes per table. There is one connection per client.
    • l.i1
      • use 2 connections/client. One inserts 4M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.
    • l.i2
      • like l.i1 but each transaction modifies 5 rows (small transactions) and 1M rows are inserted and deleted per table.
      • Wait for S seconds after the step finishes to reduce MVCC GC debt and perf variance during the read-write benchmark steps that follow. The value of S is a function of the table size.
    • qr100
      • use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.
    • qp100
      • like qr100 except uses point queries on the PK index
    • qr500
      • like qr100 but the insert and delete rates are increased from 100/s to 500/s
    • qp500
      • like qp100 but the insert and delete rates are increased from 100/s to 500/s
    • qr1000
      • like qr100 but the insert and delete rates are increased from 100/s to 1000/s
    • qp1000
      • like qp100 but the insert and delete rates are increased from 100/s to 1000/s
    Results: overview

    For each server there are two performance reports
    • latest point releases
      • has results for the latest point release I tested from each major release
      • the base version is Postgres 12.22 when computing relative QPS
    • all releases
      • has results for all of the versions I tested
      • the base version is Postgres 12.22 when computing relative QPS
    Results: summary

    The performance reports are here for:
    The summary sections from the performance reports have 3 tables. The first shows absolute throughput by DBMS tested X benchmark step. The second has throughput relative to the version from the first row of the table. The third shows the background insert rate for benchmark steps with background inserts. The second table makes it easy to see how performance changes over time. The third table makes it easy to see which DBMS+configs failed to meet the SLA.

    I use relative QPS to explain how performance changes. It is: (QPS for $me / QPS for $base) where $me is the result for some version $base is the result from the base version. The base version is Postgres 12.22.

    When relative QPS is > 1.0 then performance improved over time. When it is < 1.0 then there are regressions. The Q in relative QPS measures: 
    • insert/s for l.i0, l.i1, l.i2
    • indexed rows/s for l.x
    • range queries/s for qr100, qr500, qr1000
    • point queries/s for qp100, qp500, qp1000
    Below I use colors to highlight the relative QPS values with yellow for regressions and blue for improvements.

    I often use context switch rates as a proxy for mutex contention.

    Results: latest point releases

    The summaries are here for the 24-core and 32-core servers

    The tables have relative throughput: (QPQ for my version / QPS for MySQL 5.6.51). Values less than 0.95 have a yellow background. Values greater than 1.05 have a blue background.

    From the 24-core server:

    • there are small improvements on the l.i1 (write-heavy) step. I don't see regressions.
    • thanks to vacuum, there is much variance for insert rates on the l.i1 and l.i2 steps. For the l.i1 step there are also several large write-stalls.
    • the overhead from get_actual_variable_range increased by 10% from Postgres 14 to 18. Eventually that hurts performance.
    • with the exception of get_actual_variable range I don't see new CPU overheads in Postgres 18

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    pg1222_o2nofp.cx10a_c24r641.001.001.001.001.001.001.001.001.001.00
    pg1322_o2nofp.cx10a_c24r641.030.971.021.021.011.021.001.011.001.02
    pg1419_o2nofp.cx10a_c24r640.980.951.101.071.011.011.011.011.011.01
    pg1515_o2nofp.cx10a_c24r641.021.021.081.051.011.021.011.021.011.02
    pg1611_o2nofp.cx10a_c24r641.020.981.040.981.021.021.021.021.021.02
    pg177_o2nofp.cx10a_c24r641.020.981.070.991.021.021.021.021.021.02
    pg181_o2nofp.cx10b_c24r641.021.001.060.971.001.011.001.001.001.01

    From the 32-core server:

    • there are small improvements for the l.x (index create) step.
    • there might be small regressions for the l.i2 (random writes) step
    • thanks to vacuum, there is much variance for insert rates on the l.i1 and l.i2 steps. For the l.i1 step there are also several large write-stalls.
    • the overhead from get_actual_variable_range increased by 10% from Postgres 14 to 18. That might explain the small decrease in throughput for l.i2.
    • with the exception of get_actual_variable range I don't see new CPU overheads in Postgres 18
    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    pg1222_o2nofp.cx10a_c32r1281.001.001.001.001.001.001.001.001.001.00
    pg1323_o2nofp.cx10a_c32r1280.890.961.000.931.001.001.000.991.001.00
    pg1420_o2nofp.cx10a_c32r1280.960.981.020.951.020.991.010.991.010.99
    pg1515_o2nofp.cx10a_c32r1281.011.000.970.971.000.991.000.991.000.99
    pg1611_o2nofp.cx10a_c32r1280.991.020.980.941.011.001.011.001.011.00
    pg177_o2nofp.cx10a_c32r1280.981.061.000.981.021.001.020.991.020.99
    pg181_o2nofp.cx10b_c32r1280.991.061.010.951.020.991.020.991.020.99


    Results: all releases

    The summaries are here for the 24-core and 32-core servers.

    From the 24-core server I small improvements on the l.i1 (write-heavy) step. I don't see regressions.
    • there are small improvements on the l.i1 (write-heavy) step. I don't see regressions.
    • io_method =worker and =io_uring doesn't help here, I don't expect them to help
    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    pg1222_o2nofp.cx10a_c24r641.001.001.001.001.001.001.001.001.001.00
    pg1322_o2nofp.cx10a_c24r641.030.971.021.021.011.021.001.011.001.02
    pg1419_o2nofp.cx10a_c24r640.980.951.101.071.011.011.011.011.011.01
    pg1514_o2nofp.cx10a_c24r641.020.981.020.881.011.011.011.011.011.01
    pg1515_o2nofp.cx10a_c24r641.021.021.081.051.011.021.011.021.011.02
    pg1610_o2nofp.cx10a_c24r641.021.001.050.931.021.021.021.021.011.02
    pg1611_o2nofp.cx10a_c24r641.020.981.040.981.021.021.021.021.021.02
    pg176_o2nofp.cx10a_c24r641.021.021.060.971.031.021.031.021.021.02
    pg177_o2nofp.cx10a_c24r641.020.981.070.991.021.021.021.021.021.02
    pg180_o2nofp.cx10b_c24r641.011.021.050.921.021.021.011.011.011.02
    pg180_o2nofp.cx10c_c24r641.001.021.060.891.011.011.011.011.011.01
    pg180_o2nofp.cx10d_c24r641.001.001.050.941.021.011.011.011.011.01
    pg181_o2nofp.cx10b_c24r641.021.001.060.971.001.011.001.001.001.01
    pg181_o2nofp.cx10d_c24r641.021.001.060.921.001.011.001.000.991.01


    From the 32-core server
    • there are small improvements for the l.x (index create) step.
    • there might be small regressions for the l.i2 (random writes) step
    • io_method =worker and =io_uring doesn't help here, I don't expect them to help
    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    pg1222_o2nofp.cx10a_c32r1281.001.001.001.001.001.001.001.001.001.00
    pg1322_o2nofp.cx10a_c32r1281.000.960.990.901.011.001.011.001.011.00
    pg1323_o2nofp.cx10a_c32r1280.890.961.000.931.001.001.000.991.001.00
    pg1419_o2nofp.cx10a_c32r1280.970.960.990.911.020.991.010.991.010.99
    pg1420_o2nofp.cx10a_c32r1280.960.981.020.951.020.991.010.991.010.99
    pg1514_o2nofp.cx10a_c32r1280.981.020.950.921.011.001.011.001.021.00
    pg1515_o2nofp.cx10a_c32r1281.011.000.970.971.000.991.000.991.000.99
    pg1610_o2nofp.cx10a_c32r1280.981.001.000.891.011.001.011.001.011.00
    pg1611_o2nofp.cx10a_c32r1280.991.020.980.941.011.001.011.001.011.00
    pg176_o2nofp.cx10a_c32r1281.001.061.020.911.021.001.011.001.021.00
    pg177_o2nofp.cx10a_c32r1280.981.061.000.981.021.001.020.991.020.99
    pg180_o2nofp.cx10b_c32r1281.001.061.040.921.000.991.000.991.000.99
    pg180_o2nofp.cx10c_c32r1280.991.061.010.961.000.991.000.991.000.99
    pg180_o2nofp.cx10d_c32r1280.991.061.000.941.000.991.000.991.000.99
    pg181_o2nofp.cx10b_c32r1280.991.061.010.951.020.991.020.991.020.99
    pg181_o2nofp.cx10d_c32r1280.981.061.010.931.000.991.000.991.000.99





    ... (truncated)

    Separating FUD and Reality: Has MySQL Really Been Abandoned?

    Over the past weeks, we have seen renewed discussion/concern in the MySQL community around claims that “Oracle has stopped developing MySQL” or that “MySQL is being abandoned.” These concerns were amplified by graphs showing an apparent halt in GitHub commits after October 2025, as well as by blog posts and forum discussions that interpreted these […]

    From Feature Request to Release: How Community Feedback Shaped PBM’s Alibaba Cloud Integration

    At Percona, we’ve always believed that the best software isn’t built in a vacuum—it’s built in the open, fueled by the real-world challenges of the people who use it every day. Today, I’m excited to walk you through a journey that perfectly illustrates this: the road from a JIRA ticket to native Alibaba Cloud Object […]

    January 21, 2026

    IO-bound Insert Benchmark vs MySQL on 24-core and 32-core servers

     This has results for MySQL versions 5.6 through 9.5 with an IO-bound Insert Benchmark on 24-core and 32-core servers. The workload uses IO-bound workload. Results for a CPU-bound workload are here.

    MySQL often has large performance regressions at low concurrency from new CPU overhead while showing large improvements at high concurrency from less mutex contention. The tests here use medium or high concurrency.

    tl;dr

    • good news
      • there are few regressions after 8.0
      • modern MySQL is mostly faster than MySQL 5.6.51
    • bad news
      • there are big regressions from 5.6 to 8.0
      • results for modern MySQL would be much better if the CPU regressions were fixed
    • other news
      • Postgres 18.1 is mostly faster than MySQL 8.4.7 but Postgres write rates suffer from too much variance thanks to vacuum
    Builds, configuration and hardware

    I compiled MySQL from source for versions 5.6.51, 5.7.44, 8.0.43, 8.0.44, 8.4.6, 8.4.7, 9.4.0 and 9.5.0. I also compiled Postgres 18.1 from source.

    The servers are:
    • 24-core
      • the server has 24-cores, 2-sockets and 64G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. Intel HT is disabled.
      • the standard MySQL config files are here for 5.65.78.08.4 and 9.x
      • the Postgres config file is here (x10b) and uses io_method=sync
    • 32-core
      • the server has 32-cores and 128G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. AMD SMT is disabled.
      • the standard MySQL config files are here for 5.65.78.08.49.4 and 9.5. For 8.4.7 I also tried a my.cnf file that disabled the InnoDB change buffer (see here). For 9.5.0 I also tried a my.cnf file that disabled a few gtid features that are newly enabled in 9.5 to have a config more similar to earlier releases (see here).
      • the Postgres config file is here and uses io_method=sync
    The Benchmark

    The benchmark is explained here. It was run with 8 clients on the 24-core server and 12 clients on the 32-core server. The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 1800 seconds each.

    The benchmark steps are:

    • l.i0
      • insert X rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client. X is 250M on the 24-core server and 300M on the 32-core server.
    • l.x
      • create 3 secondary indexes per table. There is one connection per client.
    • l.i1
      • use 2 connections/client. One inserts 4M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.
    • l.i2
      • like l.i1 but each transaction modifies 5 rows (small transactions) and 1M rows are inserted and deleted per table.
      • Wait for S seconds after the step finishes to reduce MVCC GC debt and perf variance during the read-write benchmark steps that follow. The value of S is a function of the table size.
    • qr100
      • use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.
    • qp100
      • like qr100 except uses point queries on the PK index
    • qr500
      • like qr100 but the insert and delete rates are increased from 100/s to 500/s
    • qp500
      • like qp100 but the insert and delete rates are increased from 100/s to 500/s
    • qr1000
      • like qr100 but the insert and delete rates are increased from 100/s to 1000/s
    • qp1000
      • like qp100 but the insert and delete rates are increased from 100/s to 1000/s
    Results: overview

    For each server there are three performance reports
    • latest point releases
      • has results for MySQL 5.6.51, 5.7.44, 8.0.44, 8.4.7, 9.4.0 and 9.5.0
      • the base version is 5.6.51 when computing relative QPS
    • all releases
      • has results for MySQL 5.6.51, 5.7.44, 8.0.43, 8.0.44, 8.4.6, 8.4.7, 9.4.0 and 9.5.0
      • the base version is 5.6.51 when computing relative QPS
      • uses two configs for MySQL 9.5.0
        • the cz12a config is the same as was used for 9.4.0
        • the cz13a config disables a few gtid options that were off by default prior to 9.5.0 but are on by default in 9.5.0
    • MySQL vs Postgres
      • has results for MySQL 8.4.7 and Postgres 18.1
      • the base version is MySQL 8.4.7 when computing relative QPS
      • uses two configs for MySQL 8.4.7
        • the cz12a config is my standard my.cnf and is used for the base version
        • the cz12_nocb config is similar to cz12a but disables the InnoDB change buffer
    Results: summary

    The performance reports are here for:
    The summary sections from the performance reports have 3 tables. The first shows absolute throughput by DBMS tested X benchmark step. The second has throughput relative to the version from the first row of the table. The third shows the background insert rate for benchmark steps with background inserts. The second table makes it easy to see how performance changes over time. The third table makes it easy to see which DBMS+configs failed to meet the SLA.

    I use relative QPS to explain how performance changes. It is: (QPS for $me / QPS for $base) where $me is the result for some version $base is the result from the base version. The base version is MySQL 5.6.51 for the latest point releases and all releases reports, and then it is MySQL 8.4.7 for the MySQL vs Postgres reports.

    When relative QPS is > 1.0 then performance improved over time. When it is < 1.0 then there are regressions. The Q in relative QPS measures: 
    • insert/s for l.i0, l.i1, l.i2
    • indexed rows/s for l.x
    • range queries/s for qr100, qr500, qr1000
    • point queries/s for qp100, qp500, qp1000
    Below I use colors to highlight the relative QPS values with yellow for regressions and blue for improvements.

    I often use context switch rates as a proxy for mutex contention.

    Results: latest point releases

    The summaries are here for the 24-core and 32-core servers

    The tables have relative throughput: (QPQ for my version / QPS for MySQL 5.6.51). Values less than 0.95 have a yellow background. Values greater than 1.05 have a blue background.

    From the 24-core server

    • for the load step (l.i0)
      • this is a lot faster in modern MySQL than 5.6.51. While CPU and context switches are similar, modern MySQL uses a lot less read IO -- see rpq, rkbpq, cpupq and cspq here. The reason for read IO dropping is probably the introduction of innodb_log_write_ahead_size that arrives in MySQL 5.7 and avoids the read-before-write cycle that occurs when log writes are smaller than the filesystem page size.
    • for the write-heavy steps (l.i1, l.i2)
      • for the l.i1 step modern MySQL is slower than 5.6.51
        • it uses a bit more read IO (rpq, rkbpq) and write IO (wkbpi) per operation and a lot more more context switches (cspq) and CPU (cpupq) per operation relative to MySQL 5.6.51 -- see here.
        • from charts the max insert time show less variance for 5.6.51 than for 8.4.7 while the insert rate (IPS) charts have different shapes
      • for the l.i2 step modern MySQL is faster than 5.6.51
        • A possible reason is that 5.6.51 has more MVCC GC and writeback debt during l.i2 because it did writes faster during l.i1. 
        • The metrics are here and modern MySQL does less read and write IO per operation while using a similar amount of CPU relative to MySQL 5.6.51.
        • from charts the max insert time looks better for 5.6.51 than for 8.4.7 but the insert rate (IPS) looks better for 8.4.7
    • for the range-query steps (qr100, qr500, qr1000)
      • all versions are able to sustain the target write rates
      • for qr100.L1 where modern MySQL is slower than 5.6.51 it uses more CPU per query than 5.6.51 - see cpupq here
      • but for qr500.L3 where modern MySQL is faster than 5.6.51 is does less read IO and uses less CPU per query than 5.6.51 -- see rpq and cpupq here. Some of this might be side-effects of the change buffer.
    • for the point-query steps (qp100, qp500, qp1000)
      • all versions are able to sustain the target write rates
      • modern MySQL is faster than 5.6.51 but the difference is reduced as the write-rate increases and the difference is largest for qp100.
        • for qp100 modern MySQL uses a bit less read IO (rpq, rkbpq) and a lot less CPU (cpupq) and context switches (cspq) per query relative to MySQL 5.6.51 -- see here
        • for qp1000 the differences in the HW efficiency metrics are smaller than they were for qp100 -- see here
    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my5651_rel_o2nofp.cz12a_c24r641.001.001.001.001.001.001.001.001.001.00
    my5744_rel_o2nofp.cz12a_c24r641.372.750.971.300.861.901.201.341.181.24
    my8044_rel_o2nofp.cz12a_c24r641.272.180.881.190.831.871.181.281.151.09
    my8407_rel_o2nofp.cz12a_c24r641.252.210.851.150.821.791.161.081.091.05
    my9400_rel_o2nofp.cz12a_c24r641.272.120.821.100.841.751.191.121.101.05
    my9500_rel_o2nofp.cz13a_c24r641.262.120.861.140.851.811.201.041.121.03


    From the 32-core server
    • for the load step (l.i0)
      • the load step (l.i0) is a lot faster in modern MySQL than 5.6.51 (see above) and modern MySQL does much less read IO -- see here
    • for the write-heavy steps (l.i1, l.i2)
      • for l.i1 and l.i2 the results are mixed, throughput is sometimes faster and sometimes slower in modern MySQL compared to 5.6.51 
        • for l.i1 the HW efficiency metrics are similar between modern MySQL and 5.6.51
        • for l.i2 the HW efficiency metrics are worse in modern MySQL than 5.6.51
        • MySQL 9.5.0 does better with the cz13a config that disables a few newly enabled gtid options than it does with the cz12a config where they are enabled by default
    • for the range-query steps (qr100, qr500, qr1000)
      • all versions are able to sustain the target write rates for qr100, only 9.4.0 sustains them for qr500 and none sustain them for qr1000. The server needs more IOPs capacity. So I focus on qp100.
      • modern MySQL does worse than 5.6.51 on qr100.L1
        • it does more read IO (rpq, rkbpq) and more write IO (wkbpi), uses more CPU (cpupq) and does more context switches (cspq) than 5.6.51 -- see here
    • for the point-query steps (qp100, qp500, qp1000)
      • all versions are able to sustain the target write rates for qp100, only 5.6.51, 8.4.7 and 9.4.0 sustain them for qp500 and none sustain them for qp1000. The server needs more IOPs capacity. So I focus on qp100.
      • modern MySQL does better than 5.6.51 on qp100.L2
        • it does less read IO (rpq, rkbpq) and less write IO (wkbpi), uses less CPU (cpupq) and does fewer context switches (cspq) than 5.6.51 -- see here

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my5651_rel_o2nofp.cz12a_c32r1281.001.001.001.001.001.001.001.001.001.00
    my5744_rel_o2nofp.cz12a_c32r1281.282.861.061.140.861.571.250.871.070.85
    my8044_rel_o2nofp.cz12a_c32r1281.571.491.071.030.791.661.210.920.930.66
    my8407_rel_o2nofp.cz12a_c32r1281.541.441.050.930.761.581.190.770.870.64
    my9400_rel_o2nofp.cz12a_c32r1281.551.451.060.920.791.561.210.770.860.63
    my9500_rel_o2nofp.cz12a_c32r1281.531.450.691.380.781.651.160.820.980.62
    my9500_rel_o2nofp.cz13a_c32r1281.531.461.070.950.771.591.220.790.880.64



    Results: all releases

    The summaries are here for the 24-core and 32-core servers. I won't describe these other than to claim that performance is similar between adjacent point releases (8.4.6 vs 8.4.7, 8.0.43 vs 8.0.44).

    Results: MySQL vs Postgres

    The summaries are here for the 24-core and 32-core servers.

    The c12a_nocb config disabled the InnoDB change buffer and the impact from that helped on some steps but hurt on others. It definitely works.
    • MySQL sustains higher write rates when it is enabled. The rates for read IO (rpq, rkbpq) and write IO (wkbpi) are much higher when it is disabled -- see here.
    • MySQL sometimes gets higher query rates when it is disabled but that only occurred on steps where the target write rates weren't sustained so I won't claim this really helped.
    From the 24-core server
    • for the load step (l.i0)
      • Postgres is faster. Relative to MySQL it uses a bit more read IO (rpq, rkbpq) and write IO (wkbpi) but a lot less CPU (cpupq) and context switches (cspq) -- see here 
    • for the write-heavy steps (l.i1, l.i2)
      • for l.i1 Postgres is faster
        • Postgres does less read IO (rpq, rkbpq) and write IO (wkbpi) per insert. It also uses less CPU (cpupq) and fewer context switches per insert -- see here.
        • the insert rate over time has far more variance for Postgres than for MySQL
      • for l.i2 Postgres is slower
        • Postgres does less read IO (rpq, rkbpq), write IO (wkbpi) and uses fewer context switches per insert. But is uses almost 3X more CPU per insert (cpupq) -- see here.
    • for the range-query steps (qr100, qr500, qr1000)
      • MySQL 8.4.7 with the change buffer enabled and Postgres 18.1 sustained the target write rates
      • for qr100 Postgres was faster than MySQL and uses less of everything, read IO (rpq, rkbpq), write IO (wkbpi), CPU (cpupq) and context switches (cspq) -- see here
    • for the point-query steps (qp100, qp500, qp1000
      • MySQL 8.4.7 with the change buffer enabled and Postgres 18.1 sustained the target write rates
      • for qp100 Postgres was slower than MySQL and uses more read IO (rpq), CPU (cpupq) and context switches (cspq) per query -- see here. But Postgres was much faster than MySQL for qp500 and qp1000.

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my8407_rel_o2nofp.cz12a_c24r641.001.001.001.001.001.001.001.001.001.00
    my8407_rel_o2nofp.cz12a_nocb_c24r640.970.970.542.510.991.041.001.111.400.61
    pg181_o2nofp.cx10b_c24r641.552.431.910.771.660.861.672.851.942.37


    From the 32-core server
    • for the load step (l.i0)
      • Postgres is faster. Relative to MySQL it uses a bit more read IO (rpq, rkbpq) and write IO (wkbpi) but a lot less CPU (cpupq) and context switches (cspq) -- see here 
    • for the write-heavy steps (l.i1, l.i2)
      • for l.i1 Postgres is faster 
        • Postgres uses less of everything per insert -- read IO (rpq, rkbpq), write IO (wkbpi), CPU (cpupq), context switches (cspq) -- see here
        • charts for the insert rate over time make nice art but both MySQL and Postgres have too much variance although the variance is worse for Postgres than for MySQL
      • for l.i2 Postgres is faster
        • Postgres does less read IO (rpq, rkbpq), write IO (wkbpi) and uses fewer context switches per insert. But is uses almost 3X more CPU per insert (cpupq) -- see here.
    • for the range-query steps (qr100, qr500, qr1000)
      • MySQL 8.4.7 with the change buffer enabled sustained the target write rate for qr100 but not for qr500 or qr1000 so I focus on qr100. Postgres 18.1 sustained the target write rate for qr100 and qr500 but not for qr1000. The server needs more IOPs capacity.
      • for qr100 Postgres was faster than MySQL and uses less of everything, read IO (rpq, rkbpq), write IO (wkbpi), CPU (cpupq) and context switches (cspq) -- see here
    • for the point-query steps (qp100, qp500, qp1000)
      • MySQL 8.4.7 with the change buffer enabled sustained the target write rate for qp100 and qp500 but not for qp1000 so I focus on qp100. Postgres 18.1 sustained the target write rate for qp100 and qp500 but not for qp1000. The server needs more IOPs capacity.
      • for qp100 Postgres was faster than MySQL and uses more read IO (rpq) but less CPU (cpupq) per query -- see here

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my8407_rel_o2nofp.cz12a_c32r1281.001.001.001.001.001.001.001.001.001.00
    my8407_rel_o2nofp.cz12a_nocb_c32r1281.011.030.480.741.001.000.900.461.000.70
    pg181_o2nofp.cx10b_c32r1281.522.753.521.261.671.101.913.142.321.45




















    Automate the export of Amazon RDS for MySQL or Amazon Aurora MySQL audit logs to Amazon S3 with batching or near real-time processing

    Amazon RDS for MySQL and Amazon Aurora MySQL provide built-in audit logging capabilities, but customers might need to export and store these logs for long-term retention and analysis. Amazon S3 offers an ideal destination, providing durability, cost-effectiveness, and integration with various analytics tools. In this post, we explore two approaches for exporting MySQL audit logs to Amazon S3: either using batching with a native export to Amazon S3 or processing logs in real time with Amazon Data Firehose.

    The Evolution of Redis: From Cache to AI-Database (V1.0 to 8.4)

    One of our founders, Peter Zaitsev took a look at Redis (Remote DIctionary Server) when it first emerged in 2009 https://www.percona.com/blog/looking-at-redis/ which reminded me how far this project has come in sixteen years, evolving from a simple key-value store into a multi-model platform including vector search. This article covers this evolution in four distinct eras. […]

    Agentic AI and The Mythical Agent-Month

    The premise of this position paper is appealing. We know Brooks' Law: adding manpower to a late software project makes it later. That is, human engineering capacity grows sub-linearly with headcount due to communication overhead and ramp-up time. The authors propose that AI agents offer a loophole: "Scalable Agency". Unlike humans, agents do not need days/weeks to ramp up, they load context instantly. So, theoretically, you can spin up 1,000 agents to explore thousands of design hypotheses in parallel, compressing the Time to Integrate (TTI: duration required to implement/integrate new features/technologies into infrastructure systems) for complex infrastructure from months to days.

    The paper calls this vision Self-Defining Systems (SDS), and suggests that thanks to Agentic AI future infrastructure will design, implement, and evolve itself. I began reading with great excitement, but by the final sections my excitement soured into skepticism. The bold claims of the introduction simply evaporated (especially about TTI), never to be substantiated or revisited. Worse, the paper left even the most basic concepts (e.g., "specification") vague. 

    Still, the brief mention of Brooks' Law stayed with me. (The introduction glides past it far too casually.) Thinking it through, I came to conclude that we are not escaping the Mythical Man-Month anytime soon, not even with agents. The claim that "Scalable Agency" bypasses Brooks' Law is not supported by the evidence. Coordination complexity ($N^2$) is a mathematical law, not a sociological suggestion as some people take Brooks' Law to mean. Until we solve the coordination and verification bottlenecks, adding more agents to a system design problem will likely just result in a faster and more expensive way to generate merge conflicts. 


    The "Scalable Agency" vs. The Reality

    The core premise of "Scalable Agency" seems to rely on a flawed assumption, that software engineering is an embarrassingly parallel task. My intuition, and the paper's own results, suggests the opposite.

    The case study in the paper tasked agents with building an LLM inference runtime. The agent-built monolithic-llm-runtime achieved 1.2k tokens/s, while a simple human-written baseline, nano-vLLM, achieved 1.76k tokens/s. The paper says that the agents successfully "rediscovered" standard techniques like CUDA graphs (I suspect this was already in training data for the LLMs), but they hit a hard ceiling. They could not propose "qualitatively new designs". They scaled volume, but not insight. This reminds me of the famous video of three professional soccer players against one hundred kids: You can add more bodies, but without shared intuition and expert judgment, numbers alone do not win real games.

    The results get worse when we look at integration. When tasked with building allmos_v2 on top of an existing codebase (allmos), the agents took 35 days to produce a working system. The paper blames "deployment failures," "GLIBC mismatches," and "driver issues", but I suspect the true complexity came from the architecture itself. Allmos_v2 was not a monolith, but an integration on top of distributed pre-existing components. The agents were likely thrashing against the exponential complexity of a distributed dependency graph.

    (As an aside: The paper claims the second system, monolithic-llm-runtime, took only 2 days because the agents reused a "key solutions playbook" generated during the allmos_v2 attempt. While the playbook surely helped, I suspect the monolithic architecture was the real hero here.)

    This mirrors the classic Common Knowledge Problem in distributed systems: for a protocol to be robust, every node must know not just the fact (distributed knowledge), but that every other node knows it (common knowledge). Agentic systems face a similar epistemic gap. "Context loading" is not equivalent to "common knowledge". Agents may ingest 100,000 lines of code instantly, but reading tokens is not the same as understanding the causal chain of changes across the system. As architectures grow (especially non-monolithic ones) the compute required to simulate downstream effects of a line change explodes exponentially. An agent may "see" the entire codebase, but without common knowledge of how, for example, a batcher change propagates through kernel fusion, regressions appear. Multi-agent systems do not escape Brooks' Law, they simply hit the wall of state-space explosion faster than humans do.


    The Shell Game

    In the end Self-Defining Systems remains as a blue sky vision paper that fails to improve on concrete results compared to its predecessor, the ADRS paper ("Barbarians at the Gate"). ADRS admitted to being a tool for optimizing existing codebases, tuning parameters and heuristics within a defined sandbox. SDS promises to go further, claiming the ability to design systems from scratch, but in practice it largely replays the ADRS workflow with new terminology. SDS proposes a progression ranging from Phase 1: Self-Configuring to Phase 5: Self-Managing, but admits in the fine print that "Our current methodology retains goal setting, architecture decomposition, and evaluation design as human responsibilities..." This means that we haven't actually moved the needle on design at all. We are still just doing hyper-parameter tuning with AI as the intern.

    This reminds me of the Shell Game podcast, where Evan Ratliff tried to build a startup using only AI agents. (You know what a shell game means, right?)  Evan sets out to build a real company, HurumoAI, staffed almost entirely by AI agents to test if a one-man unicorn was possible by managing AI employees in a startup environment. The experiment sounds futuristic until you learn how this unravels. Spoiler alert: even with the help of a Stanford technical co-founder, Evan eventually bails out and pivots to a joke/novelty business model: AI agents that procrastinate and browse brainrot so you don't have to. 

    January 20, 2026

    Using the shared plan cache for Amazon Aurora PostgreSQL

    In this post, we discuss how the Shared Plan Cache feature of the Amazon Aurora PostgreSQL-Compatible Edition can significantly reduce memory consumption of generic SQL plans in high-concurrency environments.

    CPU-bound Insert Benchmark vs MySQL on 24-core and 32-core servers

    This has results for MySQL versions 5.6 through 9.5 with a CPU-bound Insert Benchmark on 24-core and 32-core servers. The workload uses a cached database so it is often CPU-bound but on some steps does much write IO. 

    Results from a small server are here and note that MySQL often has large performance regressions at low concurrency from new CPU overhead while showing large improvements at high concurrency from less mutex contention. The tests here use medium or high concurrency while low concurrency was used on the small server.

    tl;dr

    • good news
      • Modern MySQL has large improvements for write-heavy benchmark steps because it reduces mutex contention
    • bad news
      • Modern MySQL has large regressions for read-heavy benchmarks steps because it uses more CPU
    • other news
      • Postgres 18.1 was faster than MySQL 8.4.7 on all of the benchmark steps except l.i2 which is write-heavy and does random inserts+deletes. But Postgres also suffers the most from variance and stalls on the write-heavy benchmark steps.

    Builds, configuration and hardware

    I compiled MySQL from source for versions 5.6.51, 5.7.44, 8.0.43, 8.0.44, 8.4.6, 8.4.7, 9.4.0 and 9.5.0. I also compiled Postgres 18.1 from source.

    The servers are:
    • 24-core
      • the server has 24-cores, 2-sockets and 64G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. Intel HT is disabled.
      • the standard MySQL config files are here for 5.65.78.08.4 and 9.x
      • the Postgres config file is here (x10b) and uses io_method=sync
    • 32-core
      • the server has 32-cores and 128G of RAM. Storage is 1 NVMe device with ext-4 and discard enabled. The OS is Ubuntu 24.04. AMD SMT is disabled.
      • the standard MySQL config files are here for 5.6, 5.7, 8.0, 8.4, 9.4 and 9.5. For 8.4.7 I also tried a my.cnf file that disabled the InnoDB change buffer (see here). For 9.5.0 I also tried a my.cnf file that disabled a few gtid features that are newly enabled in 9.5 to have a config more similar to earlier releases (see here).
      • the Postgres config file is here and uses io_method=sync
    The Benchmark

    The benchmark is explained here. It was run with 8 clients on the 24-core server and 12 clients on the 32-core server. The point query (qp100, qp500, qp1000) and range query (qr100, qr500, qr1000) steps are run for 1800 seconds each.

    The benchmark steps are:

    • l.i0
      • insert 10M rows per table in PK order. The table has a PK index but no secondary indexes. There is one connection per client.
    • l.x
      • create 3 secondary indexes per table. There is one connection per client.
    • l.i1
      • use 2 connections/client. One inserts 16M rows per table and the other does deletes at the same rate as the inserts. Each transaction modifies 50 rows (big transactions). This step is run for a fixed number of inserts, so the run time varies depending on the insert rate.
    • l.i2
      • like l.i1 but each transaction modifies 5 rows (small transactions) and 4M rows are inserted and deleted per table.
      • Wait for S seconds after the step finishes to reduce MVCC GC debt and perf variance during the read-write benchmark steps that follow. The value of S is a function of the table size.
    • qr100
      • use 3 connections/client. One does range queries and performance is reported for this. The second does does 100 inserts/s and the third does 100 deletes/s. The second and third are less busy than the first. The range queries use covering secondary indexes. If the target insert rate is not sustained then that is considered to be an SLA failure. If the target insert rate is sustained then the step does the same number of inserts for all systems tested. This step is frequently not IO-bound for the IO-bound workload.
    • qp100
      • like qr100 except uses point queries on the PK index
    • qr500
      • like qr100 but the insert and delete rates are increased from 100/s to 500/s
    • qp500
      • like qp100 but the insert and delete rates are increased from 100/s to 500/s
    • qr1000
      • like qr100 but the insert and delete rates are increased from 100/s to 1000/s
    • qp1000
      • like qp100 but the insert and delete rates are increased from 100/s to 1000/s
    Results: overview

    For each server there are three performance reports
    • latest point releases
      • has results for MySQL 5.6.51, 5.7.44, 8.0.44, 8.4.7, 9.4.0 and 9.5.0
      • the base version is 5.6.51 when computing relative QPS
    • all releases
      • has results for MySQL 5.6.51, 5.7.44, 8.0.43, 8.0.44, 8.4.6, 8.4.7, 9.4.0 and 9.5.0
      • the base version is 5.6.51 when computing relative QPS
    • MySQL vs Postgres
      • has results for MySQL 8.4.7 and Postgres 18.1
      • the base version is MySQL 8.4.7 when computing relative QPS
      • uses two configs for MySQl 8.4.7
        • the cz12a config is my standard my.cnf and is used for the base version
        • the cz12_nocb config is similar to cz12a but disables the InnoDB change buffer
    The performance reports are here for:
    The summary sections from the performances report have 3 tables. The first shows absolute throughput by DBMS tested X benchmark step. The second has throughput relative to the version from the first row of the table. The third shows the background insert rate for benchmark steps with background inserts. The second table makes it easy to see how performance changes over time. The third table makes it easy to see which DBMS+configs failed to meet the SLA.

    I use relative QPS to explain how performance changes. It is: (QPS for $me / QPS for $base) where $me is the result for some version $base is the result from the base version. The base version is MySQL 5.6.51 for the latest point releases and all releases reports, and then it is MySQL 8.4.7 for the MySQL vs Postgres reports.

    When relative QPS is > 1.0 then performance improved over time. When it is < 1.0 then there are regressions. The Q in relative QPS measures: 
    • insert/s for l.i0, l.i1, l.i2
    • indexed rows/s for l.x
    • range queries/s for qr100, qr500, qr1000
    • point queries/s for qp100, qp500, qp1000
    Below I use colors to highlight the relative QPS values with yellow for regressions and blue for improvements.

    I often use context switch rates as a proxy for mutex contention.

    Results: latest point releases

    The summaries are here for the 24-core and 32-core servers
    • modern MySQL does better than 5.6.51 on write-heavy steps
      • For the 24-core server there was less CPU overhead (cpupq) and were fewer context switches (cspq) during the l.i1 benchmark step (see here)
      • For the 32-core server there was less CPU overhead (cpupq) and were fewer context switches (cspq) during the l.i1 benchmark step (see here). The reduction in context switches here wasn't as large as it was for the 24-core/2-socket server.
      • For the 32-core server the cz13a config that disables some newly enabled gtid options has much less mutex contention than the cz12a config that enables them (by default)
    • modern MySQL does worse than 5.6.51 on read-heavy steps, from new CPU overhead
      • For the 24-core server CPU per query (cpupq) is up to 1.3X larger for range queries and up to 1.5X larger for point queries on modern MySQL vs 5.6.51 -- see cpupq here for qr100.L1 and qp100.L2
      • For the 32-core server CPU per query (cpupq) is up to 1.4X larger for range queries and up to 1.5X larger for point queries on modern MySQL vs 5.6.51 -- see cpupq here for qr100.L1 and qp100.L2
    The tables have relative throughput: (QPQ for my version / QPS for MySQL 5.6.51). Values less than 0.95 have a yellow background. Values greater than 1.05 have a blue background.

    From the 24-core server

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my5651_rel_o2nofp.cz12a_c24r641.001.001.001.001.001.001.001.001.001.00
    my5744_rel_o2nofp.cz12a_c24r641.012.881.471.420.840.880.860.890.870.90
    my8044_rel_o2nofp.cz12a_c24r640.962.842.091.440.780.670.800.670.810.68
    my8407_rel_o2nofp.cz12a_c24r640.952.812.071.430.760.660.780.670.790.67
    my9400_rel_o2nofp.cz12a_c24r640.932.841.611.360.780.670.800.680.810.68
    my9500_rel_o2nofp.cz13a_c24r640.942.811.661.350.770.670.790.670.800.69


    From the 32-core server

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my5651_rel_o2nofp.cz12a_c32r1281.001.001.001.001.001.001.001.001.001.00
    my5744_rel_o2nofp.cz12a_c32r1281.164.061.371.620.830.850.840.860.860.87
    my8044_rel_o2nofp.cz12a_c32r1281.384.682.431.920.700.630.720.640.730.65
    my8407_rel_o2nofp.cz12a_c32r1281.374.842.421.910.700.630.710.630.720.65
    my9400_rel_o2nofp.cz12a_c32r1281.364.762.401.890.710.640.730.650.740.66
    my9500_rel_o2nofp.cz12a_c32r1281.354.842.192.000.720.640.730.650.750.66
    my9500_rel_o2nofp.cz13a_c32r1281.364.842.411.890.700.640.720.650.730.66



    Results: all releases

    The summaries are here for the 24-core and 32-core servers. I won't describe these other than to claim that performance is similar between adjacent point releases (8.4.6 vs 8.4.7, 8.0.43 vs 8.0.44).

    Results: MySQL vs Postgres

    The summaries are here for the 24-core and 32-core servers.
    • While Postgres does better than MySQL on l.i1 it does worse on l.i2, perhaps because there is more MVCC debt (things to be vacuumed) during l.i1. The l.i1 and l.i2 benchmark steps are the most write-heavy. Transactions (number of rows changed) are 10X larger for l.i1 than l.i2. 
      • For l.i1 the insert rate has more variance with Postgres than MySQL -- see here for the 24-core (MySQL, Postgres) and 32-core (MySQL, Postgres) servers. Also, Postgres has a few obvious write-stalls on the 32-core server.
      • For l.i2 the insert rate has more variance with Postgres than MySQL -- see here for the 24-core (MySQL, Postgres) and 32-core (MySQL, Postgres) servers. Also, Postgres has frequent write-stalls.
      • For l.i1 Postgres uses less CPU per operation than MySQL while for l.i2 it uses more -- see cpupq for the 24-core and 32-core servers
    • Postgres does better than MySQL on the read-heavy steps (qr* and qp*)
      • For qr100.L1 (range queries) the CPU per query is ~1.5X larger for MySQL than Postgres and context switches per query are ~1.7X larger for MySQL than for Postgres -- see cpupq and cspq for the 24-core and 32-core servers
      • For qp100.L2 (point queries) the CPU per query is ~1.25X larger for MySQL than Postgres and context switches per query are 1.5X larger for MySQL than Postgres -- see cpupq and cspq for the 24-core and 32-core servers
    • Performance for MySQL is similar between the cz12a and cz12a_nocb configs. That is expected because the database is cached and there is no (or little) use of the change buffer.
    For the 24-core server

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my8407_rel_o2nofp.cz12a_c24r641.001.001.001.001.001.001.001.001.001.00
    my8407_rel_o2nofp.cz12a_nocb_c24r640.991.031.010.991.001.000.991.000.991.00
    pg181_o2nofp.cx10b_c24r641.401.371.360.491.671.241.641.241.661.25


    For the 32-core server

    dbmsl.i0l.xl.i1l.i2qr100qp100qr500qp500qr1000qp1000
    my8407_rel_o2nofp.cz12a_c32r1281.001.001.001.001.001.001.001.001.001.00
    my8407_rel_o2nofp.cz12a_nocb_c32r1281.011.001.021.001.001.011.001.011.001.01
    pg181_o2nofp.cx10b_c32r1281.371.161.620.791.711.251.691.261.691.26







    What Oracle Missed, We Fixed: More Performant Query Processing in Percona Server for MySQL, Part 2

    Remember when Percona significantly improved query processing time by fixing the optimizer bug? I have described all the details in More Performant Query Processing in Percona Server for MySQL blog post. This time, we dug deeper into all the ideas from Enhanced for MySQL and based on our analysis, we proposed several new improvements. All […]

    January 19, 2026

    LLMs and your career

    The most conservative way to build a career as a software developer is 1) to be practical and effective at problem solving but 2) not to treat all existing code as a black box. 1 means that as a conservative developer you should generally use PostgreSQL or MySQL (or whatever existing database), Rails or .NET (or whatever existing framework), and adapt code from Stack Overflow or LLMs. 2 means that you're curious and work over time to better understand how web servers and databases and operating systems and the browser actually work so that you can make better decisions for your own problems as you adapt other people's code and ideas.

    Coding via LLM is not fundamentally different from coding with Rails or coding by perusing Stack Overflow. It's faster and more direct but it's still potentially just a human mindlessly adapting existing code.

    The people who were only willing to look at existing frameworks and libraries and applications as black boxes were already not the most competitive when it came to finding and retaining work. And on the other hand, the most technically interesting companies always wanted to hire developers who understood fundamentals because they're 1) operating at such a scale that the way the application is written matters or they're 2) building PostgreSQL or MySQL or Rails or .NET or Stack Overflow or LLMs, etc.

    The march of software has always been to reduce the need for (ever larger sizes of) SMBs (and teams within non-SMBs) to hire developers to solve problems or increase productivity. LLMs are part of that march. That doesn't change that at some point companies (or teams) need to hire developers because the business or its customer base has become too complex or too large.

    The jobs that were dependent on fundamentals of software aren't going to stop being dependent on fundamentals of software. And if more non-developers are using LLMs it's going to mean all the more stress on tools and applications and systems that rely on fundamentals of software.

    All of this is to say that if you like doing software development, I don't think interesting software development jobs are going to go away. So keep learning and keep building compilers and databases and operating systems and keep looking for companies that have compiler and database and operating system products, or companies with other sorts of interesting problems where fundamentals matter due to their scale.

    January 18, 2026

    MongoDB compared to Oracle Database Maximum Availability Architecture

    Users looking at Oracle alternatives see the limitations of PostgreSQL in terms of high availability and ask:

    Will MongoDB give me the same high availability (HA), disaster recovery (DR), and zero/near-zero data loss I’ve relied on in Oracle’s Maximum Availability Architecture (MAA)?

    I previously compared Oracle Maximum Availability Architecture with YugabyteDB’s built-in high availability features here. I've done the same with MongoDB, which also offers built-in high availability though Raft-based replication to quorum, but for a document-model application rather than SQL.

    Oracle MAA and MongoDB

    Oracle DBAs are used to a mature, integrated stack—RAC, Data Guard, GoldenGate, Flashback, and RMAN—refined for decades to work together in the MAA framework. MongoDB instead takes a cloud-native, distributed approach, embedding replication, failover, and sharding directly in its core engine rather than through separate options. While the technologies differ—MongoDB uses pull-based logical replication instead of broadcasting physical log changes—they pursue the same goals.

    MongoDB vs Oracle MAA Levels

    MongoDB Oracle🥉 MAA Bronze Oracle🥈 MAA Silver Oracle🥇 MAA Gold Oracle💰 MAA Platinum
    Main Goal Cloud-Native Resilience Backup MAA Bronze + HA MAA Silver + DR MAA Gold + Multi-Master
    HA/DR Product Native in MongoDB core: replica sets, sharding, multi-region, backups RMAN RAC RAC + Data Guard (ADG) RAC + Data Guard + GoldenGate
    RPO (Recovery Point Objective) 0 with writeConcern: "w:majority" (sync to quorum), which is the default Minutes (archivelog backups) Same as Bronze (no extra DB protection) 0 with Maximum Protection0 or more with Maximum Availability Seconds if tuned with low replication lag
    RTO (Recovery Time Objective) 5–15 seconds for automatic primary election Hours–Days depending on database size Seconds for server crash, but DB is not protected Minutes with FSFO (Sync) Seconds if no conflicts in active-active failover
    Corrupt Block Detection Automatic via storage engine checksums and replica healing RMAN detection + manual blockrecover No additional protection Automatic with lost-write detection (ADG) No additional protection beyond Gold
    Online Upgrade Zero downtime via rolling upgrades Minutes–hours for major releases Some minor rolling upgrades (OS/patch) Near-zero downtime with transient logical Yes, if GoldenGate active-active is configured correctly
    Human Error Mitigation Point-in-time restores from Atlas backups or filesystem snapshots Flashback Database, Snapshot Standby, LogMiner, Flashback Query, Flashback Transaction, dbms_rolling, dbms_redefinition, ASM Same + RAC Same + ADG Similar to Gold (GoldenGate can replay changes)
    Online DDL Non-blocking schema changes (document model) No transactional DDL; EBR (Edition-Based Redefinition) Same as Bronze Same as Bronze Same as Gold
    Active-Active Via sharded clusters or Atlas Global Clusters; shard-key design avoids conflicts No — only standby server offline unless licensed RAC active-active within the same datacenter No — standby is read-only (Active DG) Yes — GoldenGate active-active, requires conflict handling
    Application Continuity Automatic failover with drivers + retryable writes CMAN proxy Transparent failover + Transaction Guard (RAC) Transparent failover (Data Guard) Transparent failover + conflict handling
    Complexity Low (built-in distributed features) Low High (clusterware) Medium (RAC + DG) High (RAC + DG + GoldenGate)
    Price (Options) EE (~$50K/CPU) EE + 50% (RAC option) EE + RAC + 25% (ADG option) EE + RAC + ADG + 75% (GoldenGate; often ~2.5× EE base)

    Understanding Oracle MAA Levels

    Oracle’s Maximum Availability Architecture (MAA) is organized into four tiers, each building on the previous:

    1. 🥉 Bronze – Backup Only: RMAN online backups, archived log retention, manual restore. RPO in minutes to hours, RTO in hours or days.
    2. 🥈 Silver – Bronze + High Availability: Add Real Application Clusters (RAC) for server-level HA and limited rolling upgrades. Very low RTO for server failures but no additional database protection beyond Bronze’s backups.
    3. 🥇 Gold – Silver + Disaster Recovery: Add Data Guard (often Active Data Guard) for synchronous or asynchronous failover to remote standby. With FSFO (Fast Start Failover) in sync mode, RPO can be zero, RTO in minutes.
    4. 💰 Platinum – Gold + Multi-Master: Add GoldenGate logical replication for active-active writes across sites and versions. Enables global presence but requires manual conflict handling and careful design.

    In Oracle’s world, HA/DR is not one thing — it’s a stack of separate licensed products you combine according to your needs and budget. The complexity and cost rise quickly as you move up the tiers.

    Focus on features rather than product names, which can be misleading. For example, Autonomous Data Guard (RPO > 0, no real-time query, no rolling upgrade) in the Autonomous Database managed service differs from Active Data Guard, which provides all features of MAA Gold, but is not part of the managed service.

    How MongoDB Builds HA and DR Natively

    MongoDB’s availability model takes a fundamentally different approach: it is a distributed database by design, with no single authoritative copy bottleneck like a monolithic RDBMS. Replication, failover, sharding, and multi-region deployment are built in, not optional add-ons.

    Key components:

    • Replica Sets – Every MongoDB production deployment runs as a replica set by default.
      • Automatic leader (primary) election in 5–30 seconds if a node fails.
      • Tunable write concern for RPO control (e.g., "w:majority" for RPO=0).
      • Flow control to throttle writes when majority committed replication lag reaches a threshold to prevent secondaries from falling too far behind
    • Sharding – Horizontal partitioning with each shard itself being a replica set.
      • Supports scaling out writes and reads while maintaining HA per shard.
    • Multi-Region Clusters – Place replica set members in multiple regions/zones for DR.
      • Writes propagate to remote members, optionally synchronously (Atlas global writes).
    • Backups & Point-in-Time Restore – Atlas offers continuous cloud backups with PIT restore. Self-managed deployments use snapshots + Oplog replay.
    • Automatic Failover & Driver-Level Continuity – Client drivers reconnect automatically. Retryable writes can resume after failover without application-level intervention.
    • Rolling Upgrades Without Downtime – Upgrade nodes one at a time, keeping the cluster online.

    This means that what takes three separate licensed Oracle products to achieve in Platinum is, with MongoDB, simply a topology decision you make when deploying the cluster.

    Key Takeaways

    • Oracle MAA levels are additive—Bronze, Silver, Gold, and Platinum. Each higher tier requires extra products and adds complexity for on‑premises deployments, and not all tiers are available in managed services. They are very mature for enterprises, but you should not underestimate the operational cost.
    • MongoDB provides built-in HA and DR. Replication, failover, sharding, and multi-region deployments are topology settings that simplify operations. However, configuration still matters: consistency is tunable per application, and settings must align with your infrastructure’s capabilities.
    • RPO and RTO targets achievable with Oracle Platinum are also achievable with MongoDB. The main difference is flashback capabilities, so you must tightly control production access and ensure all changes are automated and tested first in pre-production.
    • MongoDB rolling upgrades eliminate downtime for routine maintenance—a major change from traditional monolithic upgrade windows. Avoid running outdated versions, as was common with legacy databases.
    • Global write scenarios are possible in both, but MongoDB’s sharded architecture can automatically avoid conflicts (ACID properties over the cluster, causal consistency)

    January 16, 2026

    Rethinking the University in the Age of AI

    Three years ago, I wrote a post titled "Getting schooled by AI, colleges must evolve". I argued that as we entered the age of AI, the value of "knowing" was collapsing, and the value of "doing" was skyrocketing. (See Bloom's taxonomy.)

    Today, that future has arrived. Entry-level hiring has stalled because AI agents absorb the small tasks where new graduates once learned the craft.

    So how do we prepare students for this reality? Not only do I stand by my original advice, I am doubling down. Surviving this shift requires more than minor curriculum tweaks; it requires a different philosophy of education. I find two old ideas worth reviving: a systems design mindset that emphasizes holistic foundations, and alternative education philosophies of the 1960s that give students real agency and real responsibility.


    Holistic Foundations

    Three years ago, I begged departments: "Don't raise TensorFlow disk jockeys. Teach databases! Teach compilers! Teach system design!" This is even more critical today. Jim Waldo, in his seminal essay On System Design, warned us that we were teaching "programming" (the act of writing code) rather than "system design" (the art of structuring complexity).

    AI may have solved the tedious parts of programming but it has not solved system design. To master system design, students must understand the layers both beneath and above what AI touches. Beneath the code layer lie compilers and formal methods. Compilers matter not so students can build languages, but so they understand cost models, optimization, and the real behavior of generated code. Formal methods matter not to turn everyone into a theorem prover, but to reason about safety, liveness, and failure using invariants. When an AI suggests a concurrent algorithm, students must be able to sense/red-flag the race conditions.

    Above the code layer lies system design. As Waldo observed, the best architects are synthesizers. They glue components together without breaking reliability, a  skill that remains stubbornly human. System design lives in an open world of ambiguity, trade-offs, incentives, and failure modes that are never written down. This is why "mutts" with hybrid background often excel in system design. Liberal arts train you to reason about systems made of humans, incentives, and the big picture. Waldo's point rings true. Great system designers understand the environment/context first, then map that understanding into software.


    Radical Agency 

    My original post called for flipped classrooms and multi-year projects. These still assume a guided path. We may need to go further and embrace the philosophy of the "Summerhill" model, where the school adapts to the learner, rather than the learner fitting the school. In an age of abundant information, the scarce resource is judgment. Universities must stop micromanaging learning and start demanding ownership. The only metric that matters is learning to learn.

    Replace tests with portfolios. Industry hires from portfolios, not from grades. Let students use AI freely, you cannot police it anyway. If a student claims they built a system, make them defend it in a rigorous, face-to-face oral exam using the Harkness method. Ask them to explain, how the system behaves under load, under failure, and at the corner cases. 

    In 2023, I argued for collaboration and writing. In 2026, that is the whole game. Collaboration now beats individual brilliance. Writing is a core technical skill. A clear specification is fast becoming equivalent to correct code. Students who cannot write clearly cannot think clearly. 

    I also argued for ownership-driven team projects. To make them real, universities must put skin in the game. Schools should create a small seed fund. The deal is simple: $20,000 for 1% equity in student-run startups. The goal is not financial return (which I think will materialize), but contact with reality. Students must incorporate, manage equity, run meetings, and make decisions with consequences.

    Deploying Percona Operator for MySQL with OpenTaco for IaC Automation

    Deploying databases on Kubernetes is getting easier every year. The part that still hurts is making deployments repeatable and predictable across clusters and environments, especially from Continuous Integration(CI) perspective. This is where PR-based automation helps; you can review a plan, validate changes, and only apply after approval, before anything touches your cluster.  If you’ve ever […]

    January 15, 2026

    Percona Operator for PostgreSQL 2025 Wrap Up and What We Are Focusing on Next

    In 2025, the Percona Operator for PostgreSQL put most of its energy into the things that matter when PostgreSQL is running inside real Kubernetes clusters: predictable upgrades, safer backup and restore, clearer observability, and fewer surprises from image and HA version drift.  Backups and restores got more resilient and more controllable In March, Operator 2.6.0 […]

    Announcing Percona ClusterSync for MongoDB: The Open Source Trail to Freedom

    Migrating mission-critical databases is often compared to changing the engines on a plane while it’s in mid-flight. For MongoDB users, this challenge has been historically steep, often involving complex workarounds or proprietary tools that keep you locked into a specific ecosystem. Today, we are thrilled to announce the General Availability of Percona ClusterSync for MongoDB […]