サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

psycopg2とasyncpgの違い:同期処理と非同期処理、性能、トランザクションを比較

close up photo of mining rig

Photo by panumas nikhomkhai on Pexels.com

PythonからPostgreSQLを操作するとき、psycopg2asyncpgでは、データベースへの問い合わせ方と待ち時間の扱いが異なります。

同期処理を前提とする既存アプリケーションではpsycopg2が扱いやすく、asyncioを使うアプリケーションでデータベース待ちの間にも別の処理を進めたい場合はasyncpgが候補になります。ただし、非同期であることだけを理由に、すべてのクエリが速くなるわけではありません。実際の選択では、アプリケーション全体の実行方式、利用するフレームワーク、同時処理の量、接続管理を合わせて考えます。

psycopg2とasyncpgの比較

psycopg2とasyncpgの主な違い
比較項目 psycopg2 asyncpg
基本の実行方式 同期処理 asyncioを使う非同期処理
呼び出し方 通常の関数呼び出し asyncawaitを使用
SQLの値の指定 %s $1$2のような番号付き
トランザクション commit()rollback()で明示的に終了 async with connection.transaction()で範囲を表現できる
合わせやすい構成 同期型の処理や既存コード asyncioを採用した非同期アプリケーション

この表は設計上の違いを示すものであり、実測性能の順位ではありません。処理時間はSQL、データ量、インデックス、ネットワーク、接続の再利用方法などにも左右されます。

同期処理と非同期処理の違い

同期処理では、データベースから結果が返るまで、その処理の流れは次へ進みません。この待ち方をブロッキングI/Oと呼びます。コードを上から順に追いやすく、バッチ処理や管理用ツールのように同時処理が少ない用途では、構成を簡潔に保ちやすい方法です。

非同期処理では、データベースの応答を待つ間に、イベントループが別の処理へ実行機会を渡します。多数のリクエストが同時に発生し、それぞれがネットワークやデータベースの応答を待つ構成では、待ち時間を使って別の仕事を進められます。

ただし、非同期処理はデータベース側のSQL実行そのものを短縮する仕組みではありません。非同期の利点は、主に複数の待ち時間を重ねて扱えることにあります。CPU負荷の高い処理や遅いSQLは、別途見直す必要があります。

psycopg2の特徴

psycopg2は、PythonからPostgreSQLへ接続するデータベースアダプターです。通常の同期APIを使う場合は、接続を作り、カーソルでSQLを実行し、結果を受け取るという順序がコードにそのまま現れます。

psycopg2が合う場面

導入時に確認したい点

同期APIでデータベースの応答を待っている間は、その実行の流れが止まります。同時リクエストを処理するアプリケーションでは、スレッドやプロセスを含めた並行処理の設計が必要です。

「同期処理だから高負荷用途には使えない」と一律に判断することはできません。必要な同時処理量を満たせるか、接続数と応答時間を測って判断します。

asyncpgの特徴

asyncpgは、Pythonのasyncioを前提とするPostgreSQLクライアントです。接続、SQL実行、結果取得をawaitするため、待機中にイベントループがほかのタスクを進められます。

asyncpgが合う場面

FastAPIを含む構成でも、フレームワーク名だけで採用を決めるのは早計です。エンドポイントからデータアクセス層まで非同期でつなぐのか、同期コードをどこに置くのかを先に決めます。API全体の判断項目は、PythonとFastAPIで業務APIを作る前に決めることでも確認できます。

導入時に確認したい点

asyncawaitを付けるだけでは、アプリケーション全体が非同期になるわけではありません。同じ処理経路に長時間の同期I/Oが残ると、イベントループを止める原因になります。データベースアクセスだけでなく、その前後の処理も確認します。

性能を比較するときの考え方

性能比較では、一回のクエリが返るまでの時間と、一定時間に処理できるリクエスト数を分けます。前者はSQLやデータベース設計の影響を強く受け、後者は待ち時間の扱い、接続数、アプリケーションの並行処理方式にも影響されます。

asyncpgは、複数のI/O待ちが重なる非同期アプリケーションで処理を組み立てやすい一方、psycopg2でも要件に合う並行処理と接続管理を設計すれば運用できます。ライブラリ名だけを変えたベンチマークではなく、次の条件をそろえて測ります。

トランザクションの書き方

トランザクションとは、複数のデータベース操作を一つの処理単位として扱う仕組みです。途中で失敗した場合は変更を取り消し、すべて成功した場合だけ確定させます。

psycopg2の例

psycopg2では、成功時にcommit()、失敗時にrollback()を呼ぶ流れを明示できます。

import psycopg2

conn = psycopg2.connect(dsn)
try:
    with conn.cursor() as cur:
        cur.execute(
            "INSERT INTO sample_table (col) VALUES (%s)",
            (value,),
        )
    conn.commit()
except Exception:
    conn.rollback()
    raise
finally:
    conn.close()

例外を再送出することで、呼び出し元も失敗を検知できます。ロールバックだけを行って例外を握りつぶすと、処理が成功したように見えるため注意が必要です。

asyncpgの例

asyncpgでは、トランザクションに含める処理を非同期コンテキストマネージャーで囲めます。

import asyncio
import asyncpg

async def main():
    conn = await asyncpg.connect(dsn)
    try:
        async with conn.transaction():
            await conn.execute(
                "INSERT INTO sample_table (col) VALUES ($1)",
                value,
            )
    finally:
        await conn.close()

asyncio.run(main())

両方の例で値をSQL文字列へ直接埋め込まず、ライブラリが用意するプレースホルダーを使っています。違いは、psycopg2が%s、asyncpgが$1形式である点です。

選択前のチェックリスト

選び方の結論

同期処理を中心とする既存アプリケーションや、小規模なバッチと管理ツールでは、psycopg2の単純な実行モデルが適しています。アプリケーション全体をasyncioで構成し、多数のI/O待ちを並行して扱う場合は、asyncpgが候補になります。

最終判断は「どちらが常に速いか」ではなく、「どちらが現在の実行方式と運用条件に合うか」で行います。候補を絞った後は、同じSQLと同じ負荷条件で測定し、性能と保守性の両方を確認してください。

モバイルバージョンを終了