猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

Heroku PostgreSQL にて Scaka Play Framework 2.6 で Slick と play-slick を利用して HikariCP でコネクションプール(スレッドプール)を使う!

f:id:yoshiki_utakata:20190813001137p:plain

Scala Play Framework 2.6 と Slick でコネクションプール(スレッドプール)の設定がしたい!

Scala の Play Framework と DB アクセスの Slick 、そして Play Framework のモジュールである play-slick を利用して Connection Pool を張った際にごにゃごにゃ設定したことのまとめです。

環境

  • Scala 2.11
  • Play Framework 2.6
  • Slick 3.2.3
  • play-slick 3.0.3
  • PostgreSQL 10.9

前置き

コネクションプール/スレッドプールを使うモチベーション

僕の過去の記事があるのでそちらを参考にしてください。

www.utakata.work

コネクションプールとスレッドプールの違いは?

結局スレッドが20個プールしてあったらコネクションも20個あったほうが良いので、スレッドプールとコネクションプール、は同じものと考えて良いと思います。非同期の文脈の場合は「スレッドプール」と呼ぶっぽいです。

なぜ play-slick を使うようにしたのか

play-slickとは、Play Framework の application.conf をもとにいい感じに Slick を使えるようにするやつです。

↑の方で上げた記事で書いたように、もともとは play-slick を使わずに、Play と Slick を別々に入れて使っていました。コネクションプールも問題なく使えていました。しかし、Play 2.6 にバージョンアップした瞬間、maximumPoolSize が効かなくなりました。

maximumPoolSize が効かなくなり、おそらくデフォルトの設定が適用されるようになったのでしょう。コネクションプールが20以上のコネクションを求めるようになりアプリケーションが死ぬようになりました。

おそらくconfigのmaximumPoolSizeがうまくSlickに渡せていないのだと思いますが、試行錯誤した結果何が間違っているのかよくわかりませんでした。

どうやって config 渡せばいいんだーと調べている間に、 play-slick が config の書き換えだけで導入できることがわかりました。どうせ config 書き換えるなら play-slick も入れてしまおう。もしかしたら play-slick 入れたらちゃんとコネクションプールできるかも、と思いました。

結果 play-slick 入れたら簡単にコネクションプールの設定もできたので、play-slick を入れてからコネクションプールを導入するまでの流れを説明しようと思います。

play-slick の導入

基本的に Play のドキュメントや play-slick のドキュメントを読んでいけばできるかと思います。

build.sbt に play-slick などを追加

build.sbt に依存パッケージを追加する必要があります。play-slick のドキュメントを見てみると。

  • Play 2.6.x の場合
    • Scala のバージョンは 2.11.x または 2.12.x
    • play-slick のバージョンは 3.0.x
    • Slick のバージョンは 3.2.x

と書いてあります。この条件を満たしかつこの記事を書いている時の最新バージョンは

  • play-slick 3.0.3
  • Slick 3.2.3

Scala のバージョンは 2.11.12 を使っていました。

ということでこうなりました。

// Scalaのバージョンは 2.11.12 を使う
scalaVersion := "2.11.12"

libraryDependencies ++= Seq(
  ...,
  // slick は 3.2.3 を使う
  "com.typesafe.slick" %% "slick" % "3.2.3",
  // 今回は PostgreSQL を利用する
  "org.postgresql" % "postgresql" % "42.2.6",
  // コネクションプールを使いたいので slick-hikaricp を入れておく
  // バージョンは slick のバージョンと合わせる
  "com.typesafe.slick" %% "slick-hikaricp" % "3.2.3",
  // play-slick を入れる。バージョンは 3.0.3
  "com.typesafe.play" %% "play-slick" % "3.0.3"
)

モデルからテーブルのマイグレーションを行う場合は play-slick-evolutions を使うようなのですが、今回僕はこれは使いません。

application.conf

以下のような設定を追記しました。

# DB (Slick)
slick.dbs = {
  default = {
    profile = "slick.jdbc.PostgresProfile$"
    db.driver = "org.postgresql.Driver"
    db.url = "jdbc:postgresql://127.0.0.1/lgtmoon?user=postgres&password=postgres"
    db.numThreads = 16
    db.maxConnections = 16
  }
}

slick.dbs = {...} と書いて、 {...} の中に default = ... を書くのは、 slick.dbs.default = ... と書くのと同じ意味になります。

slick.dbs 以下には default 以外にもいろいろな設定をもたせられるのですが、 DB への接続時に何も指定しないと default が使われることになるので、 default で設定します。

profile や driver の設定は Play のドキュメントなどを読んでください。探せば出てくるはず。

db.numThreds と db.maxConnections

これらはコネクションプール向けの設定になります。これを書くと勝手にコネクションプールになるようです。書かなければコネクションプールにならないっぽい。

一番重要なのは db.numThreads です。ここに設定したいコネクション数を指定します。 db.maxConnections が本当は設定したいコネクション数なのですが、指定しなければ db.numThreads と同じ値になります。 *1

ただし、過去には db.maxConnections = db.numThread * 5 がデフォルトになっていたこともあるようなので、ちゃんと db.numThreads と同じ値を設定しています。

ちなみに、 numThread >= maxConnections になっていないと warning が出ます。基本は同じ値を設定しておけば問題ないです。

DB接続部分のコード

Slickの基本的なコードの書き方はここでは説明しません。Slickのドキュメントや他のブログなどを参考にしてください。

import javax.inject.Inject
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.jdbc.JdbcProfile

class ImageDatabase @Inject() (
  protected val dbConfigProvider: DatabaseConfigProvider
) extends HasDatabaseConfigProvider[JdbcProfile] with ImageRepository {

  def getXxxx() = {
    ...
    db.run(action)
    ...
  }
}

まず重要なのが、 HasDatabaseConfigProvider[JdbcProfile] を extends します。これによって、プロパティ部分に db が定義されるので、db.run(action) が実行できるようになります。

問題はこの db に設定を渡す方法で、これを DatabaseConfigProvider 経由で渡すようにします。 protected val dbConfigProvider: DatabaseConfigProvider を定義すると、 HasDatabaseConfigProvider はこの dbConfigProvider を経由で接続設定をとってきてくれます。なのでこいつの名前は必ず dbConfigProvider でなければいけないし、 protected val でないといけません。

今回の場合特に接続の名前などは指定していないので、 default が使われます。default以外を使う方法はドキュメントなどを参照してください。 dbConfigProvider オブジェクトは @Inject を書いておけば勝手にInjectされます。

まとめ

これで Scala Play で play-slick を使ってコネクションプールを貼ることができるかと思います。