Goでsqlcを使ってデータベースを管理する:実際のパフォーマンスと究極の型安全性

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Goのデータベース選定:生SQLかORMか?

新しいGoプロジェクトを開始する際、データベースライブラリの選択は常に開発者を悩ませる問題です。通常、以下の3つの選択肢があります。

  • 標準ライブラリ (database/sql): 最速ですが、非常に手間がかかります。各カラムを手動で構造体にマッピングする必要があり、スキーマが少し変更されるだけでコードがクラッシュしやすくなります。
  • ORM (GORM, Ent): 開発スピードが速く便利です。しかし、リフレクションの多用によりパフォーマンスが大幅に低下します。また、制御が難しい5〜6層ものJOINを含む肥大化したクエリが自動生成されることもあります。
  • SQL Builders (Squirrel): コードでクエリを書く問題を解決しますが、バックエンドエンジニアが求める厳密な型安全性(type-safety)が不足しています。

多くのプロジェクトを経て得た結論は、「SQLは依然としてデータクエリに最適な言語である」ということです。SQLを避けるのではなく、最適化する方法を見つけるべきです。それが、私がsqlcを第一の選択肢としている理由です。

sqlc:SQLがコンパイラの力を得る時

sqlcは一般的なORMではありません. 実態は強力なコンパイラです。生SQLを書くだけで、sqlcがスキーマを読み取り、対応するGoコードを自動生成します。

大きな違いは、generateコマンドを実行した瞬間にSQLのチェックが行われる点です。カラム名の指定ミスやデータ型の不一致があれば、コンパイラが即座にエラーを報告します。ランタイムでクエリミスによる予期せぬクラッシュを心配する必要はもうありません。

なぜsqlcを使うべきなのか?

  • 圧倒的なパフォーマンス: 生成されるコードは純粋なGoコードです。中間層を介さず、データベースドライバを直接呼び出します。ベンチマークでは、sqlcはGORMよりも2〜3倍高速です。
  • 究極の型安全性: すべての入力と出力が明確な構造体として定義されます。これにより、Goコンパイラの型チェック機能を最大限に活用できます。
  • SQL最適化の自由度: PostgreSQLやMySQLの特有の機能をすべて利用できます。ORMの抽象化された関数に制限されることはありません。

考慮すべき制限事項:

  • 動的SQL(例:条件によってフィルタが変わるクエリ)の処理がやや複雑です。
  • 開発プロセスがコード生成ステップに依存するため、チーム内でワークフローを統一する必要があります。

Fintechプロジェクトでの実体験

以前、約5万行のコードを持つFintechシステムリファクタリングに参加しました。当初、チームは開発スピードを優先してGORMを使用していました。しかし、レコード数が数百万件に達すると、クエリの遅延が顕著になりました。

sqlcに移行した際、15個以上の潜在的なバグを発見しました。その多くは、GORMが見逃していたint4int8のデータ型の不一致でした。移行後、主要なAPIのレイテンシは200msから50ms以下に短縮されました。

5ステップで始めるsqlc導入ガイド

まずは、PostgreSQL上のシンプルなauthorsテーブルを管理してみましょう。

ステップ1:ツールのインストール

sqlcのインストールは、HomebrewまたはGo installで簡単に行えます。

# macOSの場合
brew install sqlc

# またはGoで直接インストール
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest

ステップ2:sqlc.yamlの設定

SQLの場所とGoコードの出力先を指定する設定ファイルを作成します。

version: "2"
sql:
  - schema: "schema.sql"
    queries: "query.sql"
    engine: "postgresql"
    gen:
      go:
        package: "db"
        out: "db"

ステップ3:スキーマとクエリの作成

schema.sqlファイルでテーブル構造を定義します。

CREATE TABLE authors (
  id   BIGSERIAL PRIMARY KEY,
  name text      NOT NULL,
  bio  text
);

query.sqlに操作用のSQLを記述します。関数名を指定するために -- name を使用することに注意してください。

-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;

-- name: CreateAuthor :one
INSERT INTO authors (name, bio) 
VALUES ($1, $2) 
RETURNING *;

ステップ4:コード生成の実行

ターミナルで以下のコマンドを実行してコードを生成します。

sqlc generate

sqlcはクエリロジックをすべて含むdbフォルダを自動生成します。これらのファイルを手動で修正してはいけません。

ステップ5:Goプロジェクトへの統合

これで、データベースの呼び出しが非常に簡潔かつ安全になります。

func main() {
	ctx := context.Background()
	conn, _ := sql.Open("postgres", "user=postgres dbname=test sslmode=disable")

	queries := db.New(conn)

	// 型安全に新しい著者を作成
	author, err := queries.CreateAuthor(ctx, db.CreateAuthorParams{
		Name: "田中 太郎",
		Bio:  sql.NullString{String: "バックエンドエンジニア", Valid: true},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("新規作成されたID:", author.ID)
}

効率的に使用するためのヒント

ワークフローを最適化するために、以下のテクニックの適用をお勧めします。

  1. Makefileの活用: sqlc generateコマンドをビルドプロセスに組み込み、コードが常にSQLと同期されるようにします。
  2. マイグレーションツールとの併用: sqlc自体はデータベースのバージョン管理を行いません。golang-migrategooseを使用して、プロフェッショナルなスキーマ管理を行いましょう。
  3. データ型のカスタマイズ: sql.NullStringが好みでない場合は、設定のoverrides機能を使用して、ポインタ型の*stringなどに変更できます。

実行速度と安全性のバランスが取れたソリューションが必要なら、sqlcこそが求めていたピースです。GoでのスムーズなSQL開発体験を楽しんでください!

Share: