메인 콘텐츠로 건너뛰기
Sonamu에서 자주 발생하는 데이터베이스 연결 관련 문제와 해결 방법을 다룹니다.

DB 연결 리소스 누수

증상

Error: remaining connection slots are reserved for non-replication superuser connections
또는
Error: too many connections for database
장시간 개발 서버를 실행하거나, Fixture 작업을 반복할 때 발생합니다.

원인

DB 연결을 생성한 후 에러가 발생하면 destroy()가 호출되지 않아 연결이 정리되지 않고 누적됩니다. 문제가 있던 코드 패턴:
const db = createKnexInstance(config);

// 여러 작업 수행
await db.raw("SELECT ...");
await db.raw("UPDATE ...");  // 여기서 에러 발생

await db.destroy();  // 도달하지 못함

해결 방법

1. try-finally 패턴 사용

const db = createKnexInstance(config);

try {
  await db.raw("SELECT ...");
  await db.raw("UPDATE ...");
  return result;
} finally {
  await db.destroy();  // 에러 발생 여부와 관계없이 실행
}

2. 여러 DB 연결 정리

const sourceDB = createKnexInstance(sourceConfig);
const targetDB = createKnexInstance(targetConfig);

try {
  // 작업 수행
  const data = await sourceDB.select("*").from("users");
  await targetDB.insert(data).into("users");
  return data;
} finally {
  await targetDB.destroy();
  await sourceDB.destroy();
}

3. 현재 연결 수 확인

PostgreSQL에서 현재 연결 상태를 확인하려면:
SELECT 
  datname,
  count(*) as connections,
  max_conn
FROM pg_stat_activity
JOIN pg_database ON pg_stat_activity.datname = pg_database.datname
WHERE pg_database.datname = 'your_database_name'
GROUP BY datname, max_conn;

예방 방법

  • DB 연결을 직접 생성하는 경우 반드시 try-finally 사용
  • BaseModel의 메서드를 사용하면 자동으로 연결 관리됨
  • Fixture 작업 시 FixtureManager의 메서드 사용 (자동 정리 포함)

연결 설정 오류

증상

Error: connect ECONNREFUSED 127.0.0.1:5432

원인

  1. PostgreSQL 서버가 실행되지 않음
  2. .env 파일의 DB 설정이 잘못됨
  3. 포트 번호가 틀림

해결 방법

1. PostgreSQL 실행 확인

# macOS (Homebrew)
brew services list
brew services start postgresql

# Linux (systemd)
sudo systemctl status postgresql
sudo systemctl start postgresql

2. 연결 정보 확인

.env 파일 확인:
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=your_database

3. 수동 연결 테스트

psql -h localhost -p 5432 -U postgres -d your_database
성공하면 비밀번호 입력 후 psql 프롬프트가 나타납니다.

타임아웃 오류

증상

Error: Timeout acquiring a connection. The pool is probably full.

원인

  1. DB 연결 풀이 고갈됨
  2. 장시간 실행되는 쿼리가 연결을 점유
  3. 연결 풀 설정이 너무 작음

해결 방법

1. 연결 풀 설정 조정

sonamu.config.ts:
export default {
  database: {
    miomock: {
      host: "localhost",
      port: 5432,
      user: "postgres",
      password: "password",
      database: "miomock",
      pool: {
        min: 2,
        max: 10,  // 기본값보다 증가
        acquireTimeoutMillis: 30000,  // 타임아웃 증가
        idleTimeoutMillis: 30000
      }
    }
  }
} satisfies SonamuConfig;

2. 장시간 쿼리 확인

-- 현재 실행 중인 쿼리 확인
SELECT 
  pid,
  now() - query_start as duration,
  query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC;

-- 특정 쿼리 종료
SELECT pg_cancel_backend(pid);  -- 부드럽게 종료
SELECT pg_terminate_backend(pid);  -- 강제 종료

3. 트랜잭션 미정리 확인

-- 오래된 트랜잭션 확인
SELECT 
  pid,
  now() - xact_start as duration,
  state,
  query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY duration DESC;

권한 오류

증상

Error: permission denied for table users

해결 방법

-- 데이터베이스 소유자 확인
SELECT d.datname, u.usename
FROM pg_database d
JOIN pg_user u ON d.datdba = u.usesysid
WHERE d.datname = 'your_database';

-- 스키마 권한 부여
GRANT ALL ON SCHEMA public TO your_user;

-- 테이블 권한 부여
GRANT ALL ON ALL TABLES IN SCHEMA public TO your_user;

-- 시퀀스 권한 부여
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO your_user;

테스트 환경 DB 충돌

증상

Error: database "miomock_test" already exists
또는
Error: database "miomock_test" is being accessed by other users

원인

  • 이전 테스트가 정상 종료되지 않아 테스트 DB가 남아있음
  • 여러 테스트 프로세스가 동시에 실행됨

해결 방법

1. 테스트 DB 강제 삭제

# 모든 연결 종료 후 DB 삭제
psql -U postgres -c "
  SELECT pg_terminate_backend(pid)
  FROM pg_stat_activity
  WHERE datname = 'miomock_test' AND pid <> pg_backend_pid();
"

psql -U postgres -c "DROP DATABASE IF EXISTS miomock_test;"

2. 테스트 실행 시 격리

package.json:
{
  "scripts": {
    "test": "vitest run --pool=forks --poolOptions.forks.singleFork=true"
  }
}

3. 테스트 전후 정리 확인

// vitest.config.ts
export default defineConfig({
  test: {
    globalSetup: "./test/setup.ts",
    globalTeardown: "./test/teardown.ts"
  }
});
// test/teardown.ts
export default async function teardown() {
  await DB.clearTestTransaction();
  await DB.closeConnection();
}

관련 문서