Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

sql.Openとsql.OpenDBの違い、そしてドライバーごとにsql.OpenDBを使うべきかの検討

id:aerealです。Goの話をします。

sql.Openとsql.OpenDBの違い

GoでRDBMSなどに繋ぐ際にはふつうdatabase/sqlを使います。 ORMを使う場合でも内部的にはこのパッケージに依存していることがほとんどです。

特定のデータベースに対して接続を確立したりクエリを実行する実装をドライバーと呼び、契約によって定められたインターフェースを実装したドライバーを利用者がdatabase/sqlに渡すことで、拡張性と独立性を実現しています。

sql.DBa database handle representing a pool of zero or more underlying connections.と説明されており、いわゆるアプリケーションレベルのコネクションプールを表現した構造体といってよいでしょう。 Perl monger的にはsql.DBはDBIx::Handlerに相当するものというとわかりやすいでしょう。

sql.DBを得る方法は2つあり、ひとつはsql.Open, もうひとつはsql.OpenDBです。 sql.OpenDBはGo 1.10で追加されたAPIでより新しいです。

新しいAPIが導入された動機は以下の通りです。

Drivers that want to construct a sql.DB for their clients can now implement the Connector interface and call the new sql.OpenDB function, instead of needing to encode all configuration into a string passed to sql.Open.

Drivers that want to parse the configuration string only once per sql.DB instead of once per sql.Conn, or that want access to each sql.Conn’s underlying context, can make their Driver implementations also implement DriverContext’s new OpenConnector method.

sql.Openはdata source name (以下、単にDSN) と呼ばれるDBへ接続するための情報を文字列として受け取ります。

DBへ接続する際は、ふつうドライバーが文字列から構造体へ変換してたとえば接続先のホストやポートなどを抽出して使用します。

この変換処理は新しい接続を確立する度に行われますが非効率です。

加えて単純な文字列であるDSNを用いる場合、関数やポインタなどリッチな言語表現が使えません。

たとえばイベントに応じて呼ばれるコールバック関数を受け取りたいという場合、標準のdatabase/sqlのAPIだけでは相互運用できず不便です。

こうした主に2つの問題を解決するために新しいsql.OpenDBというインターフェースが導入されました。

ドライバーごとの接続情報の事情

ここでは主にgo-sql-driver/mysqljackc/pgxの話をします。

まずこれらドライバーの接続設定を表す構造体は以下の通りです:

mysqlのほうは比較的単純ですがpgxはOnNoticeやOnPgErrorなど関数として持つフィールドが散見されます。

関数や構造体 (のポインタ) をとるフィールドを見ているとDSNをパースして得られる値と同等の値をアプリケーション内で組み立てられるのかが気になってきます。 つまりデフォルト値が公開されているのか、それともコピペしなければいけないのかということです。

pgxのDSNをパースする処理を見ると、BuildFrontendやBuildContextWatcherHandlerなどの関数のデフォルト実装そのものは公開されていませんが、公開メンバのみを使っているのでコピペすることは可能そうです。

気になるのがTLS接続の設定を決めるconfigTLSという実装です。

これはパラメータに加えてファイルシステムに配置された証明書の状況に応じてTLS接続に用いるオプションを決めるもので、最終的にtls.Configを返すのですが、かなり込み入っていることが一目でわかります。

この複雑なロジックはlibpqのsslmodeなどのパラメータ定義に倣ったもので、利用者にとっても明確な定義が外部にあることは嬉しいことが多いのですが、このconfigTLS関数は現時点で公開されておらずコピペするほかありません。

アプリケーションにおいてTLS接続設定の決定は、これほど柔軟である必要はないでしょう。せいぜい商用環境でTLS接続を厳格な設定で用い、ローカルでは無効にする程度の設定で十分です。

しかし先に述べた関数を値にとるフィールドが少なからずあることなどを踏まえると、ライブラリのデフォルト値にせいぜいホスト名やポート番号程度を加工した程度のpgconn.Configを得るためにpgconn.ParseConfigを用いずスクラッチから組み立てる実装を作るのはかなり不合理に思えます。

まとめ

database/sqlの簡単な歴史について触れ、MySQLとPostgreSQLのドライバー実装を読み解き、「ライブラリが提供するパース関数を使うか、それとも構造体をスクラッチから作るか」の実用性について検討しました。

変換処理が明らかにボトルネックでない場合、つまり文字列ではなく構造体として設定を持ち回ることで見通しをよくすることだけが目的であれば、go-sql-driver/mysqlはsql.OpenDBに移行してよい・jackc/pgxは割に合わないので従来通りsql.Openでも構わないという評価に落ち着きました。

MySQLとPostgreSQL両方に繋ぐアプリケーションで統一感を図ろうと考えたことがきっかけですが、結果的にこれまできちんと読んでこなかったドライバーの実装を読み進められました。

go-sql-driver/mysqlはフラットなパッケージ構成で極めてシンプルな実装でそれが設定構造体にも表れており、対してjackc/pgxはもともとlib/pqに欠けていた機能を補完するという目的があったためか現時点でも十分にリッチでかつ拡張性も見据えた実装になっています。

最後に、どちらも活発に開発されているライブラリなので記事の記述と実装に乖離がある場合、最新の実装が常に正しい点をご留意ください。

© 2020 Classi Corp.