Hosting en security

Hoeveel schaalbaarheid je product nodig heeft

Meet eerst waar de tijd heen gaat, pak dan je database aan, zet caching en queues gericht in en voeg pas daarna servers toe.

Een lange brug op pijlers die in het water weerspiegelen
Foto: Chris Briggs via Unsplash

De meeste webproducten hebben minder schaal nodig dan het team denkt. Wat je wel nodig hebt, zijn metingen en een database die op orde is. Caching of een queue zet je in waar de metingen dat aanwijzen, en meer servers of een ander platform komen daarna, als het dan nog nodig is.

Een applicatie die traag wordt bij meer gebruikers, heeft meestal een paar dure plekken: een query zonder index, een endpoint dat de database honderd keer aanroept, of werk dat in het request gebeurt terwijl het ook later kan. Die plekken los je op zonder één server toe te voegen.

Begin met meten

Zonder meting weet je niet wat traag is. Dan gok je, en een verkeerde gok bij schalen is duur werk.

Zorg dat je per endpoint de responstijd ziet. Kijk naar de p95 en p99, want het gemiddelde verbergt juist de trage requests waar gebruikers last van hebben. Zet de foutratio en de tijd per databasequery ernaast. Een APM-tool of tracing in je eigen stack is daarvoor genoeg.

Leg daarna vast wat snel genoeg is. Bijvoorbeeld: 95 procent van de API-requests binnen 300 milliseconden, bij de drukte die je over een jaar verwacht. Met zo'n doel zie je of je een probleem hebt, en ook wanneer je klaar bent.

Draai voor een grote campagne of lancering een loadtest tegen een omgeving die op productie lijkt. Dan zie je waar het als eerste breekt, en meestal is dat één onderdeel.

De database is meestal het knelpunt

Bij een trage webapplicatie zit het probleem meestal in de database zelf of in hoe de code hem aanroept. Denk aan ontbrekende indexen, N+1-queries uit de ORM, queries die een hele tabel ophalen om er in code tien rijen uit te filteren, of te veel open verbindingen.

In Postgres begin je met pg_stat_statements. Die extensie houdt per query bij hoe vaak hij draait en hoeveel tijd hij in totaal kost. Je zet hem aan via shared_preload_libraries (dat vraagt een herstart) en daarna een CREATE EXTENSION. Sorteer op totale tijd en je hebt je werklijst. Voor de duurste queries laat EXPLAIN ANALYZE zien wat de planner doet.

Mijn advies is om de vijf queries met de meeste totale tijd op te lossen voordat je iets anders doet. Dat levert vaak meer op dan een extra server.

Daarna kun je de database groter maken. Een zwaardere databaseserver is meestal goedkoper dan de ontwikkeltijd van een ingewikkelde oplossing, en een read replica helpt als je veel meer leest dan schrijft. Waarom Postgres voor de meeste producten genoeg is, lees je in Postgres als standaard database.

Cache wat duur is en weinig verandert

Caching komt na de database. Een cache verbergt een trage query, maar de query blijft traag. Elke cache brengt bovendien een nieuw soort bug mee: verouderde data.

Begin aan de buitenkant. Publieke pagina's en statische bestanden cache je met HTTP-headers en een CDN, en dat kost weinig code. Daarna komt een applicatiecache, bijvoorbeeld Redis, voor berekeningen die duur zijn en voor veel gebruikers hetzelfde.

Spreek per cache af hoe lang een waarde geldig is en wat hem ongeldig maakt. Zet de hit rate in je monitoring. Een cache met een lage hit rate kost geheugen en maakt debuggen lastiger, terwijl niemand er sneller van wordt.

Haal traag werk uit het request

Alles wat de gebruiker niet direct hoeft te zien, kan naar een queue: mails versturen, PDF's maken, afbeeldingen verwerken, webhooks naar externe partijen, imports. Het request zet een taak klaar en antwoordt meteen, en een worker doet daarna het werk.

Dat heeft twee voordelen. De queue vangt pieken op, zodat je workers ze in hun eigen tempo wegwerken. Daarnaast probeert de worker het later opnieuw als een externe dienst even niet reageert, in plaats van dat je gebruiker een foutmelding krijgt.

Om te beginnen heb je meestal geen aparte message broker nodig. Een tabel in Postgres werkt als queue, met SELECT ... FOR UPDATE SKIP LOCKED zodat meerdere workers elkaar niet blokkeren. De Postgres-documentatie noemt dat gebruik zelf. Zorg wel dat taken idempotent zijn, dat mislukte taken ergens terechtkomen waar iemand ze ziet, en dat je de lengte van de queue meet.

Bereid schalen voor zonder te schalen

Een paar dingen kosten nu weinig en zijn later duur om te veranderen. Die regel je vroeg:

  • Houd je app-servers stateless: sessies in de database of Redis, uploads in object storage, niets op de lokale schijf.
  • Zet configuratie in environment variables, zodat een tweede instance zonder handwerk kan opstarten.
  • Schrijf migraties die tabellen niet lang op slot zetten.
  • Laat achtergrondwerk via de queue lopen, ook als er nu maar één worker is.

Met die basis is horizontaal schalen een configuratiewijziging: op de dag dat het nodig is, zet je twee of drie instances achter een load balancer.

Wie microservices, Kubernetes, sharding of meerdere regio's invoert voordat het nodig is, maakt het systeem lastiger om te debuggen en te beheren. Voor een team van een paar developers kost dat vooral snelheid. Wanneer die stap wel zin heeft, staat in monoliet of microservices en in heb je Kubernetes nodig.

Wanneer je wel moet schalen

Deze signalen moet je serieus nemen. De database draait structureel op hoge CPU, ook nadat de duurste queries zijn opgelost. De queue loopt elke dag verder uit. De p95 stijgt mee met het aantal gebruikers terwijl er in de code niets is veranderd. Of er komt een piek aan die je kent, zoals een campagne of een drukke periode in het jaar.

Schaal dan in deze volgorde: meer app-servers, een grotere database, een read replica. Pas daarna komen dingen als het partitioneren van grote tabellen of één onderdeel apart zetten als eigen service. Begin steeds bij het onderdeel dat de meting aanwijst.

Zo pakten we het ook aan bij Zibber: eerst de bestaande techniek doorlichten en de knelpunten in prestaties en schaalbaarheid benoemen, en pas daarna een architectuur ontwerpen.

De volgorde op een rij

  1. Schrijf op wat snel genoeg is en welke piek je verwacht.
  2. Zet meting aan: responstijd per endpoint met p95, en pg_stat_statements in Postgres.
  3. Los de vijf duurste queries op.
  4. Verplaats traag werk naar een queue.
  5. Cache gericht, met een vaste geldigheidsduur en een meting van de hit rate.
  6. Voeg pas daarna servers toe, op de plek die de meting aanwijst.

Bronnen

Veelgestelde vragen

Wanneer moet je een webapplicatie gaan schalen?

Als metingen laten zien dat de database of de servers structureel tegen hun grens zitten, nadat je de duurste queries en ander verspild werk hebt opgelost. Zonder metingen is schalen gokken.

Wat is de eerste stap om een trage applicatie sneller te maken?

Meet welke endpoints en queries de meeste tijd kosten. In Postgres laat de extensie pg_stat_statements zien welke queries in totaal de meeste tijd gebruiken. Begin met die lijst.

Heb je microservices of Kubernetes nodig om te kunnen schalen?

Voor de meeste producten niet. Stateless app-servers achter een load balancer, een goed ingerichte database, caching en een queue komen ver. Microservices en Kubernetes maken een systeem lastiger te beheren en betalen zich pas terug bij grotere teams en duidelijke knelpunten.

Over de auteur

Erik Schoorl

Erik Schoorl

CTO en hoofdarchitect

Erik richtte samen met Bas ESBL op. Hij is CTO van Brainpower Group en eindverantwoordelijk voor architectuur en oplevering.

Meer van Erik
Contact
Bas Leijder HavenstroomMaarten ter VeldeBarry Willems

Vertel ons wie je zoekt.

Een kennismaking duurt een half uur, online of op ons kantoor in Amsterdam. In de chat antwoorden we meestal binnen een paar minuten.

Of bel +31 20 210 1978hello@brainpowergroup.eu

Bas, Maarten en BarryNu live · antwoord binnen een paar minuten

Na het versturen gaat de chat open.