Sláva abstrakci

February 2nd, 2010

Dnes mám nějakou náladu na filozofování, tak napíši něco, čeho už jsem se několikrát dotkl. Takže žádná novinka. Začnu svým oblíbeným prohlášením. Já píšu programy pro uživatele, snažím se řešit nějaký jejich problém. Říkejme tomu business problém. Rozhodně nepíši operační systémy, programovací jazyky, vývojová prostředí ani knihovny. Obvykle píši něco, co mému zákazníkovi přináší nějaké peníze.

Počítače ale pořád ještě pracují s nulami a jedničkami, od kterých je k penězům setsakra daleko. A od toho tu právě jsou abstrakce. Operační systém mě odstíní od těch nul a jedniček, virtuální stroj mě odstíní od operačního systému a správně zvolené knihovny mě odstíní od virtuálního stroje.

V ideálním případě bych měl psát kód, který co nejblíže popisuje řešení daného business problému. Pokud to tak není, je něco špatně. Pokud musím v kódu kopírovat pole bytů, starat se o vlákna, řešit systémové výjimky, starat se o paralelizaci, tak vím, že je někde chyba. Já chci prostě a jednoduše psát jenom business kód.

A nejsem to jenom já, podívejme se tímto pohledem na úspěšné technologie. Můžeme začít třeba samotnou Javou. Ta uspěla také díky tomu, že programátora odstínila od spousty do té doby běžných infrastrukturních starost. V Javě už jste se nemuseli starat o dealokování paměti, nemuseli jste řešit, jestli je String ukončený nulou nebo čím, nemuseli jste řešit jaký typ procesoru používáte nebo jaký je rozdíl mezi endianem a indiánem. V Javě jste se mohli jste se soustředit na psaní kódu. (Ano vím, že Smalltalk to uměl už o sto let dříve).

Podobně úspěšné je servlet API. To nám zas umožňuje chovat se k vícevláknovému systému tak, jako by v něm bylo jen jedno vlákno. Ano, jako každá abstrakce i tato prosakuje, ale v zásadě to tak je. Vsadil bych se, že tato podařená abstrakce stojí za úspěchem servlet API a tím pádem i enterprise Javy. Naopak špatnou abstrakci poznáte podle toho, že vás nutí dělat něco, co byste jinak nedělali. Třeba EJB 2 a jeho šílená rozhraní.

Nejen že mi dobrá abstrakce umožňuje se soustředit na můj business problém, ona mi i umožňuje držet krok s vývojem. Změní se operační systém? Nic se neděje. Změní se architektura procesoru? Nezájem, já jsem svůj program napsal v obecnější rovině, takže mě to (pravděpodobně) neovlivní. Naopak, já mohu využít novinek aniž bych na svůj kód třeba jen šáhnul. Procesor má tisíc jader? Nevadí, jenom změním konfiguraci serveru a mám vystaráno. Práce s více vlákny je pomalá? Nevadí, udělám update JVM a hned je vše jinak. Moje problémy za mě řeší někdo jiný.

A to je vlastně důvod, proč to všechno píši. Pokud mě někdo nutí zahrnout do programu něco, co nevyjadřuje business problém, vím že je to špatně. Pokud mě někdo například nutí používat asynchronní API a z mých požadavků to přímo nevyplývá, vím, že se snaží řešit problém na nesprávném místě. Často není vyhnutí, často není technologie dost pokročilá. Ale většinou je to prostě jen špatná volba.

Představme si, že například nechci, aby uživatel čekal, až se mi data zapíší do databáze, tak jak to píše Dagi. Samozřejmě, že to mohu řešit v kódu. Ale tam já to řešit nechci, tam já chci řešit jenom business. Musím se proto zamyslet jak to dělat jinak. Použít rychlejší databázi? Použít lepší JDBC ovladač? Použít databázi, která bere transakce velmi benevolentně? Všechno je možné, změna kódu je až poslední varianta.

Je tu ještě jeden z argumentů, který jsem zatím nezmínil. Lidé, kteří píší knihovny, databáze nebo virtuální stroje tomu rozumí mnohem lépe než já. Já vím jak převádět peníze, oni rozumí transakcím. Já vím co zajímá zákazníka, oni vědí jak synchronizovat data mezi vlákny. Kdykoliv to musím řešit za ně, vím, že buď odvedli špatnou práci nebo že já jsem si zvolil špatnou technologii.

Vemte si například ParallelArray. To mi umožňuje provádět operaci nad poli paralelně. Úžasná a i převratná knihovna. Ale z mého pohledu je špatná v tom, že mě nutí řešit thread pooly. To já nechci, to zákazníka vůbec nezajímá. Zamysleme se, proč to za mě nemůže řešit někdo jiný. Řekněme, že zavolám klasické Arrays.sort(). Proč nemůže běžet paralelně? Že JVM neví kolik vláken se má použít? Jak to že ne? To to ví dokonce lépe než já. Virtuální stroj dokonce i ví, kolik přesně má v daném okamžiku volných procesorů. To já nikdy v okamžiku psaní programu nevím a vědět nikdy nebudu. Že neví jestli je to operace k paralelizaci vhodná? Jak to? HotSpot to ví zase mnohem přesněji než já. To je stejné, jako kdybyste tvrdili, že je lepší, když programátor ručně uvolňuje paměť nebo optimalizuje kód.

Tak se nad tím prosím zkuste zamyslet. Třeba nad tím jak do toho všeho zapadají dynamické jazyky, DSL a podobně. Připadá mi, že to všechno dává docela smysl.

Proč nepotřebuji asynchronní JDBC

January 28th, 2010

Můj blog nějak usíná, tak jsem si řekl, že nalákám čtenáře na nějakou kontroverzní diskuzi. Jako záminku si beru pondělní CZJUG, při kterém jsem narazil, že moc nerozumím asynchronnímu volání. Nebo jinak, myslím si, že tomu rozumím a připadá mi, že to moc nepotřebuji. Budu rád, když mě někdo vyvede z omylu. Proto budu psát víc kousavěji než obvykle. Snad tím někoho vyprovokuji.

Ještě musím podotknout, že budu psát o klasickém serveru, který má problém s tím, že má moc uživatelů. Rozhodně se to netýká třeba dávkového zpracování velkého množství dat, to je úplně jiná kapitola.

Takže o co jde? V pondělí jsem viděl bleskořeč o node.js. Zaujal mě příklad, na kterém bylo předvedeno asynchronní čtení z databáze. Motivace je jednoduchá. Čtení z databáze je obvykle pomalé a je škoda zdržovat drahé a cenné vlákno čekáním. Je lepší ho poslat obsluhovat další požadavky. Výsledek databázového dotazu může být zpracovaný později, buď tím samým, nebo jiným vláknem. Tím pádem bude náš systém skoro neomezeně škálovatelný. To dává smysl, že? Ani náhodou!

Původně jsem chtěl napsat, že to je špatně zvolený příklad, že asynchronní JDBC je nesmysl. Ale pak se mi to rozleželo a jsem ochoten připustit, že by v tom něco být mohlo. Ale jen malinko.

Nejdřív si musíme uvědomit, že databázový dotaz trvá dlouho proto, že databáze nestíhá. Chudinka totiž bývá tím jedním bodem, na který se všichni připojují. Tím že aplikačnímu serveru umožním zpracovat víc připojení celou situaci jen zhorším. Je to stejné, jako když na pomalém internetu čekáte na načtení stránky a z nudy si otevřete druhou. Ta samozřejmě také dlouho trvá a proto si otevřete třetí. A ono pořád nic. Dokonce to vypadá, jako by ten internet byl ještě pomalejší. Stejná věc by se vám mohla stát i u databáze. Takže si prosím zapamatujte, že pokud máte pomalou databázi, tak ji více souběžnými dotazy moc nepomůžete.

Naštěstí máme obvykle omezený pool připojení do databáze takže to akutně nehrozí (Jirka Mareš má bod za postřeh). Za domácí úkol se zkuste zamyslet co by se v takovém případě stalo? Čekaly by požadavky ve frontě (jaké, jak dlouho) na uvolnění připojení nebo by to vyhazovalo chybu?

Obdobný problém máme, i pokud čekáme na pomalý disk nebo zatížený procesor. Tím že budeme přijímat víc práce, situaci jen zhoršíme. V takovém případě asynchronní volání smysl moc nedává. Naopak, potřebujeme požadavky co nejdříve přibrzdit, aby měl server čas na zotavení.

Představme si ale situaci, kdy nečekáme na nedostatkový zdroj, ale na něco, co prostě jen dlouho trvá. Jsou lepší vlákna nebo asynchronní volání? Já hlasuji pro vlákna. Asynchronní volání až moc smrdí předčasnou optimalizací. Nutí programátora myslet úplně jinak, jenom kvůli strachu z něčeho, co možná nenastane. Bojíme se, že náš systém nezvládne dost vláken a proto znepřehledňujeme zdrojový kód. Klasická předčasná optimalizace.

Schválně, zkuste odhadnout kolik vláken zvládne JVM na běžném serveru? 10, 100, 1K, 10K nebo 100K? Víte to? Já jsem také nevěděl, tak jsem se vydal hledat. Moc zdrojů k tomu není, ale narazil jsem na užasnou prezentaci. Tu si určitě přečtěte. Kromě jiného se v ní dočtete, že 10 000 vláken by neměl být problém (s menším laděním velikosti zásobníku)! Navíc je to prý i dost rychlé. Najdou se i tací, kteří tvrdí, že použití vláken je dokonce rychlejší než událostní řízení! Takže je možné, že si chceme znepřehledňovat kód jen proto, abychom ho zpomalili. Nevím jestli už jsem to psal, ale to by byla klasická předčasná optimalizace. Zapamatujte si proto prosím, že spící vlákno (skoro) nic nestojí.

Ale i kdyby vlákna byla pomalejší, radši bych počkal až to za mě Oracle vyřeší při dalším updatu Javy, než abych kvůli tomu učil spoustu programátorů myslet jinak! Schválně, jak dlouho vám bude trvat pochopit asynchronní servlety. Mě se to pořád ještě pořádně nepodařilo. A to jsem se docela snažil. (Pěkný úvod najdete zde.)

Ale abych přeci nebyl jen tak jednostranný, je spousta míst, kde se asynchronní zpracování hodí. Jedním z nich může být šílený AJAX dotaz, který drží otevřené připojení a tím i vlákno desítky sekund. Třeba proto, že čeká až vám přijde mail. Pak má možná smysl uvažovat nad tím, uvolnit vlákno pro někoho jiného. Ale i to by mělo jít řešit transparentně. Něco ve smyslu: JVM si všimlo, že toto vlákno dlouho spí, tak jeho zásobník někam schová a uvolní prostředky. Já se pak se nebudu musit učit úplně jinému způsobu myšlení a moje programy budou zároveň škálovatelné i čitelné.

Rozloučím se pár citáty:

What's harder, synchronizing 2 threads or synchronizing 1000 threads?

We examined the code structure of the Flash web server and of several applications in Ninja, SEDA, and TinyOS. In all cases, the control flow patterns used by these applications fell into three simple categories: call/return, parallel calls, and pipelines. All of these patterns can be expressed more naturally with threads.

[Rob von Behren, Jeremy Condit and Eric Brewer, Why Events Are A Bad Idea (for high-concurrency servers)]

RESTované webové služby

December 6th, 2009

Jak je o mě jistě známo, jsem svého druhu dinosaurus. Stále ještě programuji v Javě a dokonce používám takové předpotopní technologie, jakými jsou SOAP webové služby. Ale abych věděl, o čem všichni mluví, občas si přečtu i nějakou tu knihu o novinkách. Onehdá jsem studoval Scalu (vřele doporučuji) a teď jsem zrovna dočetl knihu RESTful Web Services od pánů Richardsona a Rubyho.

Pokud se chcete dozvědět co to vlastně ten REST je, tak je to ta pravá kniha pro vás. Dozvíte se tam hodně o filozofii RESTu, o jeho výhodách, o tom jak takové služby navrhovat a jaké technologie k tomu používat. Většina příkladů je sice napsána v jakémsi obskurním jazyce jménem Ruby, ale pochopí je i Javista.

Kniha je napsána i docela čtivě, musíte jenom překousnout demagogii typu „REST je nejlepší, RPC je k ničemu“. Co se mi hodně líbilo je to, že jsem snad konečně pochopil principy RESTu. Je to totiž jedna z těch technologií, o kterých všichni mluví, ale málokdo ji plně rozumí. Pokusím se tu shrnout to nejdůležitější.

Dělej to jako Web

To je asi hlavní krédo RESTovaných aplikací. Web funguje, je snadno použitelný, dobře škáluje, tak proč bychom nemohli dělat webové služby úplně stejně. Je tu veliký rozdíl v tom, že normální web je určen pro lidi a webové služby pro stroje. Ale proč nepoužít stejných osvědčených principů?

Zdrojově orientované

Zdrojově orientované je můj geniální překlad pro Resource-Oriented. Znamená to, že se místo na služby, orientujeme na zdroje. Místo na slovesa typu udělej, založ, smaž se soustředíme na podstatná jména typu účet, zákazník a podobně. Je to podobné jako objektové programování, v něm bychom se měli soustředit hlavně na objekty samotné, jejich operace by z nich měli vyplývat. Stejné princip by měl platit u RESTu. Tady musím dát příznivcům RESTu za pravdu, normální WS jsou silně funkcionální, o objektech tam není ani památky. Na druhou stranu je spousta služeb, které se jako RESTové jenom tváří, ale v podstatě jsou také jen RPC.

Adresovatelnost

Další důležitý princip je adresovatelnost. Každá věc která je v daném systému důležitá, by měla mít vlastní adresu. To znamená, že každá instance objektu, by měla mít vlastní URI. Hodně to souvisí s orientací na zdroje. Každý zdroj, má svoji adresu, například takovouto http://www.exaple.com/v1/bank/client/12345. Pokud použijeme přirovnání k objektům, tak URI je podobná referenci.

Propojenost (Connectedness)

Propojenost hodně souvisí s předchozími body. Znamená to, že jako výsledek operací dostávám odkazy na další zdroje. Pokud se například zeptám na seznam klientů banky, měl bych dostat seznam jejich URI a ne jen jejich ID. Pokud bereme URI jako referenci na instanci, tak to dává smysl.

Idempotentnost

Krásné české slovo označující, to, že by mělo být možné zavolat jednu službu vícekrát, aniž by hrozilo něco ošklivého. To znamená, že operace by pokud možno neměly mít žádné vedlejší účinky. Pokud mění stav systému, měly by to dělat tak, aby se nic nestalo, pokud ho změní vícekrát. Je překvapivé, že takových operací je valná většina. Nic se nestane, pokud například vícekrát nastavím stejnou hodnotu nebo něco smažu. Jsou tu samozřejmě výjimky, ale i u těch se dá idempotentnost zařídit.

Bezstavovost

Bezstavovost je často nepochopené téma. A není se čemu divit, je to hodně matoucí. Pokusím se to ale vysvětlit. Bezstavovostí se v tomto případě myslí, že webová služba neukládá stav klienta. Rozhodně tam není žádná session. Klient může navíc volat operace v libovolném pořadí, takže by se nemělo objevovat žádné flow jak ho známe z normálního webu. Ale aplikace samotná samozřejmě stav má. Takže si samosebou pamatuje, kolik kdo má na účtu, kam chce letět na dovolenou nebo kam rád chodí do restaurace. Ale nejedná se o stav klientské aplikace, jedná se o stav zdrojů. V tom je veliký rozdíl.

Bezstavovost je docela rozumný princip, díky ní se například dobře škáluje. Bohužel často k jejímu dodržení musíme švindlovat. Občas prostě potřebujeme držet stav klientské aplikace. V RESTu proto dělají krok stranou a prohlásí stav klienta za stav systému. Takže když potřebují uložit obsah nákupního košíku, řeknou, že to není stav klienta, ale stav aplikace. Nákupní košík je vlastně takový zdroj, jehož stav aplikace naprosto přirozeně drží. Takový košík má pak svoji URI, své operace a je to prostě normální zdroj. Problém vyřešen. Tedy kromě té škálovatelnosti, s ní jsem na tom úplně stejně jako kdybych používal session. (Kvízová otázka. Jak zajistit, aby přidání položky do nákupního košíku bylo idempotentní?)

No zbytek z těch asi 400 stránek tu opisovat nebudu. Radši napíšu proč se budu držet dál normálních (tlustých) webových služeb.

V první řadě se mi nelíbí úzká vazba na HTTP. To je jedna z věcí, která mi ještě nedošla, takže budu rád když mi ji někdo vysvětlí. Na jedné straně čtu, že REST je na HTTP úplně nezávislý. Na druhé straně, je ale REST s HTTP neuvěřitelně úzce spjat. Chcete-li změnit stav zdroje, máte použít HTTP PUT, chcete-li ho smazat, použije HTTP DELETE, chcete zjistit jeho stav, použijte GET. Nastane chyba? Dostanete ten a ten HTTP chybový kód. Tak je to závislé nebo ne? Mě spíš připadá že jo.

Dalším důvodem, proč zatím zůstanu u klasického ošklivého SOAPu je to, že mi vyhovuje. Já prostě nepíši aplikace, které jsou podobné webovým stránkám. Píši věci, které jsou svoji podstatou spíše procedurální než objektové. Navíc mi vyhovuje to, že existuje něco jako WSDL, které je sice ošklivé jak noc, ale které i přes své mouchy funguje. Ano pro REST máme WADL, ale že by byl široce rozšířen se říci nedá.

Také mi vyhovuje, že v SOAPu jsou metadata součástí zprávy a ne protokolu, kterým se zpráva posílá. Prostě mi to připadá bezpečnější. Když volám webovou službu tak volání bohužel často přejde přes několik JMS front a pár HTTP připojení. Takže se na to radši dívám tak, že posílám XML dokument, který přes nějaké složitosti snad doputuje k svému adresátu.

Tím rozhodně nechci říci, že je REST špatný. Pokud bych vystavoval webové služby pro něco, co má svůj ekvivalent ve webové aplikaci, asi bych nad RESTem vážně uvažoval. Protože se ale pohybuji v jiném světě, tak je moje volba jiná. To mi ale nebrání se poučit a pár fint od RESTu okoukat.