Opdatering fra udviklingsafdelingen

Bubbles, FC Sunnyvale Udvikler 25. oktober 2019, 14:42

Pyhhh, der har været nok at se til den sidste måneds tid. Her vil jeg fortælle lidt om hvad vi har haft gang i.

Sæsonopdateringen 20. oktober

Som I nok opdagede var der bøvl med sæsonopdateringen i søndags.

Det skyldtes at vi for første gang kørte med en ny metode til oprettelse af kampprogram, som har til formål at gøre sæsonopdateringen hurtigere.

Siden vi flyttede til vores nye server-host, har sæsonopdateringen været noget langsom. Serveren, der eksekverer den, har den hurtigste CPU, man kan få ved vores nye host, og på papiret er den hurtigere end den gamle. Når sæsonopdateringen kører, kan vi se at hverken serveren eller vores database bruger særligt meget af deres ressourcer, så vores formodning er at det kan skyldes en højere latens på netværket mellem de to. Og eftersom sæsonopdateringen er en proces som udfører enormt mange små database-læsninger og -skrivninger i kun 1 enkelt tråd, så vil en bare lidt højere latens på netværket kunne få de to servere til at spilde en masse tid på at vente. For et par uger siden rettede vi desuden en bug, som havde gjort at vi i lang tid ikke havde fyldt op med bots (den engelske liga var helt tømt). Da alle disse manglende bots igen blev oprettet, blev sæsonopdateringen væsentligt langsommere.

Derfor implementerede vi en multi-trådet process til oprettelse af kampprogram. Den nye process kører 1 tråd per land, så kampprogrammerne bliver oprettet sideløbende, i stedet for et land ad gangen.

Men, multitrådet programmering kan være risikabelt, og en lille fejl kan have vilde konsekvenser.

Det der skete var, at der godt nok blev oprettet det korrekte antal kampe mod de rigtige modstandere, men de meta-data, som laver forbindelsen mellem en klub og deres kampe, og mellem en kamp og deltagernes taktikker, blev oprettet 14 gange per kamp i stedet for 1.

2x14 ekstra rækkers data per kamp lyder måske ikke af meget, men det fik alligevel kampsiderne til at dø fuldstændigt og tage resten af sitet med. For når man hentede siden med ens kampprogram resulterede det i at der blev oprettet:

30 kampe x 14 klub/kamp-links x 14 kamp/taktik-links x 2 klubber(dig og modstanderen) x 11 spillere + alt muligt andet data = hundredetusindvis af objekter, som blev oprettet og slugte al serverens RAM. Dette fik hele sitet til at køre dræbende langsomt søndag aften.

Derudover blev kampene godt nok oprettet i de rigtige divisioner og puljer, men i tilfældige lande!

I sidste ende var vi nødt til at slette alle kampene og oprette et nyt kampprogram med den gamle metode.

Når vi implementerer risikable eller kritiske ting, såsom ændringer i sæsonopdateringen, så skriver vi samtidig et antal automatiske test til. Automatiske tests er en bid kode, som opstiller data til et specifikt scenarie, kører det igennem en del af vores kode, og så tjekker om resultatet er korrekt. I dag har vi ca. 1600 tests, med over 5000 individuelle kontrolpunkter, som vi kører igennem når vi laver nye ting.

Men, tests skriver selvfølgelig ikke sig selv, så der bliver kun kontrolleret de ting, vi har fantasi til at forestille os kan gå galt. Vores test tjekkede om der blev oprettet det korrekte antal kampe mod de rigtige modstandere - og det gjorde der - men de kiggede ikke på de ekstre metadata.

Så i denne uge har vi rettet fejlen og implementeret en række ekstra foranstaltninger imod at noget lignende sker igen. Til at begynde med skrev vi et antal ekstra tests, som tjekker de ting, der gik galt i søndags.

Derudover satte vi unique index på de to database-tabeller, hvor de ekstra data blev fyldt i. Dette sikrer at hvis vi nogensinde igen skriver noget kode, som forsøger at indsætte mere end 1 kamp/klub-link for den samme klub og kamp, så vil databasen blokere for det fuldstændigt, og vores kode vil i stedet crashe øjeblikkeligt. Og, det er altid bedst hvis fejlbehæftet kode crasher øjeblikket i stedet for at det kører videre og skriver 10 millioner rækker med skralde-data, som man først opdager senere, når det giver problemer andre steder i systemet.

Der var dog en god nyhed! Oprettelsen af kampprogrammet faktisk gik superhurtigt - og det selvom fejlen gjorde at den oprettede næsten 10 millioner ekstra rækker med skralde-data.

503-fejl / loadbalancer

I de seneste uger har vi også fået styr på et problem, som vi oplevede efter vi flyttede til den nye server-host.

I vores nye setup har vi 3 webservere, som ligger bagved en loadbalancer. Loadbalanceren tager imod alle de requests, der kommer ind, og fordeler dem blandt de 3 webservere. Med jævne mellemrum tjekker den hver servers "helbred" ved at sende den en besked, og se om den svarer. Hvis dette helbredstjek fejler, så tager loadbalanceren den pågældende server ud af rotationen. Hvis alle 3 fejler helbredstjekket, så vil man som bruger se en hvid side med en "503"-fejl.

Det vi oplevede på det nye setup var, at helbredstjekkene indimellem begyndte at fejle, selvom vi kunne se at serverne fungerede fint og overhovedet ikke var belastede. Det var et totalt mysterie, for det kom og gik i bølger, og vi kunne se i vores server-log at serverne svarede fint på de helbredstjek som de fik fra loadbalanceren. Derudover kunne vi ikke se andet, fordi vi ikke har direkte adgang til loadbalanceren og dens logs.

Det tog en hel snak frem og tilbage med supporten ved vores nye host, før vi fandt frem til at det skyldtes et netværksproblem. Vores webservere er nemlig pakket godt og grundigt væk bagved en firewall, som afviser al trafik udefra, og kun slipper trafik igennem som kommer fra lokalnetværket.

Det vi med tiden fandt frem til var, at de rent faktisk ikke kunne garantere at loadbalancerens helbredstjek altid kom fra lokalnetværket. Helbredstjekkene kommer fra mange forskellige IP'er, og til tider var vi uheldige at de allesammen kom fra en IP, som blev blokeret af firewall'en, hvilket var årsagen til vores problem. Loadbalanceren troede at serverne var nede, og så sendte den ingen trafik igennem indtil der kom helbredstjeks fra andre IP'er.

Til sidst fandt supporteren dog frem til en nyligt implementeret og udokumenteret indstilling som gjorde at firewall'en altid ville slippe trafik igennem fra loadbalanceren, og efter vi fik det sat op, har der ikke været flere 503'ere (bortset fra i søndags, hvor serverne rent faktisk var i ret dårligt helbred :P).

Opgradering til Rails 4.2

Opgradering til Rails 4.2 og Ruby 2.4.9 er også færdig og alle servere har kørt udelukkende på Rails 4 i de sidste 2 uger. Det var en meget stor opgave, men også nødvendig, da vores tidligere versioner af Rails og Ruby ikke længere fik sikkerhedsopdateringer.

Meget gammel bug i taktikeditor

I denne uge har vi også uploadet en rettelse til et meget gammelt problem i taktikeditoren, som har været lidt af et mysterie. Det var svært at finde frem til fordi det kun forekom sporadisk, og fordi vi ikke selv kunne genskabe det. Og, når man ikke selv kan se problemet og måske ikke en gang er sikker på at det findes, så er det selvsagt ret umuligt at rette det.

Vi har indimellem fået rapporter om at en ændring i taktikken ikke er blevet gemt, og at manageren ikke mente at de havde solgt nogle spillere med editoren åben. Men, rapporterne kom selvfølgelig først adskillige dage efter, når manageren opdagede et hul i taktikken, hvilket gør det svært at reagere på. Vi kunne aldrig selv få det til at ske, uanset hvor meget vi trak rundt med spillerne i taktikken.

Men for nogen tid siden blev vi endelig i stand til at genskabe problemet, og da detaljerne omkring det var blevet kortlagt, var det tydeligt hvorfor det var så svært at finde.

Problemet kunne kun opstå i 2 meget specifikke situationer:

  1. Når man trak en spiller fra reserverne til en tom plads på banen.
  2. Nar man trak en spiller fra banen til en tom plads i reserverne.

Alle andre handlinger, såsom at trække spillere frem og tilbage mellem banen og spillerlisten, eller mellem reserverne og spillerlisten, eller hvis man trak ham hen på en plads hvor der allerede var en spiller var ikke berørt.

Når man flytter en spiller fra banen til reserverne, skal der ske 2 ting: først skal det registreres at han er fjernet fra banen, og først derefter skal det registreres at han skal sættes ind på reservelisten.

I de 2 meget specifikke situationer, ville taktik-editoren sende 2 separate beskeder til serveren:

  1. Fjern spiller X fra reservelisten
  2. Indsæt spiller X på banen

...og omvendt

Problemet skyldtes at man pga. den måde internettet fungerer, ikke kan garantere at beskederne ankommer til serveren i den rækkefølge de er sendt.

Så det der kunne ske var, at beskeden om at sætte spilleren ind på banen ankom FØR beskeden om at fjerne ham fra reservelisten.

Men en spiller må ikke eksistere begge steder på samme tid - ikke en gang i et splitsekund - så serveren ville nægte at sætte spilleren på banen fordi han stadig stod i reserverne. Når den første besked så endelig ankom ville spilleren blive fjernet fra reservelisten, men der ville det være for sent, og man ville stå med et hul i taktikken.

Ved alle de andre måder, man kan flytte en spiller på, bliver der kun sendt 1 besked. Hvis man f.eks trækker en spiller ind på en plads som ikke er tom, så ville editoren i stedet sende 1 besked om at "spiller X og Y skal bytte plads", og så er timing ikke noget problem.

Dette blev også løsning på problemet. Vi har implementeret en måde hvorpå flytning af en spiller fra reserverne til banen - eller omvendt - kun kræver 1 besked til serveren. Så fjerner den spilleren fra det ene sted og sætter ham ind det andet i den rigtige rækkefølge.

Men hvorfor kunne vi så pludselig selv genskabe problemet, når vi aldrig har kunnet det før?

Vi har en teori om at det skyldes serverflytningen. Førhen var sitet hostet i Danmark, og vi havde kun 1 webserver, som tog imod alle requests.

Det nye serversetup ligger i Frankfurt, og requests skal først igennem Cloudflare, derefter vores loadbalancer, og så videre til 1 af vores 3 webservere. Den længere vej til serverne gør det mere sandsynligt at internettet sender de 2 beskeder ad forskellige ruter, som kan resultere i at besked nummer 2 ankommer først.

Selvom vi nu har fået kål på dette specifikke problem, skal man dog stadig være opmærksom i de tilfælde, hvor en spiller bliver solgt i baggrunden, mens man har taktikeditoren åben. Her kan vi ikke garantere at alting fungerer, når du trækker rundt med en spiller som rent faktisk har forladt klubben. Vi har dog et par ideer til hvordan vi måske en gang i fremtiden kan gøre editoren mere robust overfor denne hændelse.

God weekend!