Tuesday, September 24, 2024

Configuration options that make writes faster or slower with MySQL and Postgres

This post is inspired by a comment about a recent blog post I shared. While I don't focus on MySQL vs Postgres comparisons, sometimes I share them and in my benchmarks with CPU-bound workloads, and fsync disabled (even more CPU-bound) the general result was that insert throughput was much better with Postgres than MySQL+InnoDB.

The commenter stated that Postgres and MySQL+InnoDB get similar insert rates when the MySQL binlog is disabled. I rarely test such a config because I never ran MySQL in production without the binlog enabled. Regardless, the commenter is a smart person so I decided to run a few more tests.

Some of the configurations I test here are great for performance but lousy for durability so don't take anything here as tuning advice.

tl;dr

  • MySQL+InnoDB suffers more tha Postgres from updates that require index maintenance
  • For MySQL with the binlog disabled, insert throughput gets close to Postgres results but not many deployments run primaries with the binlog disabled. The gap is much larger, in favor of Postgres, for updates 
  • When fsyncs are enabled the difference is much larger, in favor of Postgres. One reason is that MySQL must do an fsync for the binlog and InnoDB redo while Postgres only does it for the WAL. But there is more going on here and the fsync impact doesn't explain all of the performance differences.

Configurations tested

I used MySQL 8.0.39 and Postgres 17rc1 both compiled from source. The config files are here.

For MySQL I used 12 configurations:

name            binlog  fsync   InnoDB doublewrite
bl0_sync0_dw0   off     off     off
bl0_sync0_dw1   off     off     on
bl0_sync0_dwDO  off     off     DETECT_ONLY
bl0_sync1_dw0   off     on      off
bl0_sync1_dw1   off     on      on
bl0_sync1_dwDO  off     on      DETECT_ONLY
bl1_sync0_dw0   on      off     off
bl1_sync0_dw1   on      off     on
bl1_sync0_dwDO  on      off     DETECT_ONLY
bl1_sync1_dw0   on      on      off
bl1_sync1_dw1   on      on      on
bl1_sync1_dwDO  on      on      DETECT_ONLY

For Postgres I used 12 configurations on the large server and on the small server I used 8 (skipped the 4 that use LZ4 for wal_compression:

name                    fsync   wal_compression full_page_writes
sync0_comp0_fpi0        off     off             off
sync0_comp0_fpi1        off     off             on
sync0_comp1_fpi0        off     pglz            off
sync0_comp1_fpi1        off     pglz            on
sync0_compLZ4_fpi0      off     lz4             off
sync0_compLZ4_fpi1      off     lz4             on
sync1_comp0_fpi0        on      off             off
sync1_comp0_fpi1        on      off             on
sync1_comp1_fpi0        on      pglz            off
sync1_comp1_fpi1        on      pglz            on
sync1_compLZ4_fpi0      on      lz4             off
sync1_compLZ4_fpi1      on      lz4             on

And a few notes:
  • the high durability configuration for MySQL is bl1_sync1_dw1 but that means there will be two fsyncs per commit (one for binlog, one for InnoDB redo)
  • the InnoDB doublewrite options (disabled, enable,d DETECT_ONLY) don't impact performance here. They will matter on workloads that are heavier on IO.
  • by fsync enabled I mean fsync (or fdatasync) is done for any logs that are enabled (Postgres WAL, InnoDB redo, MySQL binlog) and enabled for InnoDB means innodb_flush_log_at_trx_commit =1 while disabled for InnoDB means =2.
  • for Postgres I use wal_sync_method=fdatasync but below I often refer to that as fsync
  • for Postgres fsync enabled/disabled is the value for synchronous_commit

Hardware

I used two server types:

  • small
    • Beelink SER7 7840HS with Ryzen 7 7840HS, 8 AMD cores, AMD SMT disabled, 32G RAM, 1T m.2 SSD for OS, an NVMe device for the database using ext4 and Ubuntu 22.04.
  • medium
    • A c2d-highcpu-32 instance type on GCP (c2d high-CPU) with 32 vCPU, 64G RAM and SMT disabled so there are 16 cores. It uses Ubuntu 22.04 and storage is ext4 (data=writeback) using SW RAID 0 over 2 locally attached NVMe devices.
I haven't measured it on either the small or medium server but fdatasync latency might not be excellent on the medium server and definitely isn't excellent on the small server.

Benchmark

I used sysbench and my usage is explained here. But I only ran a few of the tests from it rather than the full 40+. The ones for which I report performance are insert, update-index and update-noindex. Note that update-index means that index maintenance is required and update-noindex means that it is not. 

For the small server the test is run with 1 thread, 1 table and 50M rows. For the medium server it is run with 12 threads, 8 tables and 10M rows per table.

Results: small server

The columns list the throughput for the insert (ins), update-index (upd-ix) and update-noindex (upd-nix).

When fsync is disabled

  • MySQL throughput with the binlog on is (57%, 83%, 54%) of throughput with the binlog off for (insert, update-index, update-noindex) using bl0_sync0_dw1. The result for update-index is the most interesting and that looks like the issue is the low QPS from the test without the binlog.
  • The various choices for the InnoDB doublewrite buffer (off, on, DETECT_ONLY) don't have an impact here. They will have an impact on workloads that do more write-back and I am happy that InnoDB now supports DETECT_ONLY.
  • MySQL with the binlog off gets (80%, 20%, 62%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_comp0_fpi1. The low value for update-index with MySQL supports the what I write above in the first bullet point.
  • MySQL with the binlog on gets (46%, 17%, 33%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_comp0_fpi1. Most MySQL primaries will run with the binlog enabled.
  • Using Postgres with full_page_writes=on (recommended) cuts throughput in half for the update tests and by ~30% for the insert test when wal_compression uses pglz. The impact is much less from lz4, so just use lz4.

When fsync is enabled

  • It is much harder to spot CPU regressions which I why most of my tests disable it
  • MySQL throughput with the binlog on is ~50% relative to the binlog off. This is expected because the bottleneck is fsync and there are 2 fsyncs/write with the binlog on vs 1 fsync/write with it off.
  • The various choices for the InnoDB doublewrite buffer (off, on, DETECT_ONLY) don't have an impact here.
  • MySQL with the binlog off gets ~27% of the throughput relative to Postgres and for MySQL with the binlog on that drops to ~14% relative to Postgres using bl0_sync0_dw1 vs sync0_comp0_fpi1. These differences are larger than I expect.
  • The impact from full_page_writes=on for Postgres is smaller here than it is above, but again the real issue is the CPU overhead from pglz and the solution is to use lz4.
--- without fsync

- no binlog for MySQL
ins     upd-ix  upd-nix dbms
20413    5662   19837   my8039 bl0_sync0_dw0
20458    5881   20271   my8039 bl0_sync0_dw1
20252    5790   19742   my8039 bl0_sync0_dwDO

- binlog for MySQL
ins     upd-ix  upd-nix dbms
11814    4935   11051   my8039 bl1_sync0_dw0
11614    4910   10992   my8039 bl1_sync0_dw1
11649    4896   11062   my8039 bl1_sync0_dwDO

- Postgres
ins     upd-ix  upd-nix dbms
25590   28835   32951   pg17rc1 sync0_comp0_fpi0
25471   28840   32837   pg17rc1 sync0_comp0_fpi1
26452   29196   32702   pg17rc1 sync0_comp1_fpi0
18537   14276   16033   pg17rc1 sync0_comp1_fpi1

--- with fsync

- no binlog for MySQL (1 fsync per commit: InnoDB)
ins     upd-ix  upd-nix dbms
  139     135     142   my8039 bl0_sync1_dw0
  140     134     142   my8039 bl0_sync1_dw1
  140     134     142   my8039 bl0_sync1_dwDO

- binlog for MySQL (2 fsync per commit: binlog + InnoDB)
ins     upd-ix  upd-nix dbms
   72      72      73   my8039 bl1_sync1_dw0
   73      71      71   my8039 bl1_sync1_dw1
   73      68      70   my8039 bl1_sync1_dwDO

- Postgres (1 fsync per commit: WAL)
ins     upd-ix  upd-nix dbms
  512     513     516   pg17rc1 sync1_comp0_fpi0
  505     507     517   pg17rc1 sync1_comp0_fpi1
  512     514     516   pg17rc1 sync1_comp1_fpi0
  438     422     387   pg17rc1 sync1_comp1_fpi1

Results: medium server

The columns list the throughput for the insert (ins), update-index (upd-ix) and update-noindex (upd-nix).

When fsync is disabled

  • MySQL throughput with the binlog on is (44%, 79%, 43%) of throughput with the binlog off for (insert, update-index, update-noindex) using bl0_sync0_dw1. The result for update-index is the most interesting.
  • The various choices for the InnoDB doublewrite buffer (off, on, DETECT_ONLY) don't have an impact here. They will have an impact on workloads that do more write-back and I am happy that InnoDB now supports DETECT_ONLY.
  • MySQL with the binlog off gets (96%, 16%, 70%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_comp0_fpi1. Once again the result for update-index with MySQL is odd.
  • MySQL with the binlog on gets (42%, 13%, 30%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_comp0_fpi1. Most MySQL primaries will run with the binlog enabled.
  • Using Postgres with full_page_writes=on (recommended) reduces throughput by ~5% when wal_compression uses pglz and the solution is lz4.

When fsync is enabled

  • MySQL throughput with the binlog on is (34%, 58%, 41%) of throughput with the binlog off for (insert, update-index, update-noindex) using bl0_sync0_dw1.  This is (mostly) expected because the bottleneck is fsync and there are 2 fsyncs/write with the binlog on vs 1 fsync/write with it off.
  • The various choices for the InnoDB doublewrite buffer (off, on, DETECT_ONLY) don't have an impact here.
  • MySQL with the binlog off gets (133%, 68%, 132%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_compLZ4_fpi1. 
  • MySQL with the binlog on gets (45%, 39%, 54%) of the throughput relative to Postgres using bl0_sync0_dw1 vs sync0_compLZ4_fpi1. Most MySQL primaries will run with the binlog enabled.
  • The impact from full_page_writes=on for Postgres is large for updates (reduces throughput by ~50%) and smaller for the insert test because the insert test loads in PK order.

--- without fsync

- no binlog for MySQL
ins     upd-ix  upd-nix dbms
117078   17620  104215  my8039 bl0_sync0_dw0
118038   19821  103625  my8039 bl0_sync0_dw1
117373   19582  103625  my8039 bl0_sync0_dwDO

- binlog for MySQL
ins     upd-ix  upd-nix dbms
 52602   15201   44727  my8039 bl1_sync0_dw0
 52193   15736   44372  my8039 bl1_sync0_dw1
 52456   16293   45195  my8039 bl1_sync0_dwDO

- Postgres
ins     upd-ix  upd-nix dbms
127864  138330  154650  pg17rc1 sync0_comp0_fpi0
123512  123825  148386  pg17rc1 sync0_comp0_fpi1
127456  139634  155127  pg17rc1 sync0_comp1_fpi0
119504  117795  136421  pg17rc1 sync0_comp1_fpi1
127885  139301  154869  pg17rc1 sync0_compLZ4_fpi0
125192  125532  149092  pg17rc1 sync0_compLZ4_fpi1

--- with fsync

- no binlog for MySQL (1 fsync per commit for InnoDB redo)
ins     upd-ix  upd-nix dbms
 20602    6824   15804  my8039 bl0_sync1_dw0
 20350    6640   15095  my8039 bl0_sync1_dw1
 20677    6836   16134  my8039 bl0_sync1_dwDO

- binlog for MySQL (2 fsync per commit, one for binlog, one for InnoDB redo)
ins     upd-ix  upd-nix dbms
  6972    3863    6238  my8039 bl1_sync1_dw0
  6963    3829    6183  my8039 bl1_sync1_dw1
  6922    3904    6266  my8039 bl1_sync1_dwDO

- Postgres (1 fsync per commit, for WAL)
ins     upd-ix  upd-nix dbms
 19754   24293   26250  pg17rc1 sync1_comp0_fpi0
 15089    9507   10963  pg17rc1 sync1_comp0_fpi1
 19732   24335   26280  pg17rc1 sync1_comp1_fpi0
 13031    8571    9204  pg17rc1 sync1_comp1_fpi1
 19698   24629   26163  pg17rc1 sync1_compLZ4_fpi0
 15309    9712   11420  pg17rc1 sync1_compLZ4_fpi1

No comments:

Post a Comment

RocksDB on a big server: LRU vs hyperclock, v2

This post show that RocksDB has gotten much faster over time for the read-heavy benchmarks that I use. I recently shared results from a lar...