🎲mikrobloggeriet olormolorm-53 · olorm-54 · olorm-55

Krøll på tidslinja

Vi har et ganske vanlig oppsett der en applikasjon lagrer tidspunkt i en MariaDB, og jeg oppdaga en dag at noen så ut til å ligge i rar rekkefølge.

Kun på én spesiell dag, om høsten. Mellom klokka 2 og 3 på natta. Du kan kanskje gjette dagen? Det er selvfølgelig når vi skrur klokka en time tilbake, den siste dagen av sommertid.

Koden i applikasjonen ser omtrent slik ut:

var created time.Time
// ...
db.Exec("INSERT INTO t(created,id,info) VALUES(?,?,?)", created, id, info)

Både applikasjonen og databasen kjører med tidssone satt til Europe/Oslo. Tiden som skrives (created) er typisk ganske nært eksekveringstiden, noen sekunder eller minutter før Now().

Det som skjer er følgende:

0. Applikasjon og database kjører med gitt tidssone (Oslo)
1. Applikasjon oppretter en tilkobling til databasen
   som har en tidssone på _sesjonen_ (Oslo)

                 ,------------------------.       +------+
INSERT(ts) ---> (  session time_zone:XXX ( ) ---> |  DB  |
                 '------------------------'       +------+

2. Database-driveren                           3. Verdien konverteres
   i applikasjonen konverterer                    fra sesjonens tidssone
   gitt tidspunkt til sesjonens                   til UTC og lagres
   tidssone og enkoder og sender verdien          (Og oversettes fra UTC
                                                  til sesjonens tidssone
                                                  ved utlesing)

Sesjonens tidssone kan settes av klienten, og arver ellers verdien fra DB-serveren.

Jeg hopper til poenget: Problemet ligger i enkoding av verdien i kombinasjon med sesjonens tidssone. Tidspunktet enkodes som 2023-10-29 02:30:00[.000000] av driveren. Med tidssone Europe/Oslo er denne tiden som kjent tvetydig, fordi den skjer to ganger; først for sommertid, så for vintertid. Enkodingen må derfor inkludere hvilken av dem det er snakk om.

Så, er det bare driveren som er dårlig? Nei, dessverre er det i MariaDB p.t. ikke mulig å enkode tidssone-info verken som binær¹- eller tekst-verdi (i motsetning til MySQL).

Løsningen er å sette sesjonens time_zone og verdiene som skrives til en fast tidssone uten sommertid, f.eks. UTC.

Merk at dette problemet ikke er spesielt for Norge. Selv om stadig færre land har sommertid, er det fortsatt ca 70 land som gjenstår, inkludert nesten hele Europa og Nord-Amerika.

Hva kunne vi gjort for å unngå bug’en? Lite gjennomtenkte muligheter:

  1. Prøve å tenke seg til mulige spesialtilfeller?
  2. Lese hele MariaDB- og driver-dokumentasjonen?
  3. ~Lese all kildekoden?~ (urealistisk)
  4. Teste med alle tidspunkt/permutasjoner?
  5. Kun bruke ukomplisert tech/features?
  6. Detekter (tenkte) problemer når/hvis de oppstår?
  7. QA med eksperter?

Send meg gjerne gode forslag.

—Richard Tingstad

Tillegg: testing av tid

Jeg fant et kult bibliotek jeg kunne bruke for å endre tiden; libfaketime, og lagde meg følgende Dockerfile for å gjenskape tidligere tilstander:

FROM mariadb:11.3

RUN apt-get update && apt-get install libfaketime

ENV TZ=Europe/Oslo \
    MARIADB_DATABASE=d MARIADB_ROOT_PASSWORD=r \
    MARIADB_PASSWORD=p MARIADB_USER=u

COPY <<EOF /docker-entrypoint-initdb.d/0.sql
    CREATE TABLE t ( id INTEGER AUTO_INCREMENT PRIMARY KEY,
        ts TIMESTAMP, dt DATETIME );
EOF

COPY <<"EOF" run.sh
    [ -n "$START" ] || START=$(date -d '2023-10-29 02:30 CEST' +%s)
    LD_PRELOAD=$(find /usr -name libfaketime.so.1) \
        FAKETIME=-$(( $(date +%s) - $START )) \
        "$@"
EOF

ENTRYPOINT ["sh", "run.sh"]
CMD ["docker-entrypoint.sh", "mariadbd"]

(¹ “The encoding of the COM_STMT_EXECUTE parameters are the same as the encoding of the binary resultsets.” —Binary protocol)

Rabbit looking at watch