Database sql

16

Transcript of Database sql

Page 1: Database sql

database/sqlとコネクションTalos208

Page 2: Database sql

TL;DRdatabase/sqlには意図した使用法があるdatabase/sqlにはDBとの接続数を設定するAPIがいくつかある

ライブラリのソースも読んでみよう

Page 3: Database sql

Who am I株式会社スプラウト R&D Tech Lead組込み→Windowsゲーム→SIer(Web系BtoB)→セキュリティと流れてきたので、無駄に色々できる

Twitter : Talos208GitHub : https://github.com/Talos208

Page 4: Database sql

database/sqlgo標準のSQLライブラリ

使用法

db, err := sql.Open("driver name", "dsn")if err != nil { return nil, err}rows, err := db.Query(...)

db.Close()

……ってしたくなりますよね?

Page 5: Database sql

Db.Close()のドキュメントこんな記述が……

It is rare to Close a DB , as the DB handle is meant to be long‑lived and shared between many goroutines.

訳)

DBをCloseすることは、ほとんどない。なぜなら、DBハンドルは長期間生存して複数のgoroutine間で共有することを意図しているから。

Page 6: Database sql

ソースも見てみよう

type DB struct { driver driver.Driver dsn string numClosed uint64

mu sync.Mutex freeConn []*driverConn connRequests []chan connRequest numOpen int

// (後略)}

Page 7: Database sql

ソースも見てみよう(2)

func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { // (略) numFree := len(db.freeConn) if strategy == cachedOrNewConn && numFree > 0 { conn := db.freeConn[0] copy(db.freeConn, db.freeConn[1:]) db.freeConn = db.freeConn[:numFree-1] conn.inUse = true // (略) return conn, nil // freeConnの先頭を再利用 } // (略) db.numOpen++ // optimistically db.mu.Unlock() ci, err := db.driver.Open(db.dsn) // ここでOpen // (略)}

Page 8: Database sql

ソースも見てみよう(3)

func (db *DB) Close() error { // (略) var err error fns := make([]func() error, 0, len(db.freeConn)) for _, dc := range db.freeConn { fns = append(fns, dc.closeDBLocked()) } db.freeConn = nil // (略) for _, fn := range fns { err1 := fn() if err1 != nil { err = err1 } } return err}

Page 9: Database sql

ソースから読み取れたこと

 sql.Open() で返ってくるDB構造体は、コネクションプールを持

っている

接続をするときには、まずプールの空きコネクションを使用する

 DB.Exec() / DB.Query() などから自動的に DB.conn() が呼ばれる。あれば空きコネクションが利用され、無ければ新規コ

ネクションが取得される

 DB.Close() は、全ての空きコネクションを切断する

Page 10: Database sql

つまり

都度Open()/Close()すると、その度にコネクション接続/切断のコス

トがかかる

DBへのログインのコストもかかる

並行して複数のDB構造体を持つと、余計なメモリを消費する

これを避けるために

 sql.Open() は起動時に1回だけ呼ぶ

 DB.Close() は最後に1回呼ぶ

Page 11: Database sql

接続数の制限

func (*DB) SetMaxIdleConns

 func (db *DB) SetMaxIdleConns(n int) 

空きコネクションの最大数を指定。0だとコネクションプーリング

を行わない

func (*DB) SetMaxOpenConns

 func (db *DB) SetMaxOpenConns(n int) 

(空きも含めた)全コネクションの最大数。0だと上限なし

最大値しか設定できない。しばらく経つと、最大数に張り付いたままに

なる

Page 12: Database sql

What happen?症状

sql.Open()は出来るDB.Ping()も成功する

DB.Query()を実行するとコネクションエラー

????

Page 13: Database sql

Condition

サーバとDBとの間に、MariDB Maxscaleを使用していた

MaxscaleMySQL(MariaDB)向けのL7ロードバランサ

クエリ内容を見て SELECT はリードレプリカに、 UPDATE はマスターにとか出来る

Page 14: Database sql

Cause

1. サーバ <‑> Maxscaleのコネクションは存在Maxscaleとはつながるので、DB.Ping()は成功

2. Maxscale <‑> MySQLの接続がタイムアウトしていたサーバにタイムアウトが通知されない

3. クエリ発行時にコネクションエラー

じつはMaxscaleがエラーを出していた

Page 15: Database sql

SetConnMaxLifetime()Go1.6から新しいAPIが導入された

func (*DB) SetConnMaxLifetime

 func (db *DB) SetConnMaxLifetime(d time.Duration) 

一定時間以上経った接続は再利用しない

これを使うだけのためにGo1.4‑>Go1.6に移行

Page 16: Database sql

解決

1.  SetConnMaxLifetime() の設定により、サーバ <‑> Maxscale間のコネクションのほうが先に切れる

2. DB.Ping()の時点で、コネクションが再接続される

3. 正常にクエリ発行