PostgreSQL 데이터베이스 작업 중 갑작스럽게 나타나는 “terminating connection due to idle-in-transaction timeout” 에러는 많은 개발자들을 당황시키는 문제입니다. 특히 중요한 작업을 진행하다가 예상치 못하게 연결이 끊어져 버리면 답답함은 이루 말할 수 없죠.

이런 경험이 있으시다면 안심하세요. 이 에러는 PostgreSQL의 안전장치 중 하나이며, 원인을 정확히 파악하고 적절한 해결방법을 적용하면 충분히 해결할 수 있는 문제입니다. 오늘은 이 에러가 발생하는 정확한 원인부터 단계별 해결방법까지 상세히 알아보겠습니다.

 

1. 에러 발생 원인 이해하기

idle-in-transaction timeout이란?

PostgreSQL에서 ‘idle-in-transaction timeout’은 트랜잭션이 시작된 상태에서 클라이언트가 아무런 쿼리를 보내지 않고 대기하는 시간이 지정된 제한 시간을 초과했을 때 발생하는 타임아웃입니다.

이 기능은 PostgreSQL 9.6에서 처음 도입되었으며, 기본값은 0(비활성화)으로 설정되어 있습니다.

언제 이 에러가 발생하나요?

다음과 같은 상황에서 주로 발생합니다:

  • 트랜잭션 시작 후 장시간 대기: BEGIN 명령 후 다음 쿼리까지 시간이 오래 걸리는 경우
  • 애플리케이션 로직 문제: 트랜잭션 중간에 HTTP 호출이나 무거운 연산을 수행하는 경우
  • 개발 도구 사용: DBeaver 같은 도구에서 쿼리 실행 중 사용자가 오랫동안 응답하지 않는 경우
  • 연결 풀링 문제: 애플리케이션에서 트랜잭션을 적절히 종료하지 않는 경우

 

 

2. 현재 설정 확인하기

문제를 해결하기 전에 먼저 현재 데이터베이스의 타임아웃 설정을 확인해야 합니다.

-- 현재 세션의 타임아웃 설정 확인
SHOW idle_in_transaction_session_timeout;

-- 전체 설정 확인 (관리자 권한 필요)
SELECT name, setting, unit, context, source 
FROM pg_settings 
WHERE name LIKE '%idle%';

결과 예시:

 idle_in_transaction_session_timeout 
-------------------------------------
 5min
(1 row)

만약 값이 0이 아니라면 해당 시간(밀리초 단위) 후에 타임아웃이 발생합니다.

 

 

3. 즉시 해결방법 (임시 조치)

현재 세션에서만 비활성화

가장 빠른 해결방법은 현재 세션에서 타임아웃을 비활성화하는 것입니다:

-- 현재 세션에서 타임아웃 비활성화
SET SESSION idle_in_transaction_session_timeout = 0;

-- 또는 시간을 늘리기 (30분으로 설정)
SET SESSION idle_in_transaction_session_timeout = '30min';

주의사항: 이 방법은 현재 세션에서만 적용되며, 새로운 연결 시에는 다시 기본값으로 돌아갑니다.

psql에서 자동 설정하기

psql 클라이언트를 사용할 때는 연결 옵션으로 타임아웃을 설정할 수 있습니다:

# 연결 시 타임아웃 설정
psql 'options=-cidle_in_transaction_session_timeout=1800000 host=localhost user=postgres dbname=mydb'

# 또는 환경변수 사용
export PGOPTIONS="-c idle_in_transaction_session_timeout=30min"
psql -h localhost -U postgres -d mydb

 

 

4. 근본적인 해결방법

데이터베이스 레벨에서 설정 변경

특정 데이터베이스에 대해서만 타임아웃을 조정할 수 있습니다:

-- 특정 데이터베이스의 타임아웃 설정 (30분으로 설정)
ALTER DATABASE mydatabase SET idle_in_transaction_session_timeout = '30min';

-- 타임아웃 비활성화
ALTER DATABASE mydatabase SET idle_in_transaction_session_timeout = 0;

-- 설정 제거 (기본값으로 복원)
ALTER DATABASE mydatabase RESET idle_in_transaction_session_timeout;

사용자/역할 레벨에서 설정 변경

특정 사용자나 역할에 대해서만 설정을 변경할 수도 있습니다:

-- 특정 사용자에 대한 타임아웃 설정
ALTER ROLE myuser SET idle_in_transaction_session_timeout = '1hour';

-- 개발자 역할에 대한 설정
ALTER ROLE developer SET idle_in_transaction_session_timeout = 0;

-- 운영 환경 사용자에게는 보수적인 설정
ALTER ROLE production_user SET idle_in_transaction_session_timeout = '10min';

postgresql.conf 파일 수정 (전역 설정)

주의: postgresql.conf에서 이 설정을 변경하는 것은 모든 세션에 영향을 주므로 권장되지 않습니다.

# postgresql.conf
idle_in_transaction_session_timeout = 600000  # 10분 (밀리초 단위)

설정 후 PostgreSQL 재시작:

sudo systemctl reload postgresql

 

 

5. 애플리케이션 코드 개선방법

트랜잭션 최적화

타임아웃 에러의 근본 원인은 대부분 잘못된 트랜잭션 관리에 있습니다. 다음과 같이 코드를 개선해보세요:

잘못된 예시:

import psycopg2
import time
import requests

conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()

cur.execute("BEGIN;")
cur.execute("INSERT INTO users (name) VALUES ('John');")

# 문제: 트랜잭션 중간에 외부 API 호출
time.sleep(10)  # 또는 긴 처리 시간
response = requests.get("https://api.example.com/data")

cur.execute("UPDATE users SET status = 'active' WHERE name = 'John';")
conn.commit()

개선된 예시:

import psycopg2

conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()

# 외부 처리를 트랜잭션 밖에서 수행
external_data = requests.get("https://api.example.com/data").json()

# 트랜잭션을 짧게 유지
try:
    cur.execute("BEGIN;")
    cur.execute("INSERT INTO users (name, status) VALUES (%s, %s);", 
                ('John', 'active'))
    cur.execute("INSERT INTO user_data (user_id, data) VALUES (%s, %s);", 
                (user_id, external_data))
    conn.commit()
except Exception as e:
    conn.rollback()
    raise

연결 풀 설정 최적화

연결 풀을 사용하는 경우 적절한 타임아웃 설정이 중요합니다:

# SQLAlchemy 예시
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool

engine = create_engine(
    'postgresql://user:password@localhost/dbname',
    poolclass=QueuePool,
    pool_size=10,
    max_overflow=20,
    pool_pre_ping=True,  # 연결 상태 확인
    connect_args={
        "options": "-c idle_in_transaction_session_timeout=300000"  # 5분
    }
)

 

 

6. Idle 트랜잭션 모니터링, 오래된 idle 트랜젝션 정리하는 방법

현재 활성 트랜잭션 확인

다음 쿼리로 현재 시스템의 idle 트랜잭션을 모니터링할 수 있습니다:

-- 현재 idle in transaction 상태인 세션 확인
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    state,
    state_change,
    now() - state_change AS idle_duration,
    query
FROM pg_stat_activity 
WHERE state = 'idle in transaction' 
ORDER BY state_change;

자동 정리 스크립트

오래된 idle 트랜잭션을 자동으로 정리하는 스크립트를 설정할 수 있습니다:

-- 5분 이상 idle인 트랜잭션 종료
SELECT pg_terminate_backend(pid) 
FROM pg_stat_activity 
WHERE state = 'idle in transaction' 
  AND now() - state_change > interval '5 minutes'
  AND pid <> pg_backend_pid();

 

 

7. 권장 설정값

상황별 권장 설정값을 정리해보겠습니다:

환경 권장 설정 설명
개발 환경 0 (비활성화) 개발자가 자유롭게 디버깅할 수 있도록
테스트 환경 30min 테스트 시간을 고려한 충분한 여유
운영 환경 (웹 애플리케이션) 5-10min 웹 요청 처리에 적합한 시간
운영 환경 (배치 작업) 1-2hour 대용량 처리를 고려한 시간
대화형 세션 (psql, DBeaver) 30min-1hour 사용자 작업 시간 고려

 

8. 추가 고려사항

다른 타임아웃 설정과의 관계

PostgreSQL에는 여러 타임아웃 설정이 있으며, 이들 간의 관계를 이해하는 것이 중요합니다:

  • statement_timeout: 단일 쿼리 실행 시간 제한
  • lock_timeout: 락 대기 시간 제한
  • idle_session_timeout: 일반 idle 연결 시간 제한
  • transaction_timeout: 전체 트랜잭션 시간 제한 (PostgreSQL 17+)

성능에 미치는 영향

idle 트랜잭션이 오래 지속되면 VACUUM 작업에 영향을 주어 테이블 블로팅을 유발할 수 있습니다. 따라서 적절한 타임아웃 설정은 데이터베이스 성능 유지에 중요합니다.

 

PostgreSQL의 ‘idle-in-transaction timeout’ 에러는 처음에는 당황스럽지만, 원인을 이해하고 적절한 해결책을 적용하면 충분히 해결할 수 있는 문제입니다. 핵심은 타임아웃 설정 조정애플리케이션 코드 개선을 함께 진행하는 것입니다. 단순히 타임아웃 값만 늘리는 것보다는 트랜잭션을 효율적으로 관리하고, 상황에 맞는 적절한 설정을 적용하는 것이 중요합니다.

 

댓글 남기기