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)]