greenletを使う場合にはPostgreSQLを使う方がいいという話
こんにちわ、Python界の便所虫ことmopemopeです。
前から書こうとしていた話を書いておきます。
最近、eventlet、geventが市民権を得てきましたね。
特にgunicorn経由で使用しはじめてる人も多いんじゃないかなと思います。
まあこれらは NonBlocking IO + 協調スレッドなわけなんですが、全部をうまく
非同期で処理できるわけではありません。
サードパーティのライブラリがC拡張であると割り込めないのでどうしようもないのです。
サードパーティのライブラリで一番使っているのはそうです、DBドライバです。
肝心のDBドライバがブロックしちゃうともったいないわけです。
これって問題だなーと思ったpsycopg2の人たちは拡張できる仕組みを入れました。
(2.2以降)
eventletを例に見てみましょう。
psyco_eventlet.py
import psycopg2 from psycopg2 import extensions from eventlet.hubs import trampoline def make_psycopg_green(): """Configure Psycopg to be used with eventlet in non-blocking way.""" if not hasattr(extensions, 'set_wait_callback'): raise ImportError( "support for coroutines not available in this Psycopg version (%s)" % psycopg2.__version__) extensions.set_wait_callback(eventlet_wait_callback) def eventlet_wait_callback(conn, timeout=-1): """A wait callback useful to allow eventlet to work with Psycopg.""" while 1: state = conn.poll() if state == extensions.POLL_OK: break elif state == extensions.POLL_READ: trampoline(conn.fileno(), read=True) elif state == extensions.POLL_WRITE: trampoline(conn.fileno(), write=True) else: raise psycopg2.OperationalError( "Bad result from poll: %r" % state)
とまあこれだけです。
読み書きできる状態になるまで待ち合わせるcallbackを設定します。
eventletでは読み書きできる状態になるとスレッドを再開されるtrampolineがあるのでそれを呼び出します。
これでクソ重いクエリを待続ける必要はなくなりましたね。
実際使用した例
import eventlet eventlet.monkey_patch() import psyco_eventlet psyco_eventlet.make_psycopg_green() import urllib2 # green import psycopg2 import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") logger = logging.getLogger() logger.info("testing psycopg2 with eventlet") conn = psycopg2.connect("dbname=xxxxx") def download(num, secs): url = "http://localhost:8000/%d/" % secs for i in range(num): logger.info("download %d start", i) data = urllib2.urlopen(url).read() logger.info("download %d end", i) def fetch(num, secs): cur = conn.cursor() for i in range(num): logger.info("query %d start", i) cur.execute("select pg_sleep(%s)", (secs,)) logger.info("query %d end", i) logger.info("making jobs") pool = eventlet.GreenPool() pool.spawn(download, 2, 3), pool.spawn(fetch, 3, 2), logger.info("join begin") pool.waitall() logger.info("join end")
冒頭のパッチ以外は全く通常のpythonコードですね。
greenletを使う場合にはPostgreSQLを使った方がいいよという話でした。
ちなみにmeinheld 0.4からはeventletなどと同様、socketにmonkey_patchを当てれるようになりますよ!