You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Doobie测试超出PostgreSQL最大连接数问题排查

PostgreSQL连接数耗尽问题排查与解决(Doobie IOChecker测试场景)

问题根源

  1. 连接池重复初始化
    测试类中用def定义transactor,导致每个测试方法执行时都会重新初始化一个全新的Hikari连接池。DatabaseConfig.transactor是Resource[IO, HikariTransactor[IO]]类型,每次调用allocated.unsafeRunSync()都会启动独立的连接池实例,每个池默认占用10个连接。多个测试类/方法叠加后,连接数快速超出数据库max_connections限制。

  2. 资源未及时释放
    即使尝试用afterAll,如果没有执行allocated返回的释放动作(即allocated.unsafeRunSync()._2),旧连接池的连接不会被回收,持续占用数据库连接。

  3. Doobie Check的连接累积
    Doobie的check方法会通过transactor执行元数据查询验证SQL,若每个check对应独立的连接池,连接占用会进一步累积。

解决方案

1. 测试类复用单一连接池并确保资源释放

将测试类中的transactor改为lazy val,只初始化一次连接池,并在测试结束后执行释放逻辑:

class RegionDAOTest extends AsyncFlatSpec with AsyncIOSpec with IOChecker {

  // 初始化连接池,同时保存释放动作
  private val (transactor, releaseTransactor) = DatabaseConfig.transactor.allocated.unsafeRunSync()
  val dao = new RegionDAO

  // 测试套件结束后释放连接池
  override def afterAll(): Unit = {
    releaseTransactor.unsafeRunSync()
    super.afterAll()
  }

  "RegionDAO" should "generate a valid SQL for all regions query" in {
    IO(check(dao.allQuery))
  }

  it should "generate a valid SQL for a single region query" in {
    IO(check(dao.regionQuery(1)))
  }

  it should "generate a valid SQL for insert query" in {
    val regionCreate = RegionCreate("New Region", 1)
    IO(check(dao.createQuery(regionCreate)))
  }

  it should "generate a valid SQL for update query" in {
    val country = CountrySimple(1, "Country")
    val region  = Region(1, "Updated Region", country)
    IO(check(dao.updateQuery(region)))
  }

  it should "generate a valid SQL for delete query" in {
    IO(check(dao.deleteQuery(1)))
  }
}

2. 配置测试专属的Hikari连接池参数

测试环境不需要大连接池,显式缩小maxPoolSize减少单池连接占用:

object DatabaseConfig {
  // 测试环境可通过环境变量设置更小的池大小
  private val maxPoolSize = Properties.envOrElse("HIKARI_MAX_POOL_SIZE", "10").toInt

  lazy val transactor =
    (for {
      url      <- Properties.envOrNone("POSTGRES_URL")
      user     <- Properties.envOrNone("POSTGRES_USER")
      password <- Properties.envOrNone("POSTGRES_PASSWORD")
    } yield HikariTransactor
      .newHikariTransactor[IO](
        "org.postgresql.Driver",
        url,
        user,
        password,
        ExecutionContext.global,
        Some(LogHandler.jdkLogHandler)
      )
      .configure { hikari =>
        hikari.setMaximumPoolSize(maxPoolSize)
        hikari.setMinimumIdle(1) // 测试环境保持最小空闲连接
        hikari.setConnectionTimeout(3000) // 缩短连接超时
        hikari
      }
    ).getOrElse(throw new IllegalStateException("Missing PostgreSQL environment variables"))
}

在测试环境设置环境变量:export HIKARI_MAX_POOL_SIZE=2,将每个连接池的最大连接数限制为2。

3. 统一所有测试类的资源管理逻辑

确保所有使用IOChecker的测试类都遵循上述复用连接池+释放资源的逻辑,避免多个测试类各自创建独立连接池。

4. 实时监控连接数排查泄漏

执行PostgreSQL查询查看实时连接:

SELECT count(*) FROM pg_stat_activity WHERE datname = 'your_test_db';

结合测试执行进度,定位哪些测试/进程在占用连接,确认是否存在未释放的连接池。

验证

修改后重新运行测试,使用上述SQL查询连接数,确认连接数稳定在预期范围内(比如单测试套件占用2-5个连接,多套件累加后不超过数据库max_connections)。

内容的提问来源于stack exchange,提问作者Björn Pedersen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:43:20