Search

Error 1997: 트랜잭션 응답 유실에 따른 불확실성

문서번호 : 11-4641894

Document Information

최초 작성일 : 2026.07.20
최종 수정일 : 2026.08.10
이 문서는 아래 버전을 기준으로 작성되었습니다.
SinglestoreDB : 9.0.27

Goal

트랜잭션 응답 유실에 따른 불확실성을 설명합니다.
Error 1997 발생 시, 안전하게 재시도하기 위한 대응 방법(tracking table 기반 멱등성 보장)을 설명합니다.

Background

분산 데이터베이스의 2PC (Two-Phase Commit)

분산 DB 환경에서는 트랜잭션의 변경 사항이 관련된 모든 파티션에 걸쳐 원자성(All-or-Nothing)을 보장하기 위해 코디네이터 노드(Aggregator)의 주도하에 2PC 프로토콜을 수행합니다.
1.
Prepare Phase (준비 단계): 코디네이터가 각 Leaf 노드에 커밋 준비를 요청하고, 노드들의 투표(Vote: Yes/No)를 수집합니다.
2.
Commit Phase (커밋 단계): 모든 노드가 ‘Yes’를 응답하면, 코디네이터가 Commit Request를 전송하고 Leaf 노드들로부터 완료 응답(ACK)을 받습니다.

트랜잭션 커밋 응답 유실과 불확실성

트랜잭션의 Commit 처리 결과를 알리는 응답이 클라이언트에게 도달하기 전, 네트워크 장애나 소켓 타임 아웃으로 인해 패킷이 유실되는 현상이 발생할 수 있습니다. 이 경우, 클라이언트는 해당 트랜잭션에 대한 최종 반영 여부 (Commit / Rollback)를 확신할 수 없는 불확실(In-Doubt/Ambiguous) 상태에 빠지게 됩니다.
이 문제는 클라이언트와 서버가 통신하면서 발생되는 구조적 한계이며, 아키텍처(단일 노드 vs 분산 DB)에 따라 발생 레이어가 달라집니다.

1. 단일 노드 환경

단일 서버 아키텍처에서는 장애 발생 지점이 클라이언트 서버 구간으로 한정됩니다.
트랜잭션의 처리 결과를 클라이언트에게 반환하는 과정에서 커밋 응답 패킷이 유실되거나 타임 아웃이 발생할 수 있습니다. 이 경우, 클라이언트는 해당 트랜잭션에 대한 최종 반영 여부(Commit / Rollback)를 알 수 없는 상태가 됩니다. MongoDB는 UnknownTransactionCommitResult 에러 레이블로 표시하고 있습니다.

2. 분산 데이터베이스 환경

분산 데이터베이스 환경에서는 클라이언트 서버 구간 외에 코디네이터 노드 Leaf 노드 구간이 추가됩니다.
트랜잭션에 연관된 파티션이 2PC 수행 중 장애로 응답(vote 또는 ack)을 보내지 못하면, 코디네이터는 해당 트랜잭션의 최종 반영 여부(Commit/Rollback)를 확정할 수 없습니다. CockroachDB는 ambiguous error 에러 레이블로 표시하고 있습니다.

Solution

1. Error 1997 개요

SingleStore에서도 와 같은 현상이 발생하며, 장애가 발생한 파티션이 다시 Online 상태가 되어 2PC resolution protocol이 완료될 때까지 트랜잭션의 최종 결과(Commit/Rollback)를 확정할 수 없는 상태가 지속됩니다.
OperationalError: 1997: Attempted to interrupt transaction execution, but the outcome is unknown because the transaction was already in its commit phase. Please check if it succeeded.
Plain Text
복사
코디네이터는 이처럼 트랜잭션의 최종 결과를 판단할 수 없는 경우 Error 1997을 반환합니다.

1-1. 2PC failure의 결과 결정 요인

Error 1997 발생 이후, 트랜잭션과 연관된 모든 파티션이 장애 발생 시점에 prepare 결정(Yes)을 disk에 기록한 상태였다면 해당 트랜잭션은 Commit으로, 그렇지 않다면 Rollback으로 확정됩니다. 그러므로 트랜잭션의 성공률과 서비스 연속성이 모두 중요한 금융 또는 트랜잭션 시스템에서는 Sync replication (H/A)과 Sync durability를 함께 구성하는 것이 권장됩니다.

2. Transaction Tracking Table 운영

Error 1997과 같이 트랜잭션의 반영 여부를 알 수 없는 상황은 분산 DB에 한정된 문제가 아닙니다. 일반적으로 사용하는 단일 노드 DBMS에서도 client-DBMS 간 네트워크가 commit 수행 중 단절되면 동일하게 발생하는 문제입니다.
이처럼 트랜잭션의 반영 여부를 알 수 없는 상황에서 안전하게 재시도하려면, 업무 트랜잭션에 멱등성(idempotency)이 요구됩니다. 따라서 멱등성이 요구되는 비즈니스 로직이 포함된 경우, 트랜잭션 고유 식별자(transaction_id)를 생성하고 업무 데이터와 함께 별도의 tracking table(log성 테이블)에 기록하는 방식을 권장합니다.

2.1. Idempotency 보장 방식

transaction_id에 PRIMARY KEY 또는 UNIQUE KEY 제약을 설정하면, INSERT 시점에 유일성 검사와 삽입이 하나의 원자적 연산으로 처리됩니다. 따라서 재시도 시점이 원본 트랜잭션의 2PC resolution 완료 전/후 관계없이, 동일한 transaction_id로 INSERT를 시도했을 때 원본과 재시도 트랜잭션 중 하나만 성공하고 나머지는 Duplicate entry 에러(Error 1062)로 실패합니다.
애플리케이션은 이 Error 1062를 "원본 트랜잭션이 이미 반영됨"으로 해석하여, 재시도 트랜잭션을 롤백하면 됩니다.

3. Transaction Tracking Table 운영 예시

3.1 Tracking Table 생성

transaction_id는 UUIDv7 사용을 권장합니다. 본 스키마는 transaction_id를 SORT KEY로도 사용하므로, 완전 랜덤인 UUIDv4보다 시간 순서 정렬성을 갖는 UUIDv7이 삽입 시 정렬 유지 비용 측면에서 유리합니다.
CREATE TABLE tx_tracking ( transaction_id BINARY(16) NOT NULL, created_at DATETIME(6) DEFAULT NOW(6), PRIMARY KEY (transaction_id), SHARD KEY (transaction_id), SORT KEY (transaction_id) );
SQL
복사
transaction_id는 애플리케이션에서 생성하며, PRIMARY KEY 또는 UNIQUE KEY 지정을 통해 멱등성을 보장할 수 있습니다.

3.2 업무 트랜잭션 수행

업무 데이터와 tracking table 기록은 반드시 동일 트랜잭션 내에서 수행되어야 합니다. 또한, 재시도 시에는 반드시 원본 transaction과 동일한 transaction_id를 사용해야 하며, 이 값이 달라질 경우 UNIQUE 제약에 의한 멱등성 보장이 되지 않습니다 (즉, 동일한 transaction이 2번 반영될 수 있습니다).
import singlestoredb as s2 import uuid import time def process_transaction(conn, business_data, max_backoff_sec=60): """ error 1997에 대한 transaction 가이드 코드입니다. - 멱등성 보장을 위한 transaction 재시도 시, UNIQUE 제약 및 동일한 transaction_id가 필수적입니다. - transaction은 아래 2가지 경우 재시도를 종료(더 이상 재시도하지 않음)합니다. - transaction이 에러 없이 정상적으로 커밋된 경우 - Duplicate Entry (Error 1062)가 발생한 경우 (원본 transaction이 이미 commit된 경우) - Duplicate Entry는 resolution에서 Commit으로 확정한 경우로 App단에서는 Rollback을 진행합니다. - 그 외의 실패(Error 1997, 1857 등)는 exponential backoff으로 재시도합니다. - 실제 코드에 반영할 경우, 무한 재시도가 되지 않도록 최대 시도 횟수를 지정하십시오. """ tx_id = uuid.uuid7().bytes attempt = 0 while True: cur = conn.cursor() try: cur.execute("BEGIN") cur.execute( "INSERT INTO tx_tracking (transaction_id) VALUES (%s)", (tx_id,) ) cur.execute( "INSERT INTO order_data (amount) VALUES (%s)", (business_data["amount"],), ) cur.execute("COMMIT") return True, tx_id except s2.IntegrityError as e: if e.errno == 1062: # 원본 트랜잭션이 resolution 과정에서 Commit 되었으므로 Rollback cur.execute("ROLLBACK") return True, tx_id raise except s2.OperationalError as e: err_code = e.errno # Error 1997(결과 미확정) 또는 1857(rollback 확정)은 backoff 후 재시도 print(f"[tx_id={tx_id.hex()}] attempt {attempt} 실패 " f"(error_code={err_code}), backoff 후 재시도") finally: try: cur.close() except Exception: pass attempt += 1 wait_sec = min(2 ** attempt, max_backoff_sec) time.sleep(wait_sec)
Python
복사

4. 관련 에러 코드별 처리 기준

에러 코드
내용
처리 방식
1997
2PC 수행 중 장애로 transaction의 최종 반영 여부가 확정되지 않음
tracking table과 unique 제약을 통해 멱등성 보장
1857
Open transaction이 Rollback으로 확정됨
즉시 재시도해도 안전하나, 클러스터 장애를 고려해 exponential backoff으로 재시도
1777
해당 파티션의 master instance 부재
node crash recovery가 끝날 때까지 exponential backoff로 재시도합니다.

5. 권장 구성

2PC를 활성화 상태로 유지합니다. v8.5 이상에서 기본값으로 활성화되어 있습니다. 2PC는 하나의 트랜잭션이 여러 파티션에 걸쳐 있을 때 all-or-nothing으로 처리되는 것을 보장합니다.
Transaction 식별자 기반의 멱등성 보장 로직을 사용합니다. 2장에서 설명한 바와 같이, transaction_id를 tracking table의 PRIMARY KEY 또는 UNIQUE KEY로 사용하여 재시도 시 중복 반영을 방지합니다.
2PC는 여러 파티션에 걸친 트랜잭션의 원자성을 보장하지만, Error 1997처럼 결과를 알 수 없는 상황 자체를 막지는 못합니다. 멱등성 로직이 없다면 이 상황에서 재시도가 오히려 중복 반영으로 이어질 수 있습니다. 따라서 두 구성을 함께 적용해야만 분산 트랜잭션의 정합성과 재시도 안전성을 모두 확보할 수 있습니다.

Reference

History

일자
작성자
비고
2026.07.20
mh.Choi
최초 작성
2026.07.23
mh.Choi
코드 및 세부 내용 수정
2026.08.10
mh.Choi
reference 및 멱등성 설명 수정