En sedelärande historia om databaskonsistens i distribuerade system

Det började som vilket migreringsprojekt som helst…

För ett tag sedan stötte jag och en kollega på en intressant bugg hos en av våra kunder, en större nordisk streamingleverantör. Efter ett par veckors förberedelser hade vi äntligen migrerat deras webbaserade innehållshanteringssystem från gamla fysiska servrar som snart skulle pensioneras till ny, molnbaserad hosting i Amazon Web Services (AWS). Migreringen hade gått smärtfritt och vi hade börjat omdirigera användare från de gamla servrarna till våra nya, fina Docker-containers i molnet utan problem.

Allt verkade vara frid och fröjd. Eller?

    Problemen börjar dyka upp

    Efter någon dag började buggrapporterna trilla in. Användarna hade problem att schemalägga nytt innehåll i tjänsten – när de försökte spara nya scheman, eller uppdatera befintliga diton, såg ändringarna ut att misslyckas. Antingen hände ingenting alls, eller så fick de ett felmeddelande om att det inte gick att spara ändringarna. Men när man laddade om sidan visade det sig att ändringarna faktiskt hade gått igenom, trots eventuella felmeddelanden.

    Problemet dök bara upp på den nya AWS-installationen, inte på de gamla fysiska servrarna, trots att vi körde nästan exakt samma kod på bägge installationerna och inte hade gjort några ändringar i schemahanteringen på flera månader. För att ytterligare komplicera felsökningen kunde vi inte heller reproducera buggen lokalt.

    Vi lusläste relevanta delar av koden ett par gånger extra utan att hitta något suspekt, och började jämföra versioner på tredjepartspaket mellan installationerna, men det gav inte heller några ledtrådar. Både de gamla servrarna och AWS-installationen körde dessutom mot samma databasinstans, så problemet kunde inte bero på skillnader mellan databasversioner. Frustrationen tilltog allt eftersom vi undersökte fler och fler spår utan att komma vidare.

    Genombrottet

    Till sist slår det oss: databasinstallationen är ett PostgreSQL-kluster i AWS, och kör i replikeringsläge med en huvudnod och en read-only-replika. Vårt databasbibliotek är konfigurerat att skriva till huvudnoden men automatiskt skicka alla läsförfrågningar till replikan för att minska lasten på huvudnoden. Koden för att uppdatera ett schema skriver först det nya schemat till databasen för att därefter läsa ut det uppdaterade objektet och skicka det till klienten. Kunde det finnas ett race condition här?

    Mycket riktigt! På den gamla installationen var roundtriptiden mellan de fysiska webbservrarna och databasklustret i AWS tillräckligt lång för att uppdateringarna skulle hinna propageras till replikan innan vi försökte läsa dem. Men på den nya installationen där webbapplikationen och databasklustret kör i samma tillgänglighetszon i AWS blir fördröjningen på sin höjd några enstaka millisekunder, och ändringarna hinner helt enkelt inte propageras till read-only-replikan innan vi försöker läsa in dem igen.

    När vi väl hade identifierat problemet var buggfixen bara några enstaka rader kod för att se till att läsningarna för att hämta de uppdaterade schemaobjekten gick direkt mot huvudnoden i stället för mot replikan.

    Sammanfattning

    Vad har vi då lärt oss av detta? Jo, nämligen:

    • ACID-egenskaperna hos relationsdatabaser gäller inte i ett databaskluster med replikering. Behöver du strikt read-after-write consistency för specifika databasfrågor, se till att de frågorna ställs direkt mot huvudnoden.
    • Om du vill vara säker på att kunna reproducera databasrelaterade buggar lokalt, se till att din lokala databaskonfiguration matchar produktionssystemet så exakt som möjligt. I det här fallet körde vi ingen replikering i vår lokala utvecklingsmiljö och kunde därför inte återskapa problemet lokalt. Hade varje utvecklare haft ett lokalt PostgreSQL-kluster med replikering i Docker hade vi förmodligen kunnat hitta och fixa buggen långt innan den dök upp i produktion.
    • Kort latency brukar för det mesta vara något att eftersträva. Men om du gör förändringar i din driftsmiljö som förkortar latencyn mellan två delsystem, kom ihåg att verifiera att din kod inte är timingkänslig och har implicita beroenden på den längre latencyn.

     

    IoT-expert

    Pär Wieslander

     Lösningsarkitekt och AWS-expert på Attentec

    Utbildning: Datavetenskapligt program (Linköpings universitet). Certifierad lösningsarkitekt för Amazon Web Services (AWS Certified Solutions Architect – Associate).

    Roll: Jag hjälper våra kunder att designa, utveckla och driftsätta stabila och kostnadseffektiva mjukvarulösningar i molnet med fokus på AWS.

    Bakgrund: Lång erfarenhet av mjukvaruutveckling, systemdesign och systemintegrationer inom bland annat IoT och strömmande media.

    par.wieslander@attentec.se

     

    Senaste nytt inom Internet of things