PostgreSQL 9.2 新機能: libpq の単一行モード

本エントリは、PostgreSQL Advent Calendar 2012の12月2日分です。

PostgreSQL 9.2がリリースされてしばらく経ちますが、皆さんもうお使いでしょうか?IndexOnlyScanやCPUスケーラビリティ向上といった性能改善が大きなトピックになっていますが、細かいところにもいろいろ改善が入っています。このエントリでは、PostgreSQLのクライアントプロトコルであるlibpqに新しく追加された「単一行モード」をご紹介します。libpqはWebアプリケーション等ではあまり意識しないレイヤーかもしれませんが、psqlコマンドや言語別APIのベースとなっているプロトコル/ライブラリです。

どんな機能?

簡単にいうと、libpqプロトコルレベルでカーソルのように結果を一行ずつ処理できるようになるモードのことです。

9.1までのlibpqでは、SQLレベルで明示的にカーソルを定義しない限り、全ての検索結果が一つのPGresultオブジェクト(結果セット)に格納されて返ってきます。libpqはメモリ確保にmallocを使用しているので、結果セットのサイズによっては莫大なヒープメモリを消費してout of memoryになることもあります。また、全ての検索結果がクライアントに渡るまで処理がブロックされるというデメリットもありました。

これに対して、9.2で導入された「単一行モード」(Single-row mode)機能を用いると、PQexec()などの関数に(非カーソルの)単純なSELECT文を指定した場合でも結果を一行ずつ処理することができるようになります。この機能により

  • 検索結果全体の到着を待たずに処理を進められる
  • 一度に必要とするメモリ量が一定量に抑えられる

といったメリットがあります。もちろんトレードオフもあり、以下のデメリットもあります。

  • 各行ごとにPGresultを生成するオーバーヘッドがある

マニュアルには以下のような説明があります。

通常、libpqSQLコマンドの結果全体を収集し、それを1つのPGresultとしてアプリケーションに返します。 これは、多くの行数を返すコマンドでは動作しなくなるかもしれません。 こうした場合、アプリケーションはPQsendQueryとPQgetResultを単一行モードで使用することができます。 このモードでは、結果行は、サーバから受け取ったかのように、アプリケーションに1度に1行返されます。

使い方はこんな感じです(エラー処理は省いています)。

func(...)
{
    first = true;    /* 結果0件の検出用 */
    conn = PQconnect(...);
    PQsendQuery(conn, "SELECT * FROM tbl");
    PQsetSingleRowMode(conn);
    for (;;)
    {
        res = PQgetResult(conn);
        if (res == NULL)
        {
            /* 単一行モードでのクエリが完了した */
            break;
        }
        if (PQresultStatus(res) == PGRES_SINGLE_TUPLE)
        {
            /* 結果が返った場合の処理(結果の格納など) */
            ...

            /* 結果は毎行廃棄する */
            PQclear(res);
            first = false;
        }
        else
        {
            /* 最後の行を読んだ直後にPGRES_TUPLES_OKのPGresultが返る */

            if (first && PQresultStatus(res) == PGRES_TUPLES_OK)
            {
                /* 結果0行だった場合の特殊処理があればここに */
                ...
            }

            /* ここでも結果を明示的に廃棄する */
            PQclear(res);
        }
    }
}

注意点としては、

  • 単一業モードに入れるのはクエリ発行直後だけ
  • いったん単一行モードに入ったら、NULLを返すまでPQgetResult()を呼ばなければならない
  • NULLが返る直前にPGRES_TUPLES_OKのPGresultが返される

といった点があります。9.2に同梱されているcontrib/dblinkで使っていますので、使い方の参考になるかと思います。

おまけ:採用までの経緯

実は、9.2では当初「Custom Row Processor」機能が提案されコミットされました。この機能は、libpqがサーバから結果行を受け取るごとに呼び出されるコールバック関数を指定するAPIが追加されたもので、結果格納用のバッファの用意などかなり細かいところまでアプリケーションで制御できるようになりました。しかし、その反面でエラー発生時に検索結果の残り(未処理分)をどう扱うべきなのかなどの点で、アプリケーションがかなり細かいところまで気を使わないといけない「諸刃の剣」的な機能でした。実はこの機能の提案中からTom Lane(PostgreSQLの開発コアチームの一人)は使い方が複雑すぎると懸念しており、数十通のメールによる議論を経て「Custom Row Processor」機能は廃止され、代わりに「Single-row mode」が採用されました。