# Azure + MySQL, MariaDB, PostgreSQL: prestaties vergeleken

> Een kijkje in de databaseprestaties van Azure, tijdens een reis langs MySQL, MariaDB en PostgreSQL. Ontdek welke database de race aanvoert.

- URL: https://www.allsubjectsmatter.nl/nl/mysql-mariadb-postgresql-performance-compared-in-azure/
- Language: nl
- Published: 2024-03-29
- Updated: 2024-03-30
- Author: Remco Loup (https://www.allsubjectsmatter.nl/nl/author/remco/)
- Tags: Tech
- Original (en, written by the author): https://www.allsubjectsmatter.nl/mysql-mariadb-postgresql-performance-compared-in-azure/
- Note: this text was translated with AI from the original.

### Inleiding

In deze post wil ik databaseperformance binnen Azure belichten, met een directe vergelijking tussen MySQL, MariaDB en PostgreSQL. Er is veel literatuur over het onderwerp, maar praktische tests die de performance in de praktijk meten, zijn schaars.

Veel discussies draaien om de verschillen tussen deze databases in plaats van om hun daadwerkelijke performance. Toen ik dit onderzoek begon, stonden niet de verschillen voorop, maar de zoektocht naar een performante database die een goed Azure-alternatief kon zijn voor onze MariaDB-monolith. Aanvankelijk dachten we dat de overstap van MariaDB naar MySQL een simpele vervanging was, met nauwelijks impact op de performance. Daar hadden we het mis!

> "There are many opinions about performance, But we needed facts"
We zagen een enorme performance-regressie bij elke query. Performance is cruciaal voor ons, en het was verwarrend om te zien dat MySQL zo ver achterbleef bij MariaDB. Toen we dit ontdekten, hebben we zelf een MariaDB-VM in Azure opgezet. Die was weer wel performant, maar bracht veel extra onderhoud en beheer met zich mee: uptime garanderen, hot standby, backups.

Daarom ging ik PostgreSQL onderzoeken, in de hoop op betere performance dan MySQL bood. Met deze introductie: de tests die we hebben gedaan, met een complexe query en een set kleine queries, om de performanceverschillen tussen de drie databases en hun implementaties binnen Azure te meten. We hebben ook Docker-tests toegevoegd om te kunnen vaststellen of de performanceverschillen aan de databases liggen of dat Azure de oorzaak is.

## Onze complexe query testen

Om de databases te beoordelen draaiden we een complexe query die data uit meerdere tabellen aggregeerde, een realistische workload voor ons systeem. Om vertrouwelijkheidsredenen hebben we de tabel- en kolomnamen in deze beschrijving aangepast. Deze query moest laten zien hoe elke database omgaat met data-aggregatie en complexe joins.

```sql
WITH AggregatedSkateData AS (
SELECT
    e.id AS eventID,
    e.theme_cid AS themeCID,
    s.id AS skateID,
    p.firstName || ' ' || p.lastName AS participantName,
    l.label AS locationLabel,
    s.priceOffered,
    s.inspectionCompleted,
    s.registrationDate,
    s.eventStartDate,
    org.name AS organizerName,
    'https://skateeventimages.net/' || im.path AS organizerImageURL,
    s.registrationCode,
    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 0 OR pa_last.id IS NULL THEN sp.id END) AS partsPending,
    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 1 THEN sp.id END) AS partsRequested,
    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 2 THEN sp.id END) AS partsConfirmed,
    COUNT(DISTINCT CASE WHEN pa_last.status_cid = 8 THEN sp.id END) AS partsDenied,
    MAX(CASE WHEN ti_unscanned.id IS NOT NULL THEN 1 ELSE 0 END) AS hasUnscannedTickets,
    s.isPublic
FROM
    skateEvents s
    INNER JOIN events e ON s.eventID = e.id
    INNER JOIN participants p ON p.id = e.participantID
    LEFT JOIN organizers org ON org.id = e.organizerID
    LEFT JOIN images im ON im.id = org.profileImageID
    LEFT JOIN locations l ON l.id = s.locationID
    LEFT JOIN skateParts sp ON sp.eventID = s.id
    LEFT JOIN partApprovals pa ON pa.id = sp.partApprovalID
    LEFT JOIN ticketsUnscanned ti_unscanned ON ti_unscanned.id = sp.ticketID AND ti_unscanned.scannedAt IS NULL
    LEFT JOIN partApprovalActions pa_last ON pa_last.id = (
        SELECT pa2.id
        FROM partApprovalActions pa2
        WHERE pa2.partID = pa.id
        ORDER BY pa2.id DESC
        LIMIT 1
    )
WHERE
    s.registrationDate IS NOT NULL
    AND e.cancelledAt IS NULL
    AND e.deletedAt IS NULL
    AND s.isArchived = false
    AND s.deletedAt IS NULL
GROUP BY
    e.id, e.theme_cid, s.id, p.id, p.firstName, p.lastName, org.id, im.id, l.id, l.label, s.priceOffered, s.inspectionCompleted, s.registrationDate, s.eventStartDate, org.name, im.path, s.registrationCode, s.isPublic
)
SELECT * FROM AggregatedSkateData
WHERE
    TO_CHAR(s.registrationDate, 'YYYY-MM') = '2024-03'
    OR partsDenied + partsRequested + partsPending > 0
ORDER BY s.registrationDate DESC
```

### Azure, complexe query

We hebben deze tests binnen dezelfde Azure-zone gedraaid voor containers en databases, om interzone-latency te vermijden. Hieronder de specificaties en resultaten, voor de transparantie. Opvallend: bij MariaDB en MySQL hadden de performancespecificaties nauwelijks invloed op de resultaten, mits we de eerste (burstable) tier vermeden. PostgreSQL was de uitzondering, met duidelijke verbeteringen bij de runtime van grote queries.

Met HeidiSQL via de Azure VPN hebben we onze beste resultaten van vijf runs bepaald.

**MySQL Flexible, Zone 2 (Standard_E4ds_v5, 450 iops)**  
Found rows: 130  
Duration for 1 query: 13,875 sec

**MariaDB VM, Zone 2 (Standard B4s v2 (4 vcpus, 16 GiB memory))**  
Found rows: 130  
Duration for 1 query: 10,625 sec  
23,4% beter dan MySQL

**Postgres Flexible, Zone 3 (Standard_E4ds_v50 p10, 500 iops)**  
Found rows: 130  
Duration for 1 query: 3,953 sec  
71,5% beter dan MySQL

### Lokale Docker, complexe query

De hardware waarop we lokaal hebben getest:

12th Gen Intel(R) Core(TM) i9-12900H, 2900 Mhz, 14 Core(s), 20 Logical Processor(s)  
Installed Physical Memory (RAM) 32,0 GB

**MySQL 8.3 Docker**  
Found rows: 130  
Duration for 1 query: 12,437 sec

**MariaDB 11.4 Docker**  
Found rows: 130  
Duration for 1 query: 9,484 sec

**Postgres 16.2 Docker**  
Found rows: 130  
Duration for 1 query: 3,969 sec

Daarmee zijn de tests met de complexe query afgerond. Geweldig om te zien dat Postgres hier zo goed presteert! Voor onze overstap naar Azure draaiden we op MariaDB. Het was behoorlijk vervelend om zo'n slechte performance in MySQL te zien zodra we met de nieuwe setup gingen testen.

## Onze set kleine queries testen

![](https://www.allsubjectsmatter.nl/nl/mysql-mariadb-postgresql-performance-compared-in-azure/images/image-2.png) 

De volgende test vuurt meer dan 20.000 transacties per seconde af op elke database, waarmee we een feed in JSON bouwen die een bepaalde tijd nodig heeft om af te ronden. Met veel queries belasten we de database beter, wat ons een gemiddelde runtime per database geeft.
Er zit ook wat PHP-logica in, want dit is een PHP-script dat we draaien om de feed te genereren. Maar dat draait altijd op dezelfde hardware en hetzelfde docker-image, dus het verschil in runtime komt van de database.
Hiervoor hebben we onze containers naar dezelfde zone verplaatst als waar de database draait, om ook mogelijke problemen met zone-latency uit te sluiten.

We draaien dit script binnen ons Laravel-framework. Onze PHP draait tegen [Swoole](https://openswoole.com/) voor de best mogelijke performance binnen PHP 8.

### Azure, kleine queries

In deze test komen ook aspecten van containerperformance kijken.
We draaien onze containers en database weer in dezelfde zone in Azure (West-Europa). Binnen Azure draaien we met een stuk minder performance dan we lokaal in onze docker-omgeving tot onze beschikking hebben.

Daarom kunnen we deze resultaten alleen gebruiken om de verschillen binnen docker en binnen Azure zelf te vergelijken, niet tussen die twee. Die met elkaar vergelijken zegt niets door dit hardwareverschil. We willen alleen het verschil in runtime tussen de databases zien, en Docker is de baseline om te checken of de verschillen overeenkomen met wat we in Azure zien.

Onze node size in Azure is een standard_b2s, 2 vCPU's met 4 GiB RAM.

**API, Zone 2 -> MariaDB VM, Zone 2**  
24,59s runtime (spot nodes)

**API, Zone 2 -> MySQL Flexible, Zone 2**  
27,87s runtime (spot nodes)

**API, Zone 3 -> Postgres Flexible, Zone 3**  
24,92s runtime (spot nodes)

### Lokale Docker, kleine queries

Voor de kleine query-tests binnen lokale Docker gebruiken we een systeem met een 12th Gen Intel(R) Core(TM) i9-12900H CPU, draaiend op 2900 MHz met 14 cores en 20 logical processors, aangevuld met 32 GB RAM.

Deze Docker images zijn zo geconfigureerd dat ze memory-bound zijn, wat betekent dat de prestaties vooral afhankelijk waren van het beschikbare geheugen en niet beperkt werden door de CPU. De CPU-instellingen stonden op onbeperkte prestaties, zodat Docker zoveel CPU-bronnen kon gebruiken als nodig.

Voor elke testrun tegen een database herstartte ik de Docker container om de verbinding te verversen. Na de herstart hield ik een pauze van 30 seconden aan om er zeker van te zijn dat de container volledig gestabiliseerd en idle was. Die pauze is belangrijk voor consistentere testomstandigheden. Zodra de container klaar was, startte ik het feed generator script om de feed op te bouwen, om zo te beoordelen hoe goed onze verschillende database-setups kleine queries aankunnen onder deze gecontroleerde omstandigheden.

**API -> MariaDB 11.4 Docker**  
22.88s runtime

**API -> MySQL 8.3 Docker**  
23.74s runtime

**API -> Postgres 16.2 Docker**  
20.77s runtime

## MariaDB query cache

In ons eerdere gebruik van MariaDB binnen onze monolithische setup maakten we veelvuldig gebruik van de query cache. Deze functionaliteit speelde een cruciale rol bij het leveren van prestaties voor herhaalde queries. Die efficiëntie houdt stand totdat de cache herbouwd moet worden, meestal getriggerd door updates of inserts op de tabellen van de gecachte query. We doen een tweede run op onze complexe query met de query cache ingeschakeld.

**Local MariaDB Docker**  
Found rows: 130  
Duration for 1 query: 0,000 sec

De prestatiewinst van de query cache is echter niet overal even groot. Bij bijvoorbeeld gemiddeld 20.000 transacties per seconde met kleine queries zakte de runtime enigszins in. Deze variatie laat zien dat de query cache niet altijd hetzelfde niveau van optimalisatie oplevert, zeker onder hoge belasting. Maar daar later meer over.

**API -> Local MariaDB Docker (query cache enabled)**  
17.30s runtime  

**API, Zone 2 -> MariaDB VM, Zone 2 (query cache enabled)**  
22.72s runtime (spot nodes)

De query cache in MariaDB werkt door de result set van SELECT queries op te slaan, samen met de querytekst. Wanneer een identieke query opnieuw wordt uitgevoerd (met dezelfde tekst en onder dezelfde database-userrechten), kan MariaDB de query-uitvoerfase overslaan en het resultaat direct uit de cache halen. Dit kan de uitvoertijd van vaak gedraaide queries die geen data wijzigen drastisch verlagen.

Wees voorzichtig met het gebruik van de query cache van MariaDB. Ondanks de voordelen is de query cache deprecated en standaard uitgezet in nieuwere versies van MariaDB 10.1.7 (en in MySQL vanaf versie 5.7.20), vanwege zorgen over de impact op prestaties in omgevingen met veel gelijktijdige transacties. Voor specifieke scenario's, met name met een overwegend read-workload, kan de query cache nog steeds aanzienlijk betere prestaties opleveren. In MySQL 8 en hoger is hij volledig verwijderd.

## Observatie over de Flexible Servers van Azure

Deze tests laten zien dat de prestaties van databases op de flexible servers van Azure aanzienlijk zijn verbeterd en dicht bij de prestaties van lokale Docker-omgevingen liggen. Dat is een flinke verbetering ten opzichte van de eerdere Azure database single servers, die doorgaans minder prestaties leverden door een vorm van overhead die voortkwam uit de manier waarop ze in de Azure-cloud draaiden.

## Azure latency-anomalie

Als bonus hebben we nog wat extra tests gedaan om de interzone-latency te meten bij het verbinden van Zone 1 of 2 met een PostgreSQL-database in Zone 3 binnen Azure.

**API, Zone 2 -> Azure PostgreSQL Flexible, Zone 3**  
De runtime was 44.23 seconden.

Interessant: de runtime verdubbelde opnieuw, een fenomeen dat we uitsluitend zien bij communicatie tussen Zone 1-2 en Zone 3, of andersom. Dit patroon suggereert dat elke container die een databaseverbinding nodig heeft onder deze omstandigheden aanzienlijke latency ondervindt, terwijl de latency vrijwel verwaarloosbaar is bij interactie tussen Zone 1 en Zone 2.

Voor wie het gemist heeft: ik heb eerder een latency-anomalie in Zone 3 besproken. De gangbare wijsheid is dat je de databaseverbinding binnen dezelfde zone houdt om latency te minimaliseren. Onze tests laten echter zien dat de latency-impact minimaal is tussen Zone 1 en 2, maar aanzienlijk wordt zodra je van Zone 1 of 2 naar Zone 3 gaat.

Deze tests bevestigen opnieuw dat het latency-probleem niet exclusief is voor MySQL Flexible services, maar ook PostgreSQL Flexible services binnen Azure raakt, wat wijst op een breder patroon in het database-aanbod van Azure.

[Latency Anomaly: An attempt to untangle Azure Performance issues in Western Europe](https://www.allsubjectsmatter.nl/azure-western-europe-zone-3-latency-issue-exploration/): Unveiling a significant Azure performance issue: Zone 3’s unexpected latency anomaly in the Western Europe region challenges cloud deployment strategies

## Conclusie

Vanuit prestatieoogpunt is het duidelijk dat de keuze voor de juiste database cruciaal is en moet aansluiten bij de specifieke snelheids- en efficiëntiebehoeften van je app. De resultaten laten zien dat MySQL vaak trager is. MariaDB presteert goed en heeft een handige query cache, maar Azure ondersteunt MariaDB niet meer. Daarmee is PostgreSQL de meest logische keuze voor prestatiegerichte gebruikers in Azure, want het blinkt uit in het verwerken van zowel complexe queries als grote aantallen simpele queries, zoals de tests hierboven laten zien.

Hopelijk helpt deze analyse bij het kiezen van de juiste databasetechnologie en onderstreept ze hoe belangrijk zorgvuldige planning is voor het verbeteren van je app-prestaties. Voor wie Azure gebruikt en topprestaties zoekt, is PostgreSQL in mijn testresultaten de beste optie.

Er zijn genoeg andere redenen om je database te kiezen. Maar in deze post wilde ik me alleen richten op performance. Dat was onze grootste zorg, want we zijn bezig met de overstap van een monolithische server naar Azure. Bij een database-switch komt nog meer kijken. Het voordeel hier is dat alle drie de databases zich redelijk makkelijk laten integreren, welke je ook kiest.

Postgres is hier het best compatibel dankzij zijn strikte routines. De gecompliceerde query hierboven is geschreven voor Postgres en draait in MariaDB zonder één wijziging. Voor MySQL hoef je alleen de TO_CHAR in de laatste regel te vervangen door DATE_FORMAT.
