pondělí 21. dubna 2014

Postupný rollout

Kolikrát už jsem jenom zažil tu situaci. Mám testy, kterým věřím, jsem skálopevně přesvědčený, že tahle změna nemůže nic rozbít, a pak se nakonec ukáže, že přece jenom rozbila. Tohle není litanie proti testům, v tomto článku se pokusím o zamyšlení nad tím, že kromě baterie testů, kterým věříte, potřebujete i způsob, kterým minimalizujete škody, které způsobí neznámé neznámo. Článek jsem zařadil do kategorie cynického software, protože účelově testovat na uživateli mi přijde dosti cynické. Nakonec drobná míra cynismu není na škodu, pokud je to win-win strategie.

Neznámé neznámo

Přihlašování je jedna z mála věcí jakéhokoliv systému, která když se pokazí, vede k jeho nedostupnosti. V GoodData máme několik způsobů, přes které je možné dělat Single Sign On (SSO) tj. uživatel se přihlásí v mateřském systému a jeho identita se propaguje dále do GoodData, kde nevyžadujeme již její ověření uživatelem. Výsledkem je, že uživatel se přihlásí právě a jenom jednou. Starší způsob je založený na tom, že Identity provider posílá podepsanou a šifrovanou zprávu - JSON dokument. JSON obsahuje login uživatele a validitu (timestamp platnosti), tedy do kdy platí GoodData autentizační kontext vydaný na základě této zprávy.

{"email": "user@domain.com","validity": 123456789}

Z důvodů většího zabezpečení jsme tuto strukturu rozšiřovali o dva nepovinné atributy (notBefore, notOnOrAfter), definující platnost samotné zprávy. Úplně původně byl kód obsluhující tento typ přihlášení napsaný v Perlu. Při přepisu do Javy se JSON parsoval ručně. Při rozšiřování jsem nechtěl mít v kódu takovou věc, jako je ruční pársování JSONu. Čuchal jsem trochu problémy se zpětnou kompatibilitou a proto jsem v kódu nechal původní metodu jako fallback. Kód vypadal následovně.

try{
 return parseStrictly(loginSessionMessage, defaultNotBefore, defaultNotOnOrAfter);
} catch(IOException e) {
 log.info("Cannot parse login message '{}'. Using fallback method for parsing its content.", loginSessionMessage);
 return parseQuirkly(loginSessionMessage, defaultNotBefore, defaultNotOnOrAfter);
}

Pokud by kód nebyl validní JSON, Jackson knihovna na práci s JSON by vyhodila potomka IOException. S důvěrou jsem tenhle kód nasadil na produkci. První problém se objevil po pár hodinách a znamenal, že jsme museli celou funkcionalitu revertovat. Na vinně byl následující řádek uvnitř metody parseStrictly.

final Long validity = (Long) map.get(VALIDITY_FIELD_NAME);

Bohužel někdo občas poslal v JSONU místo čísla řetězec a fallback catch blok nechytil ClassCastException. Největší potupa byla poslouchat jízlivé poznámky vedle sedících Perl programátorů, že tohle by se jim v Perlu nestalo.

Tohle je jeden z mnoha případů, kdy by ani sebelepší code review nebo testování nepomohlo. Všechny testy operují nad množinou stavů, která je vám předem známá. Pokud tedy nepočítáte s nějakým stavem, těžko ho můžete otestovat. Jedná se o takzvané neznámé neznámo. V tomhle případě jsme problém mohli odhalit jenom ve chvíli, kdy jsme kód vypustili na produkci.

Tak trochu jiné testování na uživateli

Vývojáři občas, při malém pokrytí testy, utrousí, že testují na uživatelích. To je samozřejmě špatný přístup. Někdy ovšem neexistuje jiný stejně efektivní způsob a nebo vůbec cesta, jak prozkoumat neznáme neznámo. V jakémkoliv software je alespoň jeden neodhalený bug a testy jsou jenom pomůckou k objevení toho zbytku. Pokud přistoupíte na tento fakt, a já se obávám, že nic jiného nám ani nezbývá, musíte nutně dojít k tomu, že je potřeba minimalizovat škody, které může tento bug nebo bugy napáchat. Jednou z technik, která k tomu pomáhá je postupné uvolňování nových vlastností nebo verzí.

Myšlenka je to velmi jednoduchá. Namísto toho, aby se k nové verzi dostali všichni uživatelé najednou, uvolňujete ji postupně. Pokud se tam objeví chyba, nezasáhne ihned všechny uživatele, ale pouze jejich omezené množství. Pokud máte navíc v systému dobrou telemetrii, můžete problém detekovat automaticky a udělat rollback na původní verzi nebo v případě složitějších změn zastavit uvolňování nové verze dalším uživatelům. Právě postupné uvolňování nových verzí a telemetrie s automatickým rollbackem je něco na čem teď v GoodData pracujeme a co bychom chtěli mít v blízké době v provozu.

Je potřeba říci a to velmi důrazně, že ve skutečnosti netestujeme na uživateli. Výše popsaný mechanismus slouží k minimalizaci škod, ke kterým může vždy dojít. Vždy musí platit, že kód, který uvolňujeme na produkci, prošel všemi mechanismy zajištujícími odpovídající kvalitu. Jinými slovy, mechanismus neslouží jako náhražka testů, ale jako doplněk a ochranná bariéra.

V další části textu najdete moje postřehy, které odpovídají míře pochopení problému v danou chvíli a diskuzím, které jsme kolem tématu zatím vedli. Vlastní implementace se může v budoucnu odlišovat, ale zřejmě pouze v detailech. Rovněž nepředpokládám, že dáme vše na první ránu a ke konečnému stavu postupně dojdeme.

Vypustíme kanárky

Uvolňování nové verze a její deployment se může dělat pomocí Canary releasingu. Nejdříve se nasadí nová verze, která funguje paralelně s tou původní. Zároveň se uživatelé začnou směrovat na novou verzi. Pokud jde vše podle plánu, přesměrují se všichni na novou verzi, a stará verze se zahodí. V případě GoodData máme jednu vstupní bránu, přes kterou tečou všechny HTTP požadavky, proto není problém na vstupu požadavek distribuovat do staré nebo nové verze dané služby. Afinita (příslušnost) k verzi je možné držet v cookies (může být problém pro programatické REST klienty) a nebo pomocí HTTP hlaviček. V případě HTML aplikace máme mechanismus, který umožňuje bootstrap konkrétní verzí. Tento mechanismus již používáme v rámci podpory více datových center, kde mohou být rovněž nasazeny různé verze.

Největší oříšek představuje požadavek na zpětnou kompatibilitu. Interně jsou GoodData vystavěné jako služby, které spolu komunikují přes REST rozhraní. Každá služba má svůj vlastní subcluster a v podstatě i vlastní životní cyklus. Některé služby jsou vyvíjeny poměrně agilně, to znamená, že i několikrát za týden je uvolněná jejich nová verze. Je proto nutné, aby všechny změny, uvolňované tímhle způsobem, byly zpětně kompatibilní. Pokud změna není zpětně kompatibilní, musí být nasazena v plánovaném okně pro odstávku celé platformy. To je jediný způsob, jak koordinovaně nasadit nekompatibilní změny přes více služeb.

Strategie uvolňování tedy výběr uživatelů je možný po několika liniích. V GoodData jsou to uživatelé nebo projekty, případně větší celky, do kterých patří. Čili uvolňování řežete na úrovni uživatelů a nebo projektů, které používají. Například nová verzi SSO dává smysl uvolnit po ose uživatelů. Mě osobně se hodně libí metoda, které říkám dog fooding, tedy že se nová verze nejdříve uvolní interním uživatelům z řad GoodData. To v praxi vypadá tak, že uživatelé s loginem @gooddata.com ,případně ty přistupující přes naše interní adresy, by dostaly vždy nejnovější verzi. Pokud by se neobjevila regrese, bylo by možné pokračovat v uvolňování dalším uživatelům. Podobný model má například Facebook.

Chcíplý kanárek

Celý proces uvolňování a případného rollbacku musí být dostatečně automatický. Jednou z klíčových součástí je detekce anomálii, které mohou signalizovat, že v nové verzi je něco v nepořádku. V současné době sbíráme celou řadu dat z telemetrie provozu, díky kterým bychom měli dokázat rozpoznat běžný provozní stav od toho neobvyklého, který se projeví nějakou anomálií. Anomálie je prakticky cokoliv, co se odlišuje od normálů - například poměr HTTP statusů (vnitřní chyba serveru nebo chyba klienta) může celkem spolehlivě indikovat nějaký problém. Když tuhle myšlenku - detekce anomálií - rozvinu dále do budoucnosti, potřebujeme něco co se naučí rozpoznávat anomálie ze historických dat o provozu. Umělou inteligenci, kterou vytrénujeme na historických datech a které periodicky předhazujeme aktuální data z provozu. Při detekci anomálie (automatika) můžeme rozhodnout (člověk) jestli pokračovat v uvolňování a nebo jej docčasně zastavit a nebo provést rollback.

Závěr

Bez ohledu na to, kam se nám celou myšlenku podaří dopracovat, je zjevné že testy v klasické podobě postačují pouze k pokrytí stavů, které jsou nám známe. Pro stavy, o kterých nevíme, musíme použít přístup, kdy případné problémy zůstanou izolované pouze na omezenou a ideálně velmi malou skupinu uživatelů. K tomu nám dopomáhají výšše zmiňované metody.

středa 16. dubna 2014

CZ Podcast 97 - Internet věcí

V 97. dílu jsme velmi rádi přijali pozvání do firmy Inmite, kde nás čekala celá řada technologických hraček - Google glasses, Oculus rift, Leap motion, a Air-Bond hardwarového zařízení přímo od InMite - a povídání které zprostředkovali pánové Michal Šrajer a Petr Dvořák. Videa najdete na naší fanouškovské stránce, kde uvítáme i vaše ohlasy.

pondělí 7. dubna 2014

Zdeformovaný programátorský trh aneb chybějící kus pokory

Docela jsem si zvykl, a bylo to zvykání příjemné, že má profese a vůbec celé odvětví je nadstandardně dobře placeno. Když jsem na podzim roku 2001 nastupoval do svého prvního zaměstnání, činila má hrubá mzda dvanáct tisíc korun. To byly na poměry maloměsta, ve kterém jsem tehdy žil, a pochopitelně doby, velmi slušné peníze. Úroveň mých vědomostí s bídou odpovídala úrovni programátorského eléva a jedinou devizou byl můj ničím neutuchající elán. Postupem času vzrůstala moje mzda, a to i násobně, a já měl pocit, že to odpovídá tomu, kolik jsem si toho odkroutil a kolik jsem se toho naučil. Po nějaké době jsem zjistil, že od určité hranice přestávám rozlišovat jestli je výplatní pásce o pár tisíc víc a nebo méně. Zní to možná komicky, ale kdyby mi tehdy přišla polovina výplaty, asi bych to ani nepoznal.


V roce 2010 jsem nastoupil do GoodData a moje tehdejší mzda byla nižší než v předchozím zaměstnání. Dostal jsem sice stock options (velmi zjednodušeně řečeno budoucí podíl na prodeji firmy), ale ty nemají, až do prodeje firmy a nebo její vstupu na burzu, cenu ani papíru na kterém jsou vytištěny. Bral jsem to tak, že je to dobrá příležitost se něco nového naučit. Kdybych to bral z pohledu mzdy, nedával tento krok z krátkodobého hlediska žádný smysl. Z dlouhodobého hlediska to teoreticky mohlo být zajímavější, protože jsem mohl doufat v to, že se naučím něco specifického, co mi dá přidanou hodnotu na trhu práce. Po čtyřech letech v GoodData stále nemám mzdu, která by odpovídala té z předchozího zaměstnání, ale za to vím, že jsem se naučil velkou spoustu věcí a získal vhled do oblastí, o kterých jsem neměl ani ponětí nebo jsem je vnímal jenom povrchně - cloud, škálovatelnost, NoSQL, distribuované systémy, agile a tak bych mohl pokračovat dále. Z mého pohledu jasná konkurenční výhoda na trhu práce.


Od určité doby jsem se začal účastnit pohovorů s případnými kandidáty na pozice k nám do firmy. Skoro vždy mě překvapilo, jaká byla disproporce mezi tím, co si člověk představoval na výplatní pásce, a tím, kolik toho potom věděl. K vysvětlení toho zjevného rozporu si stačilo nechat aktivovat možnost zasílání pracovních nabídek z LinkedIn. Jestliže je mi někdo ochoten zaplatit 4500,- eur za měsíc a práci, která by odpovídala mnou kladeným nárokům na začínajícího vývojáře, pak je něco v nepořádku. To se dá vysvětlit různě. Mohu mít přemrštěné nároky na vývojáře a nebo jsou tady firmy, které reálně přeplácejí a vytváří tyhle deviace.


Můžeme si tu na krásně říkat, že to je zákon trhu - nabídka odpovídá poptávce - ale výsledek na sebe nemusí nechat dlouho čekat. Pokud je méně kvalifikovaná práce placená jako práce, která vyžaduje vyšší kvalifikaci (úroveň vědomostí, dovedností a zkušeností), pak si dříve nebo později někdo spočítá, že za tu samou práci někde na východ od Košic zaplatí mnohonásobně méně. Jsem dalek tvrdit, že na naší práci se třesou hordy indických vývojářů dřepících někde v Bengalore, ale rozhodně bych to nebral na lehkou váhu. Já osobně raději příjmu a dobře zaplatím eléva, který se bude ochoten učit novým věcem. Budu u něj mít jistotu, že nebude zhýčkaný pětihodinovou pracovní dobou a kolbenkářským přístupem. Není jistě bez zajímavosti, že mzda dlouhodobě nefunguje jako hlavní motivační faktor. Mnohem důležitější jsou, co se týká nejen pracovního výkonu a spokojenosti, vnitřní motivátory - baví mě práce, učím se nové věci, dává má práce smysl, vidím za svojí práci nějaký výsledek.


Hloupý kdo dává, hloupější kdo nebere. Pokud vám někdo nabídne krásné peníze a je to pro vás motivace, určitě je v pořádku na takovou nabídku kývnout. Je to soukromá věc každého a nezadatelné právo. Každopádně trocha pokory by nám všem prospěla...

neděle 16. března 2014

Svět mikro služeb

Architektura většiny aplikací odpovídá jedné velké kouli bahna, pro kterou se vžilo označení monolitická. Na úrovni aplikace jsou typickými rysy bobtnající závislosti na knihovnách, vzájemné svázané části aplikace vedoucí k nulové odolnosti vůči selhání jednotlivých částí. Na úrovni operačního systému se jedná o jeden velký proces s velkými nároky na zdroje. Monolitické aplikace mají jednu obrovskou výhodu a to je jednoduchost vývoje. Nemusíte řešit, jak bude daný kód spolupracovat se zbytkem systému, prostě ho napíšete a on se stane součástí aplikace. Monolitické aplikace mají ovšem celou řadu nevýhod. Můžete je škálovat pouze jako celek. To vede k neoptimálnímu využití zdrojů. V případě selhání jedné části aplikace, byť absolutně nekrytické pro fungování z pohledu uživatele, dochází lavinovitě k selhání celé aplikace. Klasickým příkladem může být memory leak, který postihne celou aplikaci. Alternativou k monolitickému uspořádání aplikace představuje architektura micro services.


Architektura micro services vychází z toho, že aplikace a její funkcionalita se skládá z velkého množství malých služeb pospojovaných do větších celků. Důležitou charakteristikou tohoto architektonického přístupu je nebo by měla být izolovanost jednotlivých služeb. Adrian Cockcroft (Netflix, eBay, Sun) prosazoval myšlenku, že každá mikro služba může selhat a její konzumenti s tím musí apriori počítat. Byl to právě Adrian Cockcroft a firma Netflix pod jeho vedením, která postavila celý svůj stack právě na této architektuře.


Na velikosti záleží

Velikost mikro služby se těžko kvantifikuje. Četl jsem názory, že by to mělo být maximálně pár stovek řádků kódu, ale to mi nepřijde směrodatné. Mnohem důležitějším aspektem je dodržení takzvané jedné odpovědnosti (single responsibility principle). Každá mikro služba by měla dělat právě a jenom jednu věc - login, checkout, hledání, zobrazení nákupního košíku atd. V případě REST rozhraní mi přijde jako vhodná granularita jeden resource odpovídající mikro službě nebo alespoň funkcionalita z pohledu uživatele. Docela velkým nebezpečím je opačný extrém, tedy že aplikaci příliš fragmentujete vytvořením stovek mikro služeb. Každá mikro služba má svoje nároky, například v Jave musíte počítat s režií pro JVM, a musíte počítat se síťovým overheadem pro každou komunikaci mezi jednotlivými mikro službami. Někde jsem viděl poměrně vtipné přirovnání, že mikro služby představují Enterprise Java Beans pro hipstery.


Skryté náklady

Nejvíce přitažlivé na monolitické architektuře jsou nulové náklady, které máte s přidáváním nových vlastností do aplikace. V případě mikro služeb potřebujete poměrně slušnou infrastrukturu a to pro každou ze služeb. Řekněme, že máme mikro služby, které mají všechny jednotné REST rozhraní (HTTP, JSON). Pro každou z mikro služeb potřebujete kontejner, ve kterém poběží. Potřebujete monitoring, alerting. Každá z mikro služeb by měla být high available. Klienti služeb, další mikro služby, se musí nějakým způsobem dozvědět kde tyto služby běží. Můžete začít tím, že endpointy služeb bude někde v konfiguraci, ale dříve nebo později budete potřebovat nějaký registr služeb a jejich endpointů, protože služby mohou vznikat v nových verzích a nebo kolabovat v průběhu života celé aplikace. To se bavíme jenom o nárocích na běhové prostředí, k tomu je potřeba připočítat automatizaci na úrovni deploymentu (Puppet, Chef).


Kdy začít

Vždycky když merguji pull request, trnu hrůzou z představy, aby nějaká chyba nesprovodila ze světa celý server a já nemusel vstávat ve dvě hodiny ráno a něco řešit. Můžete si zkusit aplikaci rozdělit na kritické a nekritické komponenty z pohledu uživatele, zkusit se zamyslet, jestli byste mohli efektivně škálovat jednotlivé části aplikace, ale nejspolehlivější indikátor je obava, že už vám komplexita aplikace přerůstá přes hlavu. V takovou chvíli je vhodné začít přemýšlet o mikro službách. Velkou výhodou tohoto architektonického přístupu je, že můžete migrovat postupně jednotlivé části aplikace. Nemusí se hned od začátku jednat o velký třesk. Mikro služby nejsou sami o sobě spásou, i zde můžete udělat chybu, ale pokud to uděláte dobře, je velká pravděpodobnost, že chyba složí jenom jednu malou část aplikace.

Zdroje a užitečné odkazy

sobota 15. března 2014

CZ Podcast 96 - QA panel v Avastu

Do 96. dílu jsme si pozvali, tedy co my, ale Roumen pozval a zorganizoval, QA diskuzní panel ve společnosti QA. Společně s ním dorazil Pavel Chytil, Lukáš Hasík a Jaromír Cvrček. Celou dobu jsme se si povídali o tom, jak klucí zajišťují kvalitu antivirového řešení Avastu, jak využívají Virtual Box pro testování nových verzí a vůbec o infrastruktuře, kterou používají. Vaše ohlasy, komentáře a otázky uvítáme na naší oficiální Facebook stránce.