Leállás nélküli adatmigráció

Rengetegszer látok olyat, hogy szolgáltatások karbantartás miatt állnak le. Gondoltam, leírom, hogy csomó mindent lehet leállás nélkül is migrálni, hátha valakinek új lesz.

Egy nagyon egyszerű példa: az adatbázisban az ügyfél telefonszáma eddig stringként volt tárolva, és mostantól számként akarjuk tárolni, mert rájöttünk, hogy a string mezőbe sok szar kerül.

Leállással ez úgy néz ki, hogy mindent megállítunk, kitesszük a karbantartás.jpg-t, új oszlop a sémába, áttöltjük az adatokat némi tisztítással, régi oszlop törlése a sémából, minden indulhat újra. Ha ez munkaidőben megy, akkor szív a felhasználó, ha munkaidőn kívül, akkor szív az IT-s, és főleg akkor van szívás, ha a migrációs lépésekbe valahol hiba csúszik. Ezeket küszöböli ki az alábbi két módszer.

Az egyik lehetőség a dual read:

1. Betesszük a sémába az új oszlopot.

2. Felkészítjük az alkalmazásokat, hogy két helyről olvassanak. Ha megvan az adat az új oszlopban, akkor onnan, amúgy a régiből.

3. Módosítjuk az alkalmazásokat, hogy az új adatokat már csak az új helyre írják.

4. Átmásoljuk az adatokat az új oszlopba tisztítással (backfill). Először csak pár teszt sorban, aztán szép lassan az összes többiben is. Akár hetekig is futhat a másolás.

5. Ha minden működik, megszüntetjük a dual read kódot és töröljük a régi oszlopot.

A másik a dual write:

1. Betesszük a sémába az új oszlopot.

2. Módosítjuk az alkalmazásokat, hogy két helyre írjanak. Minden adat bekerül a régi és új oszlopba is.

3. Átmásoljuk az adatokat az új oszlopba tisztítással (backfill). Először csak pár teszt sorban, aztán szép lassan az összes többiben is. Akár hetekig is futhat a másolás.

4. Módosítjuk az alkalmazásokat, hogy az adatokat csak az új helyről olvassák.

5. Ha minden működik, megszüntetjük a dual write kódot és töröljük a régi oszlopot.

Nekem a dual write jobban tetszik, mert általában könnyebb lefejleszteni az ideiglenes logikát hozzá. Ugyanakkor az új adat olvasása alapvetően egy pillanat alatt vált át a régiről az újra, ami lehet rizikó. Amúgy keverni is lehet a két stratégiát, de szerintem nem éri meg.

Van pár buktatója, ha bonyolult az infrastruktúra: nem triviális kitalálni, kik az érintett író és olvasó alkalmazások, illetve meg kell várni bizonyos lépések előtt, hogy kimenjen új release (és ígéretet kapjunk, hogy már nem lesz rollback). Ezekre is vannak jó módszerek, de az már off topic.

Hozzászólások

Sok mindent meg lehetne csinálni. Kérdés az, hogy kell-e?
Ha például kiesés nélküli kritikus rendszerről beszélünk, akkor követelménydokumentációban leírt elvárásként ott van.
Amennyiben egy pörgős webshopról beszélünk, akkor a követelménydokumentációban leírt elvárás lehet.

Amennyiben pedig egy honlapra nem követelmény, akkor meg minek. A 'csak mert úgy szép' nem indok.
A nagyobb baj, hogy hiányos a követelménydokumentáció. A kifejlesztést követő szolgáltatási életciklusra vonatkozó részét igen gyakran ki szokták felejteni belőle.

Ha ideális lenne a világ...
A különbség tipikus elmélet-gyakorlat különbség. Itt az eltérés:

  a.) jó eséllyel nem lesz semmi probléma
  b.) nem lesz semmi probléma

Az üzemelő környezetben 'okosban' módszer az a.) kockázatot vonja maga után, ellenben a főnök és a megrendelő a 'jó eséllyel' rész nélkül a b.) esetről egyeztet egymás között feletted. Ha megcsúszol, vagy a beavatkozásodtól független másik hibajelenség jelentkezne, akkor ők már a b.)-t  (azt mondtad, nem lesz semmi probléma) kérik számon rajtad. És így a tőled független okból akadozó szolgáltatásért is te magyarázkodhatsz.

Ha viszont karbantartási időablakban szolgáltatás leállítása mellett csinálod és a szükséges ellenőrzések végrehajtása után elindítod (mindezt időponttal lejegyezve), akkor a b.) esetet biztosítod elinduláskor. Pont azt, amit feletted egyeztettek és mindenki elégedett.

Mondjuk azt meg kell érteni, hogy a telefonszámok nem számok, hanem számjegyekből álló stringek. Két telefonszámot ha összeadok, az nem lesz telefonszám feltétlenül, nem lehet őket kivonni egymásból stb. A vezető 0-nak van értelme telefonszámnál, számoknál nincs. 

Hasonló igaz minden más számjegyekből álló azonosítóra. TAJ, adószám, irányítószám, személyi szám, stb. 

 Nem jó dolog számként tárolni azokat, a dbbe íráskor kell validálni, hogy az adat számjegyekből álló string. 

Nem csak elonye van ennek, hanem hatranya is. Egyreszt a legtobb modositas nem annyira trivialis, mint ami a peldaban van. Mert ez elegge az. Aztan ott van az, hogy a kodot igy tobb alkalommal kell modositani, szoval a serveren valamit egy helyett tobbszor is ujra kell inditani (ez mondjuk nem nagy problema, mert eleg gyors, es tobb geppel vannak megfelelo megoldasok erre). Ha egy teljes rendszert cserelnek le egy ujabbra, akkor azert a leallitas -> vegleges migracio -> ujrainditas celravezetobb lehet (miutan tobbszor kiprobaltak reszleges/epp aktualis adaton).

A legnagyobb talan az, hogy a tobbszor modositunk a fejleszto idejet tekintve dragabb, mint a leall -> migral -> ujraindit. A dual read es a dual write is egy csomo olyan kodot visz be, ami utana mehet/megy a kukaba. Ennel az egyszeru peldanal ez nem problema, de ha bonyolultabb modositasrol van szo, akkor a fiszemfaszom blog tulajdonosa azt fogja nezni, hogy x millio+egy este 2 es 3 ora kozott leallas, vagy 2x millio es vegig mukodik az oldala. Valoszinuleg az elobbi mellett fog donteni.

De persze az elonyei is vitathatatlanok. Csak gondoltam a hatranyok is legyenek itt.

When you tear out a man's tongue, you are not proving him a liar, you're only telling the world that you fear what he might say. -George R.R. Martin