funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

Async SQL

26. 4. 2013 (před 6 lety) — k47

JDBC blo­kuje.

JDBC blo­kuje a to před­sta­vuje pro­blém pro novou ge­ne­raci async web fra­meworků jako např. Play! je­jichž srdce je zcela asyn­chronní a re­ak­tivní a bije na thread poolu stejně velkém jako je počet pro­ce­so­ro­vých jader, který nikdy ne­blo­kuje ak­tivní vlákno. Ne­blo­kuje a ani nemůže.

Pokud z ně­ja­kého důvodu v ta­ko­vémto pro­středí po­u­ží­vám JDBC, musím pro­vá­dět blo­ku­jící ope­race ve vlast­ních vlák­nech. Můžu je za­ba­lit do blo­ku­jící Future a doufat, že si s tím nějak poradí:

furure { blocking { blockingCall() } }

Nebo pro blo­ku­jící ope­race vy­tvo­řit se­pa­rátní thread pool a doufat, že bude stačit.

Ale to jsou jenom ber­ličky. Ide­ální je, když mám k dis­po­zici asyn­chronní driver jako třeba právě Async SQL. Ten není po­sta­ven na JDBC (viz za­čá­tek článku), jde o im­ple­men­taci MySQL ko­nek­toru na zelené louce, který pod ka­po­tou po­u­žívá Akka fra­mework a je zcela asyn­chronní.

Asyn­chro­ni­cita se pro­je­vuje tak, že vý­sled­kem kaž­dého dotazu je Future:

val database = Database(actorSystem)

val res: Future[Data] = for {
  conn <- database.connect()
  data <- conn.executeQuery("select ...")
  _    <- conn.close()
} yield data

res onSuccess { case data         => println(data) }
res onFailure { case e: Exception => println("oh noes!") }

Jenom tohle nám ušetří spoustu trá­pení v asyn­chron­ním světě, ale ne­po­radí si to s Příliš Vel­kými Dotazy. Takové dotazy můžou vrátit mi­li­ony řádků, mnohem víc než se mi vejde do paměti, ale já je chci hned stre­a­mo­vat pryč nebo je chci nějak za­gre­go­vat do řádově menší datové struk­tury (na­pří­klad zre­du­ko­vat korpus textu na čet­nost výskytu jed­not­li­vých slov, možná do­konce do nějaké prav­dě­po­dob­nostní top-k struk­tury).

Když se s tím chci vy­po­řá­dat ve světě JDBC, musím použít ne­bu­f­fe­ro­vané dotazy. Ty sice ne­po­tře­bují ma­te­ri­a­li­zo­vat vý­sle­dek v apli­kaci, ale pořád blo­kují. A blo­ko­vání je špatné (viz za­čá­tek článku).

Async SQL se umí i s tímhle ele­gantně po­ra­dit. Umí totiž re­a­go­vat na data tak, jak při­chá­zejí po síti – tedy zcela re­ak­tivně!

val res: Future[X] = for {
  conn <- database.connect()
  data <- conn.executeQuery("select ...", initialValue) {
    case StartRow(composite)      => combine(composite, "start-row")
    case AValue(value, composite) => combine(composite, value)
    case EndRow(composite)        => combine(composite, "end-row")
  }
  _    <- conn.close()
} yield data

Stan­dardní API umí re­a­go­vat jenom na jed­not­livé sloupce, ne na celé řádky, ale to se dá jednou ne­pří­liš slo­ži­tou funkcí na­pra­vit.

Pozn: vypadá to, že autor vydal novou verzi pro Scalu 2.10, která už ne­po­tře­buje Akku.

@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články