되돌릴 수 없는 변경 · 변경 결과를 증거로 설명하기 · 이론
변경 결과를 증거로 설명하기
한 줄 요약
변경 보고서는 승인·실행 감사·관측한 현재 행을 같은 시점에서 대조하고, 확인하지 못한 사실을 완료라는 말에 섞지 않아야 합니다.
왜 이게 필요했나
외계인 축제의 취소 작업이 끝났다는 메시지를 받았습니다. 운영 담당자는 “세 건 처리했습니다”라고 말하고 고객은 “세 명 모두 원래대로 되었나요?”라고 묻습니다. 같은 세 건이라는 숫자가 서로 다른 질문을 가리고 있습니다. 승인한 세 ID가 맞는지, 그때 변경이 확정됐는지, 이후 다른 담당자가 바꾸지 않았는지, 지금도 그 상태인지가 모두 다릅니다.
보고서를 길게 쓰면 문제가 해결될까요? 근거 없는 완료 문장을 세 페이지로 늘리면 더 그럴듯한 오해가 됩니다. 이번에는 DB를 다시 고치는 사람이 아니라, 이미 실행된 변경을 조사하고 고객이 다음 행동을 결정할 수 있게 설명하는 담당자가 됩니다. 입력은 승인 목록, 실행 당시의 감사, 관측 시점의 현재 행입니다. 출력은 기계가 검사할 구조와 사람이 읽을 설명을 함께 가진 작은 증거 묶음입니다.
앞의 청크 수업은 작업을 이어서 실행했습니다. 이번 보고 도구에는 실행 권한을 맡기지 않습니다. 불일치가 발견되면 보류 이유를 적고, 승인이나 현재 값을 보고서에 맞게 덮어쓰지 않습니다. 보고를 초록색으로 만들기 위해 사실을 바꾸는 순간 조사와 실행의 경계가 사라집니다.
어떻게 동작하나
1. 건수보다 먼저 집합과 의미를 맞춘다
승인 ID가 1·2·5이고 감사 ID가 1·2·31이라면 둘 다 세 건이지만 완료가 아닙니다. 승인 밖 ID 31은 확인할 사고 후보이고 ID 5는 감사가 없는 대상입니다. 두 집합의 길이를 비교하는 것만으로는 이 차이를 발견할 수 없습니다.
ID도 충분하지 않습니다. 감사의 이전 revision, 이후 revision, 수량이 승인한 내용과 일치해야 해당 변경의 확정 근거로 셉니다. 감사가 기록한 이후 revision은 승인 revision+1이어야 합니다. 현재 행의 고객·수량·상태·revision은 그 뒤 다시 바뀌었는지를 확인하는 별도 자료입니다. 같은 cancelled 상태라도 revision이 더 크면 이후 작업이 있었을 수 있으므로 조용히 일치로 처리하지 않습니다.
이번 고객 계약의 분류는 다음과 같습니다. committed는 승인과 정확히 맞는 감사가 있는 ID, remaining은 나머지 승인 ID입니다. committed 중 현재 값도 맞으면 matching, 현재 행이 없으면 missing, 그 외는 drifted입니다. 승인 밖 감사나 전후 버전·수량이 다른 감사는 invalid_audit로 따로 남깁니다. 형식 자체가 깨졌거나 ID가 중복된 자료는 분석에 앞서 거절합니다.
감사가 없는 remaining의 현재 상태만 보고 미실행이라고 단정할 수는 없습니다. 다른 경로의 작업이 있었거나 감사가 누락됐을 수도 있습니다. 이 수업은 “확정 근거가 없다”로 보고합니다. 실제 실행을 다시 시작하려면 이전 수업의 현재 조건·새 승인 검증이 필요합니다. 보고서를 실행 허가서로 쓰지 마세요.
2. 여러 SELECT가 같은 순간을 보게 한다
승인을 읽은 다음 다른 연결이 주문과 감사를 함께 확정합니다. 보고 도구가 그 뒤 감사와 주문을 읽으면 첫 쿼리는 이전 시점, 다음 쿼리는 새 시점일 수 있습니다. BEGIN을 넣었다는 사실만으로 여러 조회가 같은 순간이 되지는 않습니다.
이번 수집은 PostgreSQL의 REPEATABLE READ READ ONLY 트랜잭션을 사용합니다. 같은 관측 안의 일반 테이블 조회를 일관된 스냅샷으로 유지하면서 보고 코드가 업무 테이블을 수정하지 못하게 합니다. [격리 수준 공식 문서](https://www.postgresql.org/docs/16/transaction-iso.html)에서 Read Committed와 Repeatable Read의 조회 시점을 비교하고, [SET TRANSACTION](https://www.postgresql.org/docs/16/sql-set-transaction.html)의 READ ONLY 제한도 읽어 보세요.
실습은 문자열로 격리 수준을 적었는지만 보지 않습니다. after-plan 또는 after-audit 지점에서 다른 연결이 실제 주문·감사를 커밋합니다. 이번 수집에는 새 변경이 섞이지 않고, 수집을 끝낸 뒤 다시 관측할 때만 보여야 합니다. 읽기 전용 구간에 UPDATE를 시도하면 실제 DB가 거절하는지도 확인합니다.
이 보장은 모든 업무 데이터가 올바르다는 보장이 아닙니다. 원래 감사가 손상돼 있으면 일관된 스냅샷에도 그 손상이 그대로 보입니다. 여러 시스템의 외부 API까지 같은 순간으로 묶어 주지도 않습니다. 이번 대상은 같은 PostgreSQL 안의 일반 테이블들이며 수집한 자료의 업무 정합성은 그 다음 단계에서 별도로 검사합니다.
3. 빌린 연결의 상태를 돌려준다
psycopg 연결은 재사용될 수 있습니다. 이번 호출에서 읽기 전용·격리 수준·짧은 제한 시간을 설정했다면 다음 호출에 그대로 남기지 않아야 합니다. 이 실습은 autocommit=True이며 호출 시작 시 열린 트랜잭션이 없는 연결을 계약으로 받습니다. 수집 문맥 안에서만 설정을 적용하고 성공·실패 뒤 IDLE로 돌아갑니다.
빌린 연결은 닫지 않고, publish가 스스로 만든 연결만 닫습니다. 파일을 쓰는 동안 DB 스냅샷을 계속 열고 있을 이유도 없습니다. 자료를 수집한 뒤 연결을 닫고 나서 직렬화와 파일 교체를 진행합니다. [psycopg 트랜잭션 관리](https://www.psycopg.org/psycopg3/docs/basic/transactions.html)를 보고 중첩 트랜잭션과 실제 커밋의 차이를 다시 확인하세요. 문서 버전과 별개로 실습에서는 설치된 psycopg 3.2.3에서 실행합니다.
4. 사람에게 보여 줄 설명도 같은 근거로 만든다
기계용 결과에 hold라고 적어 놓고 고객 설명에는 “완료했습니다”라고 쓰면 자료는 내부에서 이미 충돌합니다. 이번 번들은 evidence, report, summary를 함께 넣습니다. summary는 같은 analyze 결과에서 승인·확정·미확정·현재 일치·후속 차이·누락·감사 불일치 ID를 보여 주는 평문입니다.
완료라는 단어도 범위를 붙입니다. complete는 “관측 시점 완료”이고, 관측 이후 상태까지 유지된다는 약속이 아닙니다. 문제가 있는 감사·후속 차이·누락이 있으면 hold, 그런 문제가 없더라도 확정 근거가 없는 승인 ID가 남으면 incomplete입니다. 완료보다 보류가 먼저인 이유는 나머지를 실행하기 전에 현재 이상을 설명해야 하기 때문입니다.
보고서는 SQL이나 HTML이 아닙니다. JSON의 문자열과 숫자는 데이터로 다루고 실행하지 않습니다. 시각과 스냅샷 표기는 나중에 어느 관측을 말하는지 식별하는 메타데이터입니다. 값을 직접 입력할 수 있다는 사실을 잊고 공인 인증 시각처럼 설명하면 안 됩니다.
현장에서 만나는 모습
해시가 맞는데도 거짓 보고일 수 있다
직렬화할 때 키 순서·공백·UTF-8 표현을 고정하면 같은 정규 데이터의 바이트와 해시가 같아집니다. 이는 이번 형식의 로컬 규칙이며 모든 JSON 도구가 자동으로 같은 바이트를 만드는 표준이라고 주장하지 않습니다. 숫자와 문자열, 불리언을 구분하고 중복 키와 NaN 같은 입력도 거절합니다. [Python JSON 문서](https://docs.python.org/3.12/library/json.html)의 직렬화 옵션과 object_pairs_hook을 참고하세요.
해시는 내용이 바뀌었는지 대조하는 수단이지 출처를 인증하는 서명이 아닙니다. 누군가 판정만 complete로 바꾸고 해시를 다시 계산하면 단순 해시 검사에는 통과할 수 있습니다. 그래서 evidence에서 report와 summary를 다시 계산해 함께 비교합니다. 반대로 관측 시각 등 증거 전체를 일관되게 새로 만들고 해시도 계산하면 이 검사만으로 허구를 판별할 수 없습니다. 실습은 이 경우가 통과하는 반례도 보여 줍니다. 내부 일치와 진위는 다른 요구입니다.
실제 고객에게 전달할 때는 신뢰할 수 있는 수집 경로, 접근 통제, 변경 불가 기록과 승인 체계가 추가로 필요할 수 있습니다. 이번 파일 형식을 만들었다고 그 조직의 증거 보존 정책을 모두 구현했다고 말하지 않습니다. 사용자 인증과 고객별 DB 접근 권한도 이 분석 함수의 역할이 아닙니다.
보고서 파일을 절반만 덮은 채 실패하면
기존 report.json을 쓰기 모드로 먼저 열면 뒤의 오류가 이전의 완성본까지 지울 수 있습니다. 같은 디렉터리의 별도 임시 파일에 완성 바이트를 쓰고 닫은 뒤 os.replace로 목적지를 바꿉니다. 교체 전 오류면 옛 파일이 남고, 교체 후 응답만 잃었으면 새 파일이 완성된 상태로 남습니다. [os.replace 문서](https://docs.python.org/3.12/library/os.html#os.replace)와 실패 위치를 함께 읽으세요.
실습은 일반 예외에서 자기 임시 파일을 정리하고 다른 파일을 건드리지 않는지도 검사합니다. 프로세스 강제 종료에서는 finally가 실행되지 않을 수 있고 전원 장애에는 파일시스템 내구성 문제가 더해집니다. 이번 검증은 정상 실행과 주입한 예외의 파일 교체 보존이며 강제 종료·디스크 손상·전원 장애까지 증명하지 않습니다.
FDE의 보고는 다음 결정을 돕는 일이다
[Palantir FDSE 공고](https://jobs.lever.co/palantir/dab396d4-2f14-4796-aac0-0d82883dccf0)는 고객 맞춤 구현과 여러 이해관계자와의 협업을 다룹니다. 이를 근거로 고객이 읽을 설명과 기술팀이 재검증할 구조를 함께 만드는 과제를 설계했습니다. 해당 회사의 내부 양식이나 필수 기술 패턴을 재현했다는 뜻은 아닙니다.
다음 실습에서 할 것
우주 축제의 변경 증거를 일관되게 수집하고, 같은 건수의 다른 ID·감사 버전 오류·후속 변경을 분리합니다. 사람이 읽을 설명과 기계 판정을 같은 근거로 만들고, 해시를 다시 계산한 잘못된 결론과 중복 JSON 키도 거절합니다. 마지막에는 읽기 전용 DB 수집부터 기존 파일을 보존하는 발행까지 연결합니다.
실습은 가상 데이터와 학생별 일회용 DB만 사용합니다. 외부 고객에게 보고서를 보내거나 운영 DB를 수정하는 작업은 없습니다.