I en rymlig kontorslobby i varmt morgonljus går två personer fritt genom en öppen passage medan en rad medarbetare köar en och en genom ett smalt vändkors.
Guider

Sajten hänger bara när du är inloggad, och det är inte webbhotellet du tror

Känns det här igen?

Du loggar in i WordPress-adminet och varje sidladdning tar flera sekunder att bli klar. Öppnar du samma sida i ett inkognitofönster, utan att vara inloggad, laddar den nästan direkt. Driftövervakningen och serverns resursgrafer visar grönt hela tiden. Ändå upplever du, och kanske några kollegor, att sajten är seg, medan besökare som bara läser artiklar och produktsidor inte märker någonting alls.

Det är ett mönster som går igen hos den som administrerar en sajt dagligen, och det har några återkommande drag.

  • Adminpanelen (wp-admin) är trög, men den publika, utloggade delen av sajten laddar snabbt.
  • Driftövervakning och serverns resursgrafer visar normala eller låga värden, trots segheten du känner av.
  • Öppnar du flera flikar samtidigt, eller utlöser sajten flera bakgrundsanrop parallellt, till exempel WordPress admin-ajax-heartbeat, blir det ännu långsammare.
  • I webbläsarens nätverksflik (DevTools) ligger anrop och väntar med statusen "pending" efter varandra, i stället för att köras samtidigt.

Mönstret dyker typiskt upp hos redaktörer och administratörer som jobbar mycket i adminet, och hos sajter byggda kring inloggning, medlemssajter, kursplattformar (LMS), bokningssystem, forum, samt äldre skräddarsydda PHP-butiker som hanterar varukorg och inloggning i egen kod. Rena besökare, statiskt cachade sidor och den som aldrig loggar in ser sällan något av detta.

Du märker detAndra märker det inte
Inloggade redaktörer och administratörer i wp-adminBesökare som läser sidor utan att logga in
Användare av medlems-, kurs- (LMS), boknings- och forumfunktioner som kräver inloggningStatiskt cachade sidor som byggs en gång och serveras till alla
Den som har flera flikar eller parallella AJAX-anrop öppna samtidigtDriftövervakning som mäter serverns totala svarstid, inte enskilda sessioner

Varför det händer: sessionslås

Förklaringen handlar oftast om en mekanism i PHP:s sessionshantering som kallas sessionslås (session locking), och den förklarar mönstret ovan nästan exakt.

PHP:s inbyggda sessionshantering bygger på ett sessions-id, PHPSESSID, som pekar mot en lagringsplats på servern. Standardinställningen för session.save_handler är files, enligt PHP:s officiella dokumentation, det vill säga att sessionsdatan sparas som en fil på disk. Så länge ett skript har öppnat sessionen med session_start() lägger PHP ett exklusivt lås på den filen, för hela anropet, inte bara under själva läsningen eller skrivningen.

PHP:s dokumentation för session_write_close() är ovanligt konkret på den här punkten. Sessionsdata låses för att förhindra samtidiga skrivningar, och bara ett skript får arbeta med en given session åt gången. Som illustration tar dokumentationen upp ramar (framesets), där varje ram laddas i tur och ordning i stället för parallellt, just på grund av den här låsningen. Samma princip gäller flikar, AJAX-anrop och REST-anrop som delar samma PHPSESSID.

Det som utlöser flera samtidiga anrop mot en och samma session är vardagligt. Flera öppna flikar mot samma inloggade konto, WordPress egen admin-ajax-heartbeat som pollar i bakgrunden, parallella REST- eller AJAX-anrop från ett tema eller plugin, eller helt enkelt ett enda långsamt anrop som håller kön väntande medan alla efterföljande anrop mot samma session får stå i kö bakom det. Låset släpps först när skriptet anropar session_write_close() eller när skriptet är helt klart.

Vad är ett lås, konkret?

Tänk på det som en enda kassa som bara betjänar en specifik kund i taget. Andra kunder i butiken handlar som vanligt hos andra kassor, det är bara den kunden som får vänta i kö för sina egna ärenden. Sessionslåset fungerar likadant. Det är inte servern som är upptagen, det är just din session, ditt PHPSESSID, som har en kö av väntande anrop bakom sig.

Mutex, inte resurstak, så ser du skillnaden

Håll isär två saker här, annars blir slutsatserna fel. Ett sessionslås är vad som på engelska brukar kallas en mutex, en mekanism som garanterar exklusiv åtkomst till en specifik resurs, i det här fallet en enda sessionsfil. Det är alltså en kö kopplad till en nyckel, din session, och har inget att göra med hur mycket total kapacitet servern har. Ett resurstak däremot handlar om hur många samtidiga besökare eller processer hela servern klarar av innan den börjar bli mättad.

Konsekvensen är att symptomet skalar med dina egna parallella anrop, inte med hur många besökare som är inne på sajten totalt. Öppnar du fem flikar mot adminet samtidigt märker du mer kö. Har sajten tusen samtidiga anonyma besökare som aldrig loggar in, påverkar det inte din session över huvud taget, eftersom de besökarna inte delar ditt PHPSESSID.

Tre tecken brukar avslöja att det är just det här som pågår. Driften säger grönt medan du själv upplever seghet, eftersom serverns totala belastning kan vara låg samtidigt som din enskilda session har en kö. Det är bara du, eller bara inloggade användare, som märker det, inte sajten i stort. Och i webbläsarens nätverksflik ser du anrop som körs i sekvens, ett efter ett, i stället för parallellt, trots att koden borde skicka dem samtidigt.

Mer CPU och mer RAM köper mer resurstak, fler samtidiga besökare servern klarar totalt. Det köper inte en kortare kö på en enskild nyckel. Om flaskhalsen är ett lås på en session, spelar det ingen roll hur mycket kraftfullare servern blir, kön för just den sessionen finns kvar ändå.

Vad det ärVad som hjälperVad som INTE hjälper
Mutex (sessionslås)En kö för en specifik nyckel, din session, oavsett total kapacitetFärre samtidiga anrop mot samma session, snabbare enskilda anrop, tidigare stängning av sessionenMer CPU, mer RAM, snabbare disk, fler processer
ResurstakEn gräns för hur många samtidiga besökare och processer servern klarar totaltMer CPU, mer RAM, fler PHP-processer, bättre disk-IOAtt stänga sessionen tidigare, om flaskhalsen faktiskt är total kapacitet

Vem drabbas egentligen, och vem inte

Här behövs en ärlig avgränsning, för den skiljer sig från den intuitiva misstanken om att "WordPress" i allmänhet skulle vara boven. WordPress kärna autentiserar via cookies (wp_set_auth_cookie), inte via PHP:s sessionsfunktion, så en ren WordPress-sajt utan session-startande tillägg drabbas normalt inte av det här. Det finns helt enkelt ingen session_start() som låser något i WP-kärnans inloggningsflöde.

Motsvarande gäller WooCommerce. Sedan version 2.5 hanterar WooCommerce-kärnan sina sessioner (varukorg, kunddata under köpflödet) i en egen databastabell, {prefix}_woocommerce_sessions, inte i PHP:s inbyggda $_SESSION. En WooCommerce-butik som bara använder kärnans egen sessionshantering drabbas alltså inte av filsessionslåset vi beskrev ovan, oavsett hur många kunder som samtidigt lägger varor i varukorgen.

Det som faktiskt utlöser problemet är plugins eller egen kod som uttryckligen anropar session_start(). Det gäller ofta medlems-, kurs- (LMS), boknings- och forumplugins, äldre skräddarsydda PHP-butikslösningar som lagrar inloggning och varukorg direkt i $_SESSION, samt egen kod som en utvecklare har lagt till för att spara tillfällig data mellan sidladdningar. Ett enkelt diagnostiskt knep är att söka igenom koden, eller pluginmapparna, efter strängen session_start(. Hittar du den i ett aktivt plugin, är det en stark kandidat till varför sajten känns seg just för inloggade användare.

Drabbas normaltDrabbas normalt INTE
Medlemssajter, LMS/kursplattformar, bokningssystem och forum vars plugin kör session_start()Ren WordPress-kärna utan sessionsstartande tillägg (cookiebaserad inloggning)
Äldre skräddarsydda PHP-butikslösningar som lagrar varukorg eller inloggning i $_SESSIONWooCommerce-kärnans egen sessionshantering, lagrad i databastabell
Sajter med egen kod som explicit anropar session_start() för tillfällig dataStatiskt cachade sidor och besökare som aldrig loggar in

Två olika Redis, blanda inte ihop dem

Ett vanligt missförstånd är att tro att paketet "har Redis" automatiskt löser det här. Det gör det inte nödvändigtvis, för det finns två helt olika saker som båda kallas Redis i webbhotellssammanhang. Redis som objektcache avlastar databasen genom att cacha frågor och resultat, men den har ingenting att göra med var PHP lagrar sessionsdata. Redis som sessionslager är en separat konfiguration, där session.save_handler ställs in till redis i stället för files.

Skärmdump av Redis Object Cache-pluginet på WordPress.org
Redis Object Cache-pluginet gäller just objektcachen, inte sessionslagret som beskrivs här. Skärmdump från WordPress.org.

Att ett webbhotell erbjuder objektcache med Redis säger alltså ingenting om huruvida sessionerna lagras i filer eller i Redis. Vill du faktiskt byta sessionslager är det den inställningen, inte objektcachen, som avgör om filsessionslåset försvinner.

Fixa det, i rätt ordning

Det finns tre saker att pröva, och de bygger på varandra. Börja med den enklaste och mest träffsäkra, gå vidare bara om den inte räcker.

Stäng sessionen tidigt: session_write_close()

Den mest direkta åtgärden är att anropa session_write_close() så snart sessionsdatan är klar att sparas, före lång rendering eller externa API-anrop som ändå inte behöver sessionen öppen. Då släpps låset direkt, i stället för att hållas kvar tills hela skriptet är färdigt, och efterföljande anrop mot samma session behöver inte köa lika länge.

En vanlig fallgrop är output_buffering, som är vanligt förekommande i produktionskonfigurationer och därför värt att kontrollera innan du förlitar dig på den här åtgärden. Är output buffering aktivt utan att bufferten töms, kan session_write_close() bli verkningslöst, eftersom sessionen tekniskt sett stängs men resten av utdatan ändå hålls kvar och fördröjer att anropet faktiskt avslutas.

Byt sessionslager, med nyansen

Nästa steg är att byta ut files mot en annan lagringsmotor. De vanliga alternativen, phpredis och Memcached, hanterar låsning helt olika som standard, vilket avgör om bytet faktiskt löser problemet du har.

phpredis, PHP-tillägget för Redis, har låsning avstängd som standard, enligt projektets egen dokumentation om sessionslåsning, där inställningen aktiveras separat via redis.session.locking_enabled. Det tar bort kön vi beskrivit ovan, men det för med sig en avvägning värd att förstå. Om PHP:s egen dokumentation är tydlig med att lås finns just för att förhindra att flera skript skriver till samma session samtidigt, följer det logiskt att en olåst session kan drabbas av att två samtidiga anrop skriver över varandras data. Problemet byter alltså karaktär, från seghet till en risk för inkonsekvent sessionsdata vid verkligt samtidiga skrivningar.

Memcached fungerar tvärtom. Enligt PHP:s dokumentation för memcached.sess_locking är låsning på som standard. Byter du bara lagringsmotor till Memcached, utan att själv ändra den inställningen, har du kvar exakt samma kö som med filhanteraren, fast i ett annat lagringssystem.

Låsning som standard?Löser det här problemet?
phpredis (Redis)Nej, avstängd som standardJa, men med avvägning kring samtidiga skrivningar
MemcachedJa, på som standardNej, inte utan att aktivt ändra sess_locking

Vilken lagringsmotor som är tillgänglig, och hur PHP-konfigurationen för save_handler ser ut, avgörs i grunden av vad webbhotellsmiljön tillåter dig att ändra.

På delad hosting: fråga, byt inte i blindo

De flesta delade webbhotellspaket ger dig inte kontroll över session.save_handler på egen hand, det är en serverkonfiguration som ligger utanför vad ett vanligt kontrollpanelskonto rår över. Innan du byter till en dedikerad eller molnbaserad lösning enbart för det här, är det värt att fråga supporten rakt ut. Två frågor räcker långt, oavsett vilket webbhotell du använder. Erbjuds Redis som sessionslager, inte bara objektcache? Och får du som kund själv ändra save_handler om du behöver det?

Varför "uppgradera paketet" inte hjälper

Den vanliga reflexen när en sajt känns seg är att uppgradera till ett större paket, mer CPU, mer RAM, fler resurser. Det löser ett resurstak. Det löser inte en kö på en enskild nyckel, eftersom en mutex inte bryr sig om hur mycket kapacitet servern har totalt, bara om hur många anrop som väntar på just den sessionen.

Det finns förstås tillfällen då det faktiskt är kapaciteten som är problemet, och då är bilden en annan. Handlar det om när det faktiskt är en dold resursgräns, är kännetecknet att många samtidiga besökare, inte samma inloggade person, gör sajten seg för alla samtidigt. Detsamma gäller när trafiktoppar kraschar WooCommerce, där det är volymen av samtidiga köpare, inte ett enskilt lås, som får servern att vika sig.

Innan du lägger pengar på ett större paket, gå igenom stegen i den här ordningen.

  1. Bekräfta mönstret. Är det bara inloggade användare, eller bara du, som upplever segheten, medan utloggade besökare inte märker något?
  2. Sök i koden och pluginlistan efter session_start( för att hitta vad som faktiskt startar en PHP-session.
  3. Testa att stänga sessionen tidigt med session_write_close(), och kontrollera samtidigt att output buffering inte gör åtgärden verkningslös.
  4. Om det inte räcker, undersök om webbhotellsmiljön tillåter byte av sessionslager, och fråga supporten konkret om Redis som sessionslager, inte bara objektcache.
  5. Uppgradera paketets kapacitet bara om symptomen faktiskt matchar ett resurstak, alltså att många olika samtidiga besökare, inte en enskild inloggad session, gör sajten seg för alla.

Är det i stället så att segheten drabbar samtliga besökare, inloggade som utloggade, ligger förklaringen sannolikt någon annanstans, och då är hur du optimerar WordPress laddtid en mer relevant utgångspunkt än sessionslåsning.