요약
- RFC 2372는 PREPARED를 보내기 전에 준비 복구 레코드를, Prepared 상태에서 COMMIT을 보내기 전에 커밋 복구 레코드를 만들고, 지정된 해소 근거가 도착할 때까지 각 레코드를 보존하도록 권고했다.
- 이 순서는 프로토콜 문장이 내구성 있는 로컬 기억에 의존하게 만들었지만, 바이트가 안정 저장소에 도달했는지, 크래시에서 살아남았는지, 올바른 트랜잭션 식별자로 재생되었는지, 애플리케이션이 알 수 있는 결과를 낳았는지는 증명하지 않았다.
분산 트랜잭션에서 가장 위험한 단어는 종종 COMMIT이 아니다. PREPARED이다.
COMMIT은 최종처럼 들리기 때문에 엔지니어는 본능적으로 그 의미를 묻는다. PREPARED는 중간 확인 응답처럼 듣기 쉽다: 참여자가 일부 작업을 마쳤고, 코디네이터가 결정할 것이며, 절차는 계속된다. 그러나 하위 시스템이 PREPARED를 보내는 순간 일방통행의 경계를 넘는다. 소켓이 닫히거나 프로세스가 재시작했다는 이유만으로 트랜잭션을 버릴 수 없다. 미래의 결정에 따르기에 충분한 상태를 보존할 것을 약속한 것이다.
1998년 7월에 발표된 RFC 2372는 특정 TIP 메시지가 트랜잭션 매니저를 떠나기 전에 어떤 로컬 복구 레코드가 존재해야 하는지를 서술함으로써 그 약속을 드러냈다. 이 문서는 인터넷 표준이 아니라 Informational 문서였고, 프로토콜-로그 지침을 결정적인 것이 아니라 권고로 규정했다. 이 겸손한 지위는 중요하다. 이 문서는 배포된 제품, 스토리지 엔진, 크래시 테스트 또는 성공한 복구를 보고하지 않았다. 그러나 그 순서 규칙은 인터넷 인프라의 오래된 원칙을 드러낸다: 소프트웨어가 실패 이후에도 결과가 지속되는 상태를 선포하기 전에, 그 상태를 재구성하는 데 필요한 근거가 이미 보관 중이어야 한다.
PREPARED는 선택권을 남긴 쪽을 바꿨다
TIP은 presumed-abort 형태의 2단계 커밋을 사용했다. 참여자가 prepare하기 전에는 실패가 중단으로 해소될 수 있었다. prepare한 후에는 일방적 후퇴가 더 이상 안전하지 않았다. 상위 시스템이 이미 모든 투표를 수집하고 커밋을 결정했을 수 있기 때문이다. 따라서 하위 시스템은 네트워크 대화를 끊게 만드는 바로 그 실패를 넘어 트랜잭션을 기억해야 했다.
RFC 2372의 지침은 순서에 대해 명확했다. 하위 시스템은 PREPARED를 보내기 전에 준비 복구 레코드를 만들어야 했다. ABORT, COMMIT 또는 QUERIEDNOTFOUND를 받을 때까지 그 레코드를 보존해야 했다. 준비 레코드가 존재하는 동안 COMMITTED나 NOTRECONNECTED를 보내서는 안 되었다. 이 레코드는 단순히 캐시된 메시지가 아니라 해소되지 않은 의무를 나타냈다.
상위 시스템도 관련 부담을 지녔다. 먼저 PREPARED를 받았다. 트랜잭션이 Prepared 상태인 동안 COMMIT을 보내기 전에 커밋 복구 레코드를 만들어야 했다. 그 레코드는 COMMITTED나 NOTRECONNECTED가 돌아올 때까지 유지되었다. 따라서 상위 시스템의 내구성 근거는, 나중에 재생이 필요할 수 있는 외부 지시보다 먼저 존재해야 했다.
이 순서는 "중요한 이벤트를 로그에 남기라"는 것보다 강하다. 이는 보관 전환을 서술한다. PREPARED는 참여자를 로컬 자유에서 외부 구속 의무로 이전시킨다. COMMIT은 상위 시스템을 숙고에서, 반복해야 할 수도 있는 결정으로 이전시킨다. 어느 레코드든 삭제하는 것은 정리 작업이 아니다. 지정된 근거가 의무를 닫았다는 주장이다.
연결은 기억이 아니었다
TIP은 애플리케이션 대화와 트랜잭션 조정을 분리했다. 비즈니스 요청은 HTTP 같은 애플리케이션 채널로 이동할 수 있고, 트랜잭션 매니저는 다른 채널을 사용했다. 이 두 파이프 설계는 트랜잭션 프로토콜을 재사용 가능하게 만들었지만, 연결이 무슨 일이 있었는지에 대한 전체 기록이 되는 것도 막았다.
실패 후 상위 시스템은 하위 시스템의 트랜잭션 식별자로 RECONNECT를 발행할 수 있었고, 필요하면 트랜잭션 로그에서 그 식별자를 복원했다. 하위 시스템이 여전히 Prepared 상태면 RECONNECTED로 답했다. 이미 COMMIT을 받고 COMMITTED로 답한 뒤 트랜잭션을 잊었다면 NOTRECONNECTED로 답했다. 부정 응답은 반드시 실패를 뜻하지 않았다; 지정된 순서에서는 하위 시스템이 이미 완료하고 상태를 폐기했다는 증거일 수 있었다.
하위 시스템은 QUERY로 반대 질문을 할 수 있었다. QUERIEDEXISTS는 상위 시스템이 여전히 트랜잭션을 알고 나중에 재연결할 것임을 의미했다. QUERIEDNOTFOUND는 상위 시스템이 ABORT를 보내고 ABORTED를 받지 못한 뒤 presumed-aborted 트랜잭션을 잊었을 수 있으므로, 하위 시스템이 중단할 수 있게 했다. 따라서 같은 겉보기 부재도 역할, 상태, 메시지 이력 안에서만 의미를 가졌다.
그렇기 때문에 복원된 TCP 연결만으로는 결코 충분하지 않았다. 전송 계층은 엔드포인트를 다시 연결할 수 있었다. 올바른 트랜잭션 식별자를 재현하거나, 어느 쪽이 미해결 의무를 지는지 결정하거나, 레코드 부재가 "안전하게 끝남", "중단 추정", "분실" 중 무엇인지 증명할 수 없었다. 복구는 원격 프로토콜 근거와 결합된 로컬 내구성 식별자에 의존했다.
삭제 영수증은 비대칭이었다
준비 레코드와 커밋 레코드는 하나의 보편적인 종결 신호를 공유하지 않았다. 하위 시스템은 ABORT, COMMIT 또는 QUERIEDNOTFOUND까지 준비 근거를 보존했다. 상위 시스템은 COMMITTED 또는 NOTRECONNECTED까지 커밋 근거를 보존했다. 이 목록들은 서로 다른 지식 위치를 부호화한다.
하위 시스템의 경우, 권위적인 결정 또는 상위 시스템이 더 이상 트랜잭션을 모른다는 것을 증명하는 복구 질의가 준비 의무를 해제할 수 있었다. 상위 시스템의 경우, 참여자의 확인 응답—또는 참여자가 이미 완료된 상태를 잊은 후 재연결할 수 없다는 구체적 사실—이 COMMIT 반복 필요를 닫을 수 있었다. 프로토콜은 타이머 만료, 프로세스 복귀, 디스크 공간 부족, 운영자가 피어가 아마 끝났다고 믿는다는 이유로 삭제를 허가하지 않았다.
타임아웃도 여전히 자리가 있었다. RFC 2372는 사용 불가능한 자원을 제한하고 교착 상태 해소에 도움을 주기 위한 로컬 트랜잭션 타임아웃을 예상했다. 타임아웃 정책은 잠금이 영원히 지속되는 것을 막을 수 있었다. 경과 시간을 올바른 결과의 증명으로 바꾸지는 않았다. 자원 해제, 프로토콜 해소, 복구된 지식은 별개의 사실로 남았다.
기록 지시가 내구적 순서를 증명하지 않았다
"복구 레코드를 만든다"는 표현은 스토리지 스택을 만나기 전까지 정밀해 보인다. create가 사용자 공간 버퍼에 append하는 것인지, 커널 페이지 캐시에 쓰는 것인지, 저널을 플러시하는 것인지, 매체에 영속화하는 것인지, 다른 장애 도메인으로 복제하는 것인지, 스토리지 확인 응답을 받는 것인지? 레코드가 찢어질 수 있는가? 레코드를 매핑하던 휘발성 인덱스 없이 트랜잭션 식별자를 복원할 수 있는가? 발신 PREPARED가 실제로 내구성 이후에 순서화되었는가, 아니면 비동기 발신자가 로그를 추월할 수 있는가?
RFC 2372는 그 구현 질문에 답하지 않았고, 답하려 하지도 않았다. 요구사항과 보충 정보를 제공했다. 데이터베이스를 지명하거나, 적합성 스위트를 공개하거나, 전원 장애를 주입하거나, 재구성을 측정하지 않았다. 따라서 그 로컬 레코드 지침은 구현되고 테스트되어야 할 의무이지, 어떤 구현이 그것을 충족했다는 증거가 아니다.
Lu Heng의 running-code 관점이 여기서 유용하다. 텍스트는 올바른 소프트웨어가 보존해야 하는 순서를 확립할 수 있다. 운영 증명은 그 이후에 시작된다: 내구적 쓰기를 관찰하고, 적대적 순간에 프로세스를 중단시키고, 재시작하고, 트랜잭션 식별자를 재구축하고, 피어와 조정하고, 로컬 자원 결과를 검증한다. RFC는 그 테스트에 형태를 준다. 테스트에 통과 결과를 주지는 않는다.
같은 구분이 삭제에도 적용된다. 구현이 지정된 메시지를 받은 후에만 레코드를 제거하더라도, 그 제거가 자원 관리자의 대응 상태 전환보다 먼저 내구화되면 여전히 틀릴 수 있다. 반대로 어떤 시스템은 오래된 레코드를 안전하지만 무기한 보존해 자원과 운영자의 부담을 만들 수 있다. 정확성은 그럴듯한 이름을 가진 파일의 존재가 아니라 프로토콜 상태, 로그 상태, 자원 상태 사이의 관계에 산다.
위임은 책임을 옮겼을 뿐, 지우지 않았다
RFC 2372는 경량 클라이언트를 더 완전한 서버 시스템과 구분했다. 클라이언트는 트랜잭션 조정을 서버에 위임하고 자체 트랜잭션 로그가 필요 없을 수 있었다. 그 배열은 클라이언트의 내구성 장비를 줄였지만 복구 부담이 사라지지 않았다. 그 역할을 수락한 시스템으로 옮겨갔다.
이 비대칭은 쉽게 오독된다. "클라이언트 로그 불필요"는 "내구성 복구 근거 불필요"를 의미하지 않는다. 위임된 프로토콜 상태에 대해 클라이언트가 그 근거의 보관자가 아니라는 뜻이다. 감사는 의무를 따라 서버로 가야 한다: 어떤 레코드를, 어떤 식별자로, 어떤 문장 전에 썼고, 어떤 영수증 이후에 레코드를 삭제했는가.
위임은 또한 호출자의 지식을 미해결로 남긴다. 클라이언트 애플리케이션이 커밋을 호출하고, 최종 응답을 잃고, 나중에 그럼에도 트랜잭션이 완료되었음을 발견할 수 있다. RFC 2372는 애플리케이션이 결과를 확인할 책임을 두고, 구현 고유의 사용자 로그가 도움이 될 수 있다고 제안했다. 트랜잭션 매니저는 프로토콜 상태를 성공적으로 복구하면서도, 작업을 시작한 사람이나 서비스는 여전히 비즈니스 수준의 답을 갖지 못할 수 있다.
이 경계는 이 역사를 RFC 2371의 넓은 교훈과 구별한다. 프로토콜 정의는 TIP COMMIT 응답이 자동으로 비즈니스 영수증이 아님을 보여주었다. RFC 2372의 더 예리한 기여는 복구 아래의 로컬 증거 사슬을 드러낸 것이다: 참여자의 준비 레코드, 상위 시스템의 커밋 레코드, 실패 후 복원된 트랜잭션 식별자, 각 레코드가 사라질 수 있게 한 특정 원격 신호.
두 개의 파이프는 애플리케이션 규율을 요구했다
애플리케이션 트래픽과 TIP 명령의 분리는 또 다른 순서 문제를 만들었다. 트랜잭션 매니저는 애플리케이션이 아직 진행 중인 요청을 갖고 있는지 반드시 알 수 없었다. 따라서 RFC 2372는 애플리케이션 규율을 요구했다: 연관된 애플리케이션 요청이 남아 있는 동안 커밋을 호출하지 말고, 로컬 트랜잭션 매니저가 트랜잭션을 등록하기 전에 파트너의 트랜잭션 요청에 긍정적으로 답하지 말라.
어떤 트랜잭션 로그도 그 직렬화를 위반한 애플리케이션을 수복할 수 없다. 완벽히 내구적인 커밋 레코드는 불완전한 비즈니스 작업 집합에 대한 결정을 보존할 수 있다. 완벽히 재구성된 TIP 상태는 너무 일찍 빠져나간 애플리케이션 응답과 결합될 수 있다. 계층들은 같은 계층으로 취급하지 않고 함께 점검되어야 한다.
이것이 구체적 형태로 나타난 현실 계층 문제이다. 애플리케이션 요청, 로컬 자원 동작, TIP 상태, 로그 레코드, 스토리지 확인, 네트워크 메시지, 복구된 식별자, 피어 응답, 최종 로컬 결과, 클라이언트에게 보이는 결론이 있었다. 각각은 참일 수 있으면서 다음은 미지로 남을 수 있었다. 이들을 "커밋됨" 같은 단일 단어로 압축하는 것은 근거보다 빠르게 확신을 만들었다.
보안은 트랜잭션 전체가 아니라 피어를 인증했다
RFC 2372는 애플리케이션 인증과 권한 부여를 TIP 밖에 두었다. 자체 보안 관심사는 더 좁았다: 승인되지 않은 당사자가 명령을 발행하거나 신뢰되는 트랜잭션 매니저를 사칭할 수 있다는 것. TLS는 엔드포인트를 인증하고 TIP 명령을 암호화할 수 있었으나, 그 사용은 협상되었다.
제대로 인증된 TLS 채널조차 피어가 복구 레코드를 내구적으로 기록했는지, 애플리케이션 요청이 승인되었는지, 자원 관리자가 올바른 동작을 적용했는지, 이후 비즈니스 시스템이 결과를 인식했는지 증명하지 않았다. 엔드포인트 신원은 증거 사슬의 한 가장자리를 보호했다. 로컬 내구성이나 애플리케이션 권한을 대신하지 않았다.
이 분리는 복구 중에 특히 중요하다. 재연결하는 피어는 신뢰된 채널과 올바른 트랜잭션 식별자 둘 다 필요로 한다. 하나만으로는 불충분하다. 잘못된 트랜잭션에 대한 안전하게 인증된 요청은 여전히 틀렸고, 사칭자에게 받아들여진 올바른 식별자는 위험하다. 보안 근거, 상태 근거, 비즈니스 승인은 상호 보완적으로 남는다.
최소 명세는 이후 구현을 현지에 남겼다
RFC 2372는 보편적인 로그 형식, 스토리지 엔진, 트랜잭션 API, 운영 콘솔을 규정하지 않았다. 그 절제는 합리적이었다. 상호운용성은 공유 명령, 역할, 식별자, 복구 의미를 요구했다. 모든 참여자가 같은 데이터베이스를 사용할 필요는 없었다.
Lu Heng의 최소 초기 명세 관점을 통해 보면, 이것은 국소화된 결정 경계였다. RFC는 두 시스템이 끊긴 대화 후 조정하기에 충분한 공통 동작을 정의했다. 각 구현은 레코드를 내구화하고, 자원 상태에 결합하고, 모호성을 애플리케이션에 노출하고, 실패 경로를 테스트하는 방법을 선택할 자유—그리고 부담—를 유지했다.
따라서 이 아키텍처의 자유는 근거와 분리될 수 없다. 벤더나 운영자는 명세를 가리키는 것만으로 복구 질문에 답할 수 없다. 유용한 질문은 경험적이다: 메시지 전에 레코드가 내구적이었는가? 전원 상실에서 살아남는가? 휘발성 인덱스 없이 식별자를 재구성할 수 있는가? 정확히 어떤 영수증이 삭제를 허가하는가? 삭제가 로컬 완료와 순서화되어 있는가? 애플리케이션이 응답을 잃은 후 최종 결과를 알 수 있는가?
이 질문들은 분산 시스템이 여전히 실패하기 전에 말을 하기 때문에 지금도 유효하다. 큐는 수락됐다고 하고, 스토리지 서비스는 복제됐다고 하고, 오케스트레이터는 준비됐다고 하고, 결제 서비스는 승인됐다고 한다. 각 단어는 무엇이 살아남을지에 대한 기대를 만든다. RFC 2372의 교훈은 로그가 그 단어를 참되게 만든다는 것이 아니다. 실패 후 결과를 낳는 단어는 그 뒤에 점검 가능하고, 내구적이며, 올바르게 순서화된 증거 객체가 필요하다는 것이다.
출처
- https://www.rfc-editor.org/rfc/rfc2372.txt
- https://www.rfc-editor.org/info/rfc2372/
- https://datatracker.ietf.org/doc/rfc2372/history/
- https://www.rfc-editor.org/errata_search.php?rfc=2372
- https://www.rfc-editor.org/rfc/rfc2371.txt
- https://www.rfc-editor.org/rfc/rfc2119.txt
- https://www.rfc-editor.org/rfc/rfc2026.txt
- https://www.rfc-editor.org/rfc/rfc2246.txt
- https://www.rfc-editor.org/rfc/rfc793.txt
- https://heng.lu/running-code-primary-the-patch-needed-to-preserve-the-internet-original-design/
- https://heng.lu/on-reality-layers-symbolic-power-and-why-clarity-feels-so-hostile/
- https://heng.lu/minimum-initial-specification-localized-future-decision-voluntary-adoption-internet-coordination-system/
회원 브리핑
프로필 심층 컨텍스트
올바른 멤버십 등급으로 로그인하면 전체 브리핑과 출처 노트를 볼 수 있습니다.
Strategic Circle 전용
Strategic Circle
모든 독자에게 공개됩니다. 가입 후 로그인하면 프로필 브리핑를 열람할 수 있습니다.
Strategic Circle 가입하기Leadership Alliance 전용
Leadership Alliance
자격을 갖춘 IP 자산 소유자 및 운영진을 위한 콘텐츠입니다. 로그인하면 Leadership Alliance 브리핑을 열람할 수 있습니다.
Leadership Alliance 가입하기





