GoldenGate PostgreSQL Replicat이 반복해서 멈추던 문제는 odbc.ini의 기존 ODBC 드라이버 경로를 ggpsql25.so로 바꾼 뒤 더 이상 발생하지 않았다. 1TB 초기 적재를 재시작 없이 마쳤고, 이후 변경 데이터를 반영하는 CDC도 정상 동작했다.
재시작 전에 확보한 스택과 DB 세션, 소켓 상태를 살펴보니 드라이버 쪽에 문제가 있을 가능성이 높았다. 다만 드라이버 내부에서 정확히 어떤 오류가 발생했는지는 확인하지 못했다.
Oracle에서 PostgreSQL로 CLOB 테이블을 초기 적재하던 중이었다. Replicat은 RUNNING인데 CPU가 0%였고, 체크포인트는 갱신되지 않았으며 SEND 명령에도 응답하지 않았다. PostgreSQL은 idle in transaction / ClientRead로 표시됐으며 관련 로그에는 오류가 없었다.
증상: idle in transaction / ClientRead 상태로 멈춘 Replicat
초기 적재용 Classic Replicat이 CLOB 컬럼(PostgreSQL에서는 text)이 있는 테이블을 적재하다가 멈췄다.
- 프로세스 상태 RUNNING, CPU 0%, 체크포인트가 갱신되지 않음
SEND REPLICAT ... STATUS에 응답 없음 (OGG-15163 Timeout waiting for message)- PostgreSQL 세션은
idle in transaction, 대기 이벤트ClientRead, 마지막 문장은RELEASE <sp>; SAVEPOINT <sp> - Replicat 리포트, ggserr.log, PostgreSQL 로그 모두 오류 없음
- KILL 후 START 하면 멈춘 지점을 통과하지만 다른 지점에서 다시 멈춤. 같은 행을 단독으로 적재하면 정상
오류 로그에 단서가 없어, 재시작하기 전에 프로세스가 어느 함수에서 기다리고 있는지 확인했다. DB 세션과 소켓 상태도 함께 수집해 비교했다. 체크포인트가 안 움직이는 것은 같아도, 이전에 다룬 Extract 체크포인트 래그 사례와는 성격이 다르다. 이전 사례는 소스 DB의 장기 트랜잭션 때문에 처리가 지연됐지만, 이번에는 타겟 쪽 Replicat이 멈춰 있었다.
환경과 데이터 흐름: Replicat에서 ODBC 드라이버를 거쳐 PostgreSQL까지
| 구성 요소 | 버전 |
|---|---|
| Oracle GoldenGate for PostgreSQL | 21.12.0.0.4 (Classic, OL7 빌드) |
| Replicat 유형 | Classic, NODBCHECKPOINT, EXTTRAIL |
| PostgreSQL | 16.15 (Patroni HA, 비동기 복제) |
| OS | Rocky Linux 8.10 |
| 로드된 ODBC 드라이버 | GGpsql25.so 07.16.0445 |
스택을 해석하려면 Replicat과 PostgreSQL 사이에서 어떤 라이브러리가 호출되는지 알아야 한다. 아래 그림은 레코드가 적용되는 과정에서 각 구성 요소가 맡는 역할을 보여 준다. 이 환경에서는 Replicat과 PostgreSQL이 같은 호스트에서 실행되며, 로컬 TCP로 직접 연결돼 있었다.

정지 당시 수집한 자료: pstack, pg_stat_activity, ss
프로세스를 종료하면 정지 순간의 스택과 소켓 상태를 다시 확인하기 어렵다. 증상이 간헐적으로 발생했기 때문에, 정지를 감지하면 아래 세 가지를 15초 간격으로 3회 자동 수집하도록 했다.
pstack <replicat pid>: 각 스레드가 지금 어느 함수에 있는지pg_stat_activity: Replicat 세션의 상태와 대기 이벤트ss -tnp: Replicat 소켓의 송수신 큐와 연결 상대
3.1 pstack: 드라이버 안 recv()에서 정지
31초 이상에 걸쳐 세 번 수집한 스택에서 호출 경로와 주소가 모두 같았다. 매번 같은 recv() 에서 대기하고 있었다. 다만 세 차례의 수집 사이에 어떤 동작이 있었는지까지 추적하지는 않았다. 보조 스레드 4개(로깅, 파일 캐싱)는 모두 유휴 상태였다. 메인 스레드에서는 아래 호출 경로가 확인됐다.
#0 recv () from libpthread.so.0
#1~#10 ?? () from lib/GGpsql25.so
#11 SQLPrepareW () from lib/GGpsql25.so
#12~#13 ?? () from lib/libodbc.so
#14 SQLPrepareW () from lib/libodbc.so
#15 DBODBC_prepare_statement (...)
#16 ODBCUTIL_stmt_t::Prepare (...)
#17 execute_statement (...)
#18 perform_sql_statements (...)
#19 ggs::er::ReplicatContext::replicate_io (...)
#20 ggs::er::ReplicatContext::processRecord (...)
#21 ggs::er::ReplicatContext::processReplicatLoop ()
#22~#27 ... main ()
이 스택에서는 번호가 클수록 먼저 호출된 바깥쪽 프레임이다. 아래에서 위로 읽으면 호출 순서를 따라갈 수 있다. Replicat 루프 → 레코드 하나 꺼냄 → 적용 시작 → SQL 한 문장 처리 진입(execute_statement) → 그 첫 단계인 문장 prepare → 드라이버 매니저 → 실제 드라이버 → 소켓 읽기 대기.
execute_statement라는 함수 이름만 보면 SQL 실행이 이미 시작된 것처럼 보일 수 있다. 하지만 이 함수는 SQL 문장 하나를 처리하는 전체 절차를 맡고 있으며, prepare는 그중 준비 단계다. DB에 SQL을 보내는 절차는 보통 세 단계로 나뉜다.
- Prepare: 실행할 문장을 준비한다.
- Bind: 문장의 매개변수에 값을 연결한다.
- Execute: 준비한 문장을 실행한다.
이번 캡처에서 메인 스레드는 SQLPrepareW 아래 드라이버의 recv()에 있었다. 즉, 문장을 준비하는 과정에서 응답을 기다리고 있었다. 수집한 스택에서는 trail을 읽거나 OGG 내부 lock을 기다리는 모습은 보이지 않았다. 다만 이 스택만으로 Parse 전송 완료 여부나 Bind·Execute·LOB 전송의 전체 이력까지 확인할 수는 없다. ODBC 호출과 PostgreSQL 와이어 메시지가 항상 일대일로 대응하는 것도 아니다. PostgreSQL 메시지 흐름
3.2 pg_stat_activity: 서버는 클라이언트를 기다림
pid | 3486801
client_port | 39275
xact_start | 11:36:47.059
state | idle in transaction
wait_event | Client / ClientRead
query | RELEASE T5612C7B61CB0;SAVEPOINT T5612C7B61CB0
세 번의 조회 결과는 모두 같았다. ClientRead는 클라이언트의 데이터를 읽기 위해 기다린다는 뜻이다. idle in transaction과 마지막 쿼리만으로 지금 어떤 SQL을 실행 중이라고 판단할 수는 없다. PostgreSQL 세션·대기 이벤트 설명
마지막 쿼리에 SAVEPOINT가 남아 있는 이유는 드라이버 설정에서 찾을 수 있었다. DataDirect 드라이버는 TransactionErrorBehavior=2일 때 SQL 문장마다 savepoint를 자동으로 설정한다. OGG는 이 값을 내부적으로 2로 고정한다(OGG-30082). 따라서 odbc.ini에 0을 지정해도 적용되지 않는다.
3.3 ss: 큐는 비어 있고, 연결은 로컬
ESTAB 0 0 <host>:39275 <host>:5432 users:(("replicat",pid=3486796,fd=9))
클라이언트 포트 39275가 pg_stat_activity의 client_port와 일치한다. 포트 번호를 대조해 두 자료가 같은 연결을 보여 주고 있음을 확인했다.
확인 당시 Send-Q와 Recv-Q는 모두 0이었다. 상대방의 수신 확인을 기다리는 송신 데이터도, 애플리케이션이 아직 읽지 않은 수신 데이터도 큐에 남아 있지 않았다. 같은 호스트에서 직접 연결하고 있다는 점도 고려해, 외부 프록시나 네트워크 장비보다 드라이버 쪽을 먼저 조사하기로 했다. 다만 큐가 비어 있다는 사실만으로 메시지가 빠짐없이 전달됐다고 단정할 수는 없다. 애플리케이션 내부 버퍼나 네트워크 장애, HA 전환 여부는 별도로 확인해야 한다. ss 소켓 확인 도구

GoldenGate PostgreSQL Replicat의 드라이버 경로 확인
대기가 발생한 GGpsql25.so의 버전을 확인했다. 리포트의 Driver Information은 GGpsql25.so Version 07.16.0445였다. 설치된 버전이 어떤 드라이버를 제공하는지 확인하려고 21c 릴리스 노트를 찾아봤다.
Oracle 21c 릴리스 노트의 Bug 35031894는 21.9.0.0.3에서 DataDirect 08.02.2122 지원을 안내한다. 기존 DSN의 Driver= 경로도 소문자 ggpsql25.so로 바꿔야 새 드라이버를 사용한다. 이 항목이 이번 정지의 정확한 버그 번호임을 입증한 것은 아니다. Oracle 21c 릴리스 노트
해당 서버의 lib/ 디렉터리와 odbc.ini를 확인했다.
$ ls -la $OGG_HOME/lib/*psql25*
GGpsql25.so 2021-06-01 (구 드라이버, 7.16) ← odbc.ini가 가리키는 파일
GGpsql25r.so 2021-06-01
ggpsql25.so 2023-02-01 (새 드라이버, 8.02) ← 릴리스 노트가 안내하는 파일
[pgtarget]
Driver=/opt/ogg/lib/GGpsql25.so
HostName=...
PortNumber=5432
Database=...
TransactionErrorBehavior=0
21.12.0.0.4 설치본에 새 드라이버가 들어 있었지만, odbc.ini는 구 가이드(DataDirect 7.1 샘플)대로 대문자 경로를 가리키고 있었다. 드라이버 매니저는 이 경로의 파일을 로드하므로, Replicat은 새 파일이 설치된 뒤에도 2021년 드라이버로 PostgreSQL 16에 접속하고 있었다.

조치와 결과: 드라이버 경로를 바꾼 뒤 정지가 재발하지 않았다
기존 DSN 설정을 백업한 뒤, 새 드라이버의 설치 경로를 확인해 설정을 수정했다. odbc.ini에서 환경 변수가 자동으로 확장된다고 가정해서는 안 된다. 실제 설치 위치에 맞는 절대 경로를 지정해야 한다. 주로 바꾼 것은 Driver= 경로였다. 적용되지 않던 속성과 설명 항목도 함께 정리했기 때문에, 드라이버 경로 하나만 바꿔 비교한 실험은 아니다. 아래 항목을 수정한 뒤 같은 적재 작업을 다시 실행했다.
- odbc.ini
Driver=를$OGG_HOME/lib/ggpsql25.so로 변경 TransactionErrorBehavior=0제거 (OGG가 내부적으로 2로 덮어쓰므로 효과가 없던 항목)[ODBC Data Sources]와Description=을 21c 문서 샘플(Oracle GoldenGate PostgreSQL Wire Protocol)에 맞춤- 재시작 후 리포트의 Driver Information에
ggpsql25.so와08.02.xxxx가 표시되는지 먼저 확인
드라이버를 바꾼 뒤로 정지 현상(idle in transaction / ClientRead 대기)이 다시 나타나지 않았고, 별도로 보고되던 OGG-00551 / SQLSTATE 08S01 Socket closed abend도 함께 사라졌다. 이전에는 몇 분 만에 멈추던 대용량 LOB 테이블 초기 적재(1TB)가 재시작 없이 완료됐고, 이어서 CDC Replicat도 정상 운영 중이다.
조사 당시에는 Replicat이 멈춘 채 세션을 오래 유지하다가 시간 초과로 연결이 끊겨 08S01이 발생했을 가능성을 생각했다. 드라이버를 바꾼 뒤 두 증상이 함께 사라졌지만, 이것만으로 그 추정이 맞았다고 단정할 수는 없다. PostgreSQL의 시간 제한 설정 때문에 끊긴 것인지, 다른 원인이 있었는지는 확인하지 못했다. 정상화 후에는 드라이버 내부 추적을 추가로 수행하지 않았다.
직접 확인한 내용
- 정지 위치: 드라이버 내부
recv(), prepare 단계 - 관측 당시 PG 세션은 클라이언트 대기, 소켓 큐 0, 같은 호스트
- 로드된 드라이버는 2021년 7.16 버전, 릴리스 노트는 소문자 경로 요구
- 경로 변경 후 정지·08S01 재발 없음, 1TB 적재 완료, CDC 정상
가설로 남은 것
- 구 드라이버가 PG 16과 어떤 프로토콜 단계에서 어긋났는지 (Parse 뒤 Flush/Sync 누락, 불완전 전송 등)
- LOB이 클수록 처리 시간이 길어져 문제가 더 자주 발생했을 가능성
- Replicat 정지 이후 시간 초과로 연결이 끊기면서 08S01이 발생했을 가능성
확인하지 않은 것
- 구 드라이버 내부 동작 (ODBC trace를 수집하기 전에 증상이 사라짐)
- OL7 빌드·Rocky 8.10·PostgreSQL 16 조합의 인증 여부
- 08S01 당시 연결을 끊은 주체
같은 증상이 생기면 먼저 확인할 것
- 로그에 단서가 부족하다면 정지 순간의 상태를 함께 확보해야 한다. pstack, DB 세션, 소켓 큐를 같은 시점에 수집해야 서로 비교할 수 있다. 증상이 간헐적으로 발생한다면 수집을 자동화해 두는 편이 좋다. 서비스 복구를 위해 먼저 재시작해야 한다면 조건부 재기동과 모니터링 절차를 참고할 수 있다. 이때도 재시작 전에 자료를 남겨야 원인 조사를 이어갈 수 있다.
- pstack은 아래에서 위로 읽는다. 번호가 큰 프레임부터 따라가면 호출 순서를 알 수 있고,
#0에서는 현재 실행 중이거나 대기 중인 함수를 확인할 수 있다. 두 위치를 함께 살펴보면 어떤 작업 중에 대기했는지 파악하고 조사 범위를 좁힐 수 있다. - 소켓 큐만 보고 연결에 문제가 없다고 판단해서는 안 된다. 큐가 0이더라도 실제 연결 경로와 시간에 따른 상태 변화도 확인해야 한다.
- 정지 위치가 특정 라이브러리 안이라면 실제로 로드된 파일부터 확인해야 한다. 파일의 버전과 날짜를 확인하고, 설치 디렉터리에 다른 버전이 함께 있는지도 살펴봐야 한다. 이번에는 새 드라이버가 같은 디렉터리에 있었지만, 설정이 이전 파일을 가리키고 있어 2021년 드라이버가 계속 사용되고 있었다.
- 패치 후에는 설정도 바꿔야 하는지 확인해야 한다. 기존 설치를 덮어쓰는 in-place 업그레이드를 했더라도 odbc.ini가 이전 파일을 가리키면 새 드라이버는 사용되지 않는다. 실제 로드된 파일과 버전은 Replicat 리포트의 Driver Information에서 확인할 수 있다.
- 증상이 사라졌다고 내부 원인까지 밝혀진 것은 아니다. 드라이버 교체로 증상이 사라진 것은 확실하지만, 구 드라이버가 어느 프로토콜 단계에서 어긋났는지는 확인하지 않았다. 따라서 조치 후 관찰한 결과와 아직 확인하지 못한 내부 동작을 구분해 기록해야 한다.
관련 글
- Oracle GoldenGate 체크포인트 래그 해결 가이드: Extract 전체에 3.5시간 래그가 생겨 SHOWTRANS와 v$transaction으로 장기 트랜잭션을 추적한 사례
- 리커버리 끝났는데 XID가 왜 안 사라지죠?: 위 글의 후속편. LogMiner로 redo를 확인하고 FORCESTOP 여부를 결정한 과정
- GoldenGate 장애 대응: 조건부 재기동 + 모니터링 루프: STARTING/UNKNOWN 상태의 프로세스를 포트·소유자 확인 후 재기동하는 운영 절차
자주 묻는 질문
OGG for PostgreSQL Replicat이 RUNNING인데 체크포인트가 안 움직이고 SEND도 무응답(OGG-15163)이면 무엇부터 봐야 하나?
KILL하기 전에 pstack을 반복 수집해 메인 스레드가 어느 함수에서 기다리는지 확인해야 한다. 같은 시각의 pg_stat_activity와 ss -tnp를 함께 모으면 DB·드라이버·연결 상태를 대조하면서 조사 범위를 좁힐 수 있다. 이 자료만으로 원인을 단정하지 말고, 같은 시점의 오류 로그도 함께 확인해야 한다.
PostgreSQL 세션이 idle in transaction / ClientRead인데 Replicat은 왜 멈춰 있나?
ClientRead는 서버가 클라이언트의 다음 메시지를 기다린다는 뜻이다. 이 사례에서는 Replicat 쪽 드라이버도 서버 응답을 기다리고 있어 양쪽이 서로를 기다렸다. 수집 당시 PostgreSQL 세션이 기다리던 것은 Lock이나 SyncRep가 아니라 클라이언트의 다음 메시지였다. 드라이버와 서버의 프로토콜 상태 불일치는 가능한 설명이지만, 패킷이나 ODBC trace로 확인한 결론은 아니다.
GGpsql25.so와 ggpsql25.so는 무엇이 다른가?
대문자 GGpsql25.so는 DataDirect 7.x 계열 구 드라이버, 소문자 ggpsql25.so는 21.9.0.0.3부터 동봉된 8.02 계열 새 드라이버다. 두 파일이 같은 lib/에 공존할 수 있고, 어느 것이 로드되는지는 odbc.ini의 Driver= 경로가 결정한다. Replicat 리포트의 Driver Information 항목에서 실제 로드된 파일과 버전을 확인할 수 있다.
참고 문서와 기록 범위
- Oracle GoldenGate 21c 릴리스 노트: Bug 35031894
- Oracle PostgreSQL DSN 설정
- Oracle GoldenGate 오류 메시지: OGG-30082
- DataDirect TransactionErrorBehavior
- PostgreSQL 16 세션 통계·대기 이벤트
버전·31초 대기·1TB 적재 완료 결과는 당시 장애 대응 기록을 바탕으로 정리했다. 이 글을 작성하기 위해 장애를 다시 재현하거나 OL7 빌드·Rocky 8.10·PostgreSQL 16 조합의 제품 인증을 검증한 것은 아니다.


