본문 바로가기
C.W.K.
Stream
Lesson 06 of 06 · published

승인된 재구매

~11 min · authorized-rerun, identity, audit-trail, escape-hatch

Level 0Empty Shelf
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"같은 걸 두 번 사야 할 때가 진짜로 있어. 그렇다고 '두 번 못 산다'는 보증을 헐어도 된다는 뜻은 아니야."

보증이 남기는 구멍

이 트랙 2번 레슨에서 세운 신원은 빈틈이 없어. 같은 오디오, 같은 provider 설정이면 유료 호출은 딱 한 번. 영원히. 그런데 아카이브가 실제로 만들어낸 상황은 이래. 어떤 영상의 transcript 가 나빠. 구조적으로 깨진 것도 아니고, 그냥 사람이 보기에 틀린 거야. 새 증거를 받고 싶어. 오디오는 안 바뀌었고 설정도 안 바뀌었어. 그러니 content-addressed key 도 그대로고, ledger 는 완료된 행을 찾아서 예전 응답을 영원히 돌려줄 거야. 보증은 설계대로 정확히 동작하는 중이고, 그게 정당하게 원하는 걸 앞에서 막고 서 있어.

대부분의 시스템이 자기 최악의 플래그를 얻는 순간이 바로 여기야. 틀린 해법 두 개가 떠오르는데, 둘 다 한 줄이라서 달콤해.

  • ledger 행을 지운다. 키가 비니까 다음 시도가 결제하고, 끝. 동시에 처음에 돈을 냈다는 기록도 같이 사라져. 예약 ledger 가 존재하는 이유가 바로 그 기록인데 말이야. 돈 문제를 돈 이력을 지워서 고치는 셈이지.
  • ledger 검사를 건너뛰는 플래그를 단다. --force-pay--ignore-cache 든. 동작은 해. 그리고 동시에, 앞으로 영원히 모든 operator 의 반사신경 한 번 거리에 무방비 유료 POST 를 놓아두는 거야. 뭔가 막힌 것 같을 때 사람들이 반사적으로 집는 플래그가 어떻게 되는지는 트랙 5 에서 이미 봤지.

대신 신원을 늘려

Recall 의 답은 보증을 아예 안 건드려. 신원에 세 번째 성분을 더해. 유료 호출 키가 이제 쌍이 아니라 삼중항이야. 오디오 청크 해시, 설정 해시, 그리고 0 에서 시작해 늘어나기만 하는 rerun_generation 정수. DB 의 유일성 제약이 셋 다를 덮어.

이게 뭘 사주는지 보자. generation 0 안에서는, 그러니까 파이프라인이 지금껏 돌린 모든 평범한 transcription 에서는, 바뀐 게 없어. 같은 바이트 + 같은 설정은 여전히 한 키로 접히고, 여전히 완료된 행을 찾고, 여전히 두 번 결제를 거부해. 보증은 1 바이트도 안 약해졌어. 반면 generation 은 진짜로 다른 키라서, 아무것도 못 찾고, 정직하게 예약하고, 결제해. at-most-once 에 예외를 만든 게 아니야. at-most-once 가 똑같이 엄격하게 성립하는 두 번째 차선을 만든 거지.

그리고 generation 이 건너뛴 검사가 아니라 저장된 컬럼이라서, 흔적이 남아. 어떤 영상의 다음 generation 은 그 영상이 써본 최고값보다 하나 위야. 그러니까 그 숫자 자체가 "우리가 이걸 의도적으로 몇 번 다시 샀지?" 에 답해. 건너뛴 검사는 아무것도 답 못 하고. audit trail 과 구멍의 차이가 정확히 거기야.

누가 그걸 찍을 수 있나

차원은 그걸 움직일 수 있는 권한만큼만 안전해. 그래서 generation 을 찍는 건 자동으로 일이 돌아가는 어느 자리에서도 닿지 않게 일부러 떼어놨어. worker 도 아니고, retry 도 아니고, --force 도 아니야. force 는 트랙 5 에서 설명한 그대로 유료 키를 그냥 안 건드려. 유일한 경로는 operator 가 콘솔에서 특정 영상의 Retranscribe 를 누르는 것뿐이고, 그 액션은 움직이기 전에 먼저 preview 를 보여줘. preview 가 spends_scribe_credits 를 대놓고 말하고, 영상을 이름으로 짚고, 곧 찍힐 generation 이 몇 번인지 알려줘.

승인 한 번이 그 영상의 모든 오디오 청크를 덮어. 카운터가 영상 단위로 추적되고 job 에 찍히거든. 그게 맞는 granularity 야. 사람이 영상 하나를 보고 돈 쓸 가치가 있다고 판단했으면, 기계는 정확히 그 영상에만 돈을 쓰는 거지.

이 시스템엔 비슷해 보이지만 다른 문이 하나 더 있어. 구분해둘 값어치가 있어. ambiguous 제출을 명시적 유료 재실행 승인으로 해소하는 경로(트랙 4, 3번 레슨)는 같은 ledger 행을 다시 열어. 같은 키, 같은 generation 으로. 그 상황은 "내가 결제했는지 모르겠다" 니까. Retranscribe 는 새 generation 을 찍어. 그 상황은 "결제한 거 알아, 그리고 또 낼 거야" 니까. 불확실성이 다르면 문도 달라. 그리고 둘 다 반사적으로 누를 플래그는 아니고.

아무도 예상 못 하는 뒷정리

preview 안에 통째로 훔쳐갈 만한 디테일이 하나 있어. 뭘 큐에 넣기 전에, Retranscribe 는 이 영상의 현재 transcript 가 이미 지식 corpus 로 승인됐는지(트랙 7) 확인하고, 그랬다면 그 사실을 follow-through 로 job 에 실어 보내. 새 증거를 사면 새 release 가 나오고, 그 말은 하류에서 승인돼 있던 그것이 곧 틀린 것이 된다는 뜻이거든. 재구매가 자기가 무효로 만든 걸 고치러 가야 한다는 걸 기억하는 거야.

일반적인 모양이 이거야. 승인된 예외는 자기가 부른 결과를 스스로 쫓아가야 해. 문을 설계하고 나서, 하류의 모든 게 그 문이 방금 갈아치운 것 위에 지어져 있었다는 걸 잊기가 너무 쉬워.

Code

키가 예외가 아니라 세 번째 성분을 얻는다·sql
-- 전: 같은 바이트 + 같은 설정 = 유료 호출 한 번, 영원히.
--   UNIQUE (chunk_input_sha256, configuration_sha256)

-- 후: 명시적으로 승인된 차원이 키에 합류.
CREATE TABLE provider_submissions (
  submission_id        TEXT PRIMARY KEY,
  chunk_input_sha256   TEXT NOT NULL,
  configuration_sha256 TEXT NOT NULL,
  rerun_generation     INTEGER NOT NULL DEFAULT 0
                         CHECK(rerun_generation >= 0),
  status               TEXT NOT NULL,
  response_json        TEXT,
  UNIQUE (chunk_input_sha256, configuration_sha256, rerun_generation)
);

-- generation 0 = 평범한 모든 실행. at-most-once, 그대로.
-- generation 1 = 사람이 "이건 다시 사" 라고 기록에 남긴 것.
-- generation 하나하나 안에서 보증은 똑같이 엄격해.
generation 찍기는 operator 의 행위이고, 먼저 preview 를 거친다·python
# ledger 조회는 그대로 -- 세 번째 키 조각만 같이 들고 갈 뿐.
existing = ledger.find(chunk_hash, config_hash, rerun_generation)
if existing and existing.status == 'completed':
    return existing.response          # POST 없음, 청구 없음

# 새 generation 은 콘솔 Retranscribe 액션만이 찍는다.
def preview_retranscribe(video_id):
    return {
        'video_id': video_id,
        'paid_rerun_generation': next_generation(video_id),  # 최고값 + 1
        'spends_scribe_credits': True,      # 행동 전에, 대놓고
        'lantern_follow_through': current_release_is_approved(video_id),
    }

# 닿을 수 없는 곳: --force, worker, 모든 retry 경로.
# next_generation() 은 영상 단위 -- 승인 한 번이 그 영상의
# 모든 청크를 덮고, 그 밖의 어떤 것도 안 덮는다.

External links

Exercise

네 일에서 유일성이나 at-most-once 보증을 하나 찾아봐. dedupe 키, unique index, idempotency key, '한 번만 보냄' 규칙 같은 것. 이제 그 연산을 정당하게 반복해야 하는 날을 상상해. 지금의 탈출구는 뭐야? 검사를 건너뛰는 플래그야, 아니면 어느 runbook 에 적힌 수동 행 삭제야? 그걸 키의 추가 성분으로 다시 설계하고, 누가 그 값을 올릴 수 있는지 정하고, 그 반복이 하류의 무엇을 무효로 만드는지 이름 붙여봐.
Hint
좋은 탈출구와 구멍을 가르는 질문 세 개. (1) 쓰고 나서도 몇 번 썼는지, 누가 썼는지 셀 수 있어? 못 세면 그건 우회야. (2) 새 차원의 값 하나하나 안에서 보증이 여전히 완전히 강제돼? 그래야 해. 규칙을 없애는 게 아니라 차선을 더하는 거니까. (3) 지금 갈아치우려는 것에서 뭐가 파생됐고, 탈출구가 그걸 고치러 갈 줄 알아?

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.