Search

Error 1997: 분산 DB의 트랜잭션과 관련 에러 대응

문서번호 : 11-4641894

Document Information

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

Goal

Error 1997의 발생 조건과 원인(2PC protocol)을 설명합니다.
Error 1997 발생 시, 안전하게 재시도하기 위한 대응 방법(tracking table 기반 멱등성 보장)을 설명합니다.

Background

분산 데이터베이스에서의 트랜잭션

고가용성과 데이터 정합성이 중요한 시스템에서는 Sync durability + Sync replication(H/A) 활성화를 기본 구성으로 권장합니다.
데이터베이스 트랜잭션의 All or Nothing은 작업을 모두 수행(All)하거나 전혀 수행하지 않는(Nothing) 방식을 의미합니다. 즉, 트랜잭션 내 연산 중 하나라도 실패하면 전체 작업을 취소함으로써 데이터베이스의 무결성과 안정성을 보장합니다.
SingleStore와 같은 분산 데이터베이스 시스템의 트랜잭션에서는 여러 파티션의 데이터를 수정하므로 무결성을 보장하기 위해 아래의 작업 중 하나가 일어나도록 해야 합니다.
Commit: 트랜잭션 내 연산에 포함된 모든 파티션에서 데이터 수정이 발생해야 합니다. (All)
Rollback: 트랜잭션에 포함된 모든 파티션의 데이터 수정이 반영되지 않아야 합니다. (Nothing)
즉, 트랜잭션에 포함된 일부 파티션만 데이터 변경이 일어나서는 안 됩니다. SingleStore에서는 이를 보장하기 위해 Two-Phase Commit (2PC) Protocol을 사용합니다.

2PC Protocol

2PC Protocol은 트랜잭션의 모든 변경 사항이 관련된 모든 파티션에서 커밋되거나 롤백되도록 원자성을 보장합니다.
2PC에서는 Aggregator가 코디네이터로 지정되어 다른 노드들과 트랜잭션 커밋을 조정하는 역할을 합니다. 또한, 2PC Protocol에서는 준비 단계(Prepare phase)와 커밋 단계(Commit phase)로 동작합니다.
Prepare phase (준비 단계)
준비 단계에서 코디네이터는 트랜잭션에 관련된 모든 파티션에 Prepare request를 보내고, 각 Leaf는 본인 상태에 대한 커밋 가능 여부를 확인하기 위해 투표 (Vote)로 응답합니다.
해당 단계에서 모든 파티션이 커밋 허용 표시(Yes)를 응답하면 코디네이터가 커밋 요청(Commit reuqest)을 보내며 Commit phase가 진행됩니다. 그러나, 하나 이상의 파티션이 커밋 불가능 표시(No)를 응답했다면, 코디네이터는 모든 파티션에 롤백 요청을 보내 전체 트랜잭션을 롤백합니다.
Commit phase (커밋 단계)
커밋 단계에서는 Prepare phase에서 연관된 모든 파티션이 커밋 허용 표시를 응답했으므로 commit request를 전송합니다. 해당 request를 받은 각 파티션은 트랜잭션을 로컬에서 커밋하고, 로컬 커밋이 완료되었음을 코디네이터에게 ack를 전송합니다.

Solution

1. Error 1997 개요

트랜잭션에 연관된 파티션이 2PC 수행 중 장애로 응답(vote 결과 또는 ack)을 보내지 못하면, 코디네이터는 해당 트랜잭션의 최종 결과(Commit/Rollback)를 확정할 수 없습니다. 이 상태는 장애가 발생한 파티션이 다시 Online 상태가 되어 2pc resolution protocol이 완료될 때까지 지속됩니다.
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 발생 이후, 해당 트랜잭션이 최종적으로 Commit 또는 Rollback 중 무엇으로 확정될지는 장애 발생 시점에 트랜잭션과 연관된 모든 파티션이 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
트랜잭션의 결과가 확정되지 않음
tracking table과 unique 제약을 통해 멱등성 보장
1857
트랜잭션이 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처럼 결과를 알 수 없는 상황 자체를 막지는 못합니다. 멱등성 로직이 없다면 이 상황에서 재시도가 오히려 중복 반영으로 이어질 수 있습니다. 따라서 두 구성을 함께 적용해야만 분산 트랜잭션의 정합성과 재시도 안전성을 모두 확보할 수 있습니다.

References

Error 1997과 같이 Commit 수행 중 연결 장애로 트랜잭션의 성공 여부를 확인할 수 없는 상황은 SingleStore에 한정된 현상이 아닙니다. 위 Neon 문서에서도 transaction이 disk에 commit된 이후, 그 success 응답이 client에 전달되기 전 네트워크 장애가 발생하면 client가 transaction의 성공 여부를 알 수 없는 상황을 다루고 있습니다. 또한, 이를 대비해 idempotency key에 UNIQUE 제약을 적용해 재시도 시 발생하는 unique_violation 에러를 "이전 transaction 성공"으로 해석하는 방식을 권장하고 있습니다.

History

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