Lös N+1-problem. 12 mönster för Postgres och ORM-tuning.
Nar ett system borjar kannas trogt ar den forsta reflexen ofta att kopa en storre server. Ibland hjalper det, men oftast doljer det bara problemet ett tag till. I de allra flesta fall jag har varit inne i sitter tradigheten i nagra fa databasanrop som gor mycket mer arbete an de borde. Den goda nyheten ar att de gar att hitta och att fixa utan att man behover gora om hela arkitekturen. Har gar jag igenom hur jag tar mig dit.
Borja med att hitta de langsamma anropen
Man kan inte optimera det man inte ser. Det forsta jag gor ar att leta upp vilka anrop som faktiskt ar langsamma och hur ofta de kors. De flesta databaser har en logg for langsamma queries som man kan sla pa, och de flesta ramverk kan logga hur lang tid varje anrop tar. Malet ar inte att hitta det enskilt langsammaste anropet, utan det som kostar mest totalt nar man ganger tid med antal korningar.
Det ar har man ofta blir overraskad. En query som tar tjugo millisekunder later snabb, men om den kors tusen ganger per sidladdning ar den ett storre problem an en rapport som tar tre sekunder men kors en gang per dygn.
Las planen med EXPLAIN
Nar jag har en misstankt query kor jag den genom EXPLAIN, eller EXPLAIN ANALYZE om databasen stoder det. Det visar hur databasen tanker utfora anropet: vilka tabeller den laser, i vilken ordning och om den anvander index eller laser igenom hela tabellen. Det dar sista, en sekventiell genomlasning av en stor tabell, ar nastan alltid det jag letar efter.
Det tar lite ovning att lasa en query-plan, men man behover inte forsta varenda rad. Det jag fokuserar pa ar var de stora kostnaderna ligger och om databasen tvingas titta pa langt fler rader an den till slut returnerar. Nar den siffran ar skev finns det nastan alltid nagot att vinna.
Indexera ratt, inte allt
Index ar det enskilt mest kraftfulla verktyget for att snabba upp lasningar. Ett index later databasen hitta de rader den behover utan att lasa igenom hela tabellen. Men det ar inte gratis. Varje index maste underhallas vid varje skrivning, sa att indexera allt gor skrivningarna langsammare och slosar lagringsutrymme.
- Indexera de kolumner du faktiskt filtrerar och sorterar pa, inte alla for sakerhets skull.
- Sammansatta index over flera kolumner kan tacka vanliga anrop helt, men ordningen pa kolumnerna spelar roll.
- Rensa bort index som inte langre anvands, de kostar utan att ge nagot.
N+1 ar den vanligaste fallan
Den absolut vanligaste prestandaboven jag ser kommer inte fran en enskild langsam query, utan fran ett monster som kallas N plus ett. Det uppstar nar koden forst hamtar en lista med rader och sedan gor ett separat anrop per rad for att hamta relaterad data. Hamtar du femtio ordrar och sedan kundinformation for var och en blir det femtioett anrop dar ett eller tva hade rackt.
Problemet ar lurigt eftersom varje enskilt anrop ar snabbt. Det syns inte i en logg over langsamma queries, men summan blir tung. Losningen ar oftast att hamta den relaterade datan i ett anrop, antingen med en join eller genom att samla ihop alla id:n och hamta allt pa en gang. De flesta ramverk har inbyggt stod for det nar man val vet vad man letar efter.
Hamta bara det du behover
En annan vanlig vana ar att skriva anrop som hamtar allt och sedan kasta det mesta. Att lasa alla kolumner nar man bara ska visa namn och datum, eller att hamta tusen rader for att visa tio, kostar bade i databasen och i natverket. Jag brukar ga igenom de tyngsta anropen och fraga om all den datan verkligen anvands.
Sidindelning ar en del av samma sak. Listor vaxer over tid, och ett anrop som var snabbt med hundra rader blir plotsligt segt nar tabellen har vuxit till hundratusen. Att begransa antalet rader per anrop fran borjan sparar mycket framtida huvudvark.
Mat aven nar tabellerna vaxer
En sak som ofta overraskar team ar att ett anrop som var snabbt vid lanseringen blir segt nar tabellen vuxit. En query utan index kan vara helt acceptabel pa tusen rader och katastrofal pa en miljon, eftersom arbetet vaxer i takt med datan. Det jag letar efter ar darfor inte bara vad som ar langsamt idag, utan vad som kommer att bli langsamt nar volymen okar.
Mitt rad ar att testa de tyngsta anropen mot en datamangd som liknar den ni faktiskt har i produktion, inte mot en nastan tom testdatabas. Det ar forst da query-planen beter sig som i verkligheten och du ser om databasen faller tillbaka pa att lasa hela tabeller. Att fanga det innan det blir ett problem ar mycket billigare an att slacka brander nar kunderna redan markt av tradigheten.
Nar databasen inte ar problemet
Ibland har jag gjort allt detta och anropen ar fortfarande snabba, men sidan kanns anda trog. Da ar det dags att titta utanfor databasen. Kanske gors samma anrop om och om igen for data som sallan andras, och da ar ett cachelager ofta ratt svar i stallet for mer databasarbete. Jag skriver mer om det i artikeln om caching, men poangen ar att man ska atgarda ratt sak. Konkreta exempel pa sadana har genomgangar finns i var casebook.
Relaterat
- Web Performance Audit: 15 åtgärder som ger 90+ Lighthouse-poäng
- API-svarstid under 100 ms: Caching-strategier med Redis och CDN
- Teknisk skuld-inventering: Metrik, prioritering och 90-dagarsplan
Vill du ta det vidare?
Om ditt system kanns trogt och du misstanker att databasen ar boven kan vi ta ett forutsattningslost samtal dar jag tittar pa dina tyngsta anrop. Las garna mer om hur jag jobbar med optimering eller titta i var casebook for konkreta exempel.
“En query som tar tjugo millisekunder later snabb tills du inser att den kors tusen ganger per sidladdning.”
- Simon Axelsson
Vanliga frågor
- Hur vet jag vilket anrop jag ska borja med?
- Multiplicera tiden per anrop med hur ofta det kors. Det som kostar mest totalt ar ofta en snabb query som kors valdigt manga ganger, inte en enstaka langsam rapport.
- Loser fler index alltid problemet?
- Nej. Index snabbar upp lasningar men gor skrivningar langsammare och tar plats. Indexera de kolumner du faktiskt filtrerar och sorterar pa, och rensa bort index som inte anvands.
- Vad ar ett N plus ett-problem?
- Det ar nar koden hamtar en lista och sedan gor ett separat anrop per rad for relaterad data. Varje anrop ar snabbt, men summan blir tung. Losningen ar att hamta den relaterade datan i ett anrop.
