Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

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を当てれるようになりますよ!