프로젝트
약 14분 분량 학습 프로젝트/결제 시스템

운영 자동화: 인스턴스를 둘로 늘리니 같은 정산을 두 번 집계했다

Payment운영 자동화배치멱등성결제 시스템
목차

개요

터지기 전에 잡으려고 재현해 봤습니다. 정산 배치를 서버 두 대에서 돌렸더니 같은 날짜의 거래를 양쪽이 통째로 중복 집계했습니다. 정산 결과가 두 건 남지 않은 건 제가 걸어둔 가드 덕이 아니라 DB 유니크 제약이 늦은 쪽을 롤백시켰기 때문입니다. 진입 지점의 “이미 정산했으면 건너뛴다”는 가드는 두 대가 동시에 들어오면 둘 다 “아직 없다”를 보고 지나갑니다.

결제는 승인에서 끝나지 않습니다. 카드사가 답을 안 준 결제는 누군가 다시 물어봐야 확정되고, 하루치 거래는 집계해서 가맹점에 넘겨야 하고, 에스크로는 결제가 승인돼도 판매자에게 바로 주지 않고 정해진 기간 붙잡아두는 돈입니다. 기한이 되면 풀어야 합니다. 매일 같은 시각에 같은 일이 반복돼서, 그 일들을 배치 열 개로 옮겼습니다. 이 글은 옮기고 나서 만난 것을 다룹니다.

상황다룬 것
상황 1무엇을 배치로 넘기고 무엇을 사람에게 남겼나
상황 2인스턴스를 둘로 늘리니 같은 날짜를 두 번 집계했다
상황 3배치가 멈춘 것을 어떻게 알아차리나

개인 프로젝트로 만든 결제 시스템 pay의 개발 기록입니다. 실무 운영 경험이 아닙니다.

상황 1. 매일 하던 일을 배치 열 개로 옮겼다

매일 같은 시각에 반복되는 일을 배치 열 개로 옮겼습니다. 셋은 옮기지 않고 사람에게 남겼고, 남긴 이유가 셋 다 달랐습니다.

무엇을 넘기고 무엇을 남겼나

가른 기준은 하나였습니다. 규칙만으로 결정이 끝나면 배치, 판단이 필요하면 사람.

배치로 넘긴 것은 이렇습니다.

배치하는 일주기
PaymentRecoverySchedulerUNKNOWN 결제를 PG에 다시 물어 확정60초
CheckoutRecoveryScheduler중간에 끊긴 체크아웃 복구60초
CompensationScheduler실패한 보상 작업 재시도5초
SettlementScheduler전일 거래 집계와 지급액 계산24시간
EscrowAutoReleaseScheduler기한 지난 에스크로 자동 해제60초
OrderExpiryScheduler결제 없이 방치된 주문 만료60초
VaExpiryScheduler가상계좌 만료 감지60초
DunningScheduler실패한 정기결제 재청구1시간
OutboxCleanupScheduler완료된 이벤트 정리1시간
IdempotencyCleanupScheduler만료된 멱등키 정리1시간

전부 @ConditionalOnProperty 게이트를 답니다. 프로퍼티가 꺼져 있으면 빈으로 등록조차 되지 않습니다. 테스트와 로컬 부트에서 배치가 제멋대로 도는 것을 막으려는 것입니다.

반대로 사람에게 남긴 것은 셋입니다. 대사 불일치 수기 확정, 분쟁·차지백 처리, 이상거래 사후 심사. 셋을 남긴 이유가 서로 다릅니다.

① 대사 불일치 수기 확정 : 규칙이 답하는데도 남겼다

대사는 내 결제 기록과 PG가 보내주는 정산 파일을 하루치씩 대조해 안 맞는 건을 찾는 작업입니다.

  • 고를 수 있는 원인을 여덟 가지로 정해뒀습니다. 확정하는 사람은 이 중 하나를 고릅니다. 목록에 없으면 OTHER인데, 그때는 서술을 필수로 받습니다. 억지로 기존 코드에 밀어 넣으면 집계가 오염되기 때문입니다.
원인어떤 상황인가
수수료 계산 차이내 장부 10,000원, PG 파일 9,730원. 차액 270원이 수수료율 2.7%와 맞는다
부분취소 미반영10,000원 중 3,000원을 취소했는데 PG 파일은 아직 10,000원으로 싣고 있다
거래일 경계밤 11시 58분 결제. 나는 1일자로 세고 PG는 2일자로 셌다
PG 파일 지연내 장부엔 있는데 오늘 파일에 없다. 다음 회차에 실릴 것이다
망취소 반영 시점 차이승인 직후 취소돼 PG 정산 대상에서 아예 빠졌다
내부 기록 유실PG 파일엔 있는데 내 장부엔 없다. 돈은 나갔는데 우리가 모른다
같은 거래가 두 번 실림한 거래가 정산 파일에 두 번 기록됐다
위변조 의심위 어느 것으로도 설명되지 않는다. 확정이 아니라 에스컬레이션 대상이다
  • 그중 넷은 산수로 딱 떨어집니다 : 내 장부에는 10,000원인데 PG 파일에는 9,730원이라면, 차액 270원이 수수료율 2.7%와 원 단위까지 맞습니다. 다른 해석이 없어서 사람이 볼 것도 없이 수수료 차이로 확정됩니다
  • 그런데도 버튼은 사람이 누릅니다. 이유가 그 넷 안에서도 갈립니다.
원인등급사람이 누르는 이유
수수료 차이 · 거래일 경계자동 확정 후보두 숫자가 없다. 규칙이 낸 답만으로 얼마까지 사람 없이 닫아도 되는지, 그리고 그 유형이 몇 번까지 틀려도 되는지
부분취소 미반영 · PG 파일 지연제안까지만확정하는 순간 자금이 움직인다. 판별이 맞느냐와는 다른 축이라 일부러 뺀 것
  • 둘은 자동 확정 대상에서 이미 빼기로 정했습니다. 부분취소가 반영 안 된 것과 PG 파일이 늦게 온 것입니다. 판별이 확실해도 이 둘을 확정하면 그 순간 자금이 실제로 움직입니다. 판별이 맞느냐와 되돌리기가 얼마나 비싸냐는 다른 축이라, 규칙이 아무리 확실하게 답해도 화면에 제안만 띄우고 버튼은 사람이 누르게 뒀습니다
  • 나머지 둘은 숫자가 없어서 못 켭니다. 규칙이 답을 냈다고 바로 장부를 닫으려면 금액 선허용 오류율 두 가지를 먼저 선언해야 합니다. 270원이면 사람 없이 넘어가도 되고 27만 원이면 봐야 할 텐데, 그 선이 얼마인지는 계산으로 안 나옵니다. 규칙이 이 유형에서 실제로 몇 번 틀리는지도 아직 모릅니다

→ 지금은 사람이 확정할 때마다 규칙이 무엇을 제안했고 사람이 무엇을 골랐는지를 원인 유형별로 나눠 기록하고 있습니다. 그 숫자가 쌓이면 유형별로 하나씩 켤 수 있습니다.

② 분쟁·차지백 처리 : 판정 권한이 우리에게 없다

카드 주인이 “이 결제 나는 안 했다”고 카드사에 이의를 걸면, 우리는 증빙을 모아 제출합니다.

  • 우리가 하는 일 : 배송 추적번호나 접속 로그 같은 증빙 수집과 제출. 우리 DB에 없는 자료를 밖에서 가져오는 일입니다
  • 카드사가 하는 일 : 승패 판정. 결과를 우리에게 통보해 줍니다
  • 코드가 말해 주는 것 : 분쟁을 닫는 메서드가 resolve(id, win)입니다. 승패를 밖에서 받습니다. 우리가 판정한다면 이 자리에서 증거를 보고 결정했을 것입니다

→ 규칙을 아무리 잘 짜도 자동화되지 않습니다. 규칙 문제가 아니라 권한 문제입니다.

③ 이상거래 사후 심사 : 규칙이 사람에게 보낸 것이다

이상거래 탐지는 규칙이 위험 점수를 매겨 네 등급으로 나눕니다. 통과, 추가 인증, 사람 검토, 차단.

⚠️ 이 절의 “차단”은 실제로 결제를 세우지 않습니다. 나중에 코드를 다시 보고 알았고, 아래 뒷이야기에 적었습니다.

  • 사람 검토는 규칙이 답을 못 낸 자리가 아니라 규칙이 고른 네 갈래 중 하나입니다
  • 통과와 차단을 한 줄로 가르면 그 선을 어디에 두든 한쪽이 틀립니다
임계를 낮추면임계를 높이면
정상 결제를 막는다 (매출 손실)사기가 통과한다 (차지백)

→ 애매한 구간을 떼어 사람에게 보내는 쪽을 골랐습니다. 자동화의 실패가 아니라 의도한 3분할입니다.

뒷이야기 — “차단”이 결제를 안 막고 있었습니다

이 글을 쓸 때 모듈 주석에 이렇게 적혀 있었습니다.

동일한 판정 엔진을 두 결에서 재사용한다: (1) 동기 인라인 판정, (2) 비동기 사후 탐지

(1)이 없었습니다. 판정을 부르는 곳은 사후 리스너 하나뿐이고, 그건 결제 완료 이벤트를 받는 경로입니다. 결제가 이미 끝난 뒤에 다시 평가합니다.

그래서 BLOCK 판정이 결제를 세우지 않습니다. 네 등급을 계산은 하는데, 둘만 심사 큐에 쌓이고 결제는 이미 나갔습니다.

”지연 때문에 안 붙였다”가 근거가 없었습니다

왜 승인 경로에 안 붙였냐면 판정 시간이 결제 응답에 얹히기 때문이라고 적어 뒀습니다. 그런데 제 검토 문서에는 이렇게도 적혀 있었습니다.

지연 예산을 한 번도 재본 적이 없다

재본 적 없는 걸 근거로 설계를 정하고 있었던 겁니다. 그래서 쟀습니다.

판정 한 번 (실 Redis 2,000회)p50 1.19 / p95 1.53 / p99 1.92ms
승인 경로 종단 (k6 50VU, 4,007요청)med 26.2 / p95 56.8 / p99 105.4ms, 실패 0%
붙였을 때 예상 p99약 107ms

카드 비대면 결제의 업계 예산이 250~300ms입니다. 절반도 안 씁니다. 판정이 차지하는 몫은 종단의 1.8%고요.

판정이 Redis를 카드·기기·IP로 세 번 왕복하는데도 그렇습니다. 업계가 룰 엔진에 잡는 2~5ms 안에 듭니다.

난이도도 지연도 아니었고, 그냥 재본 적이 없었던 겁니다.

그런데도 안 붙였습니다

숫자가 되는 것과 막을 준비가 된 것은 다릅니다.

  • Redis가 죽으면 결제가 어떻게 되나. 지금은 fail-open(그냥 통과)입니다. 사후 탐지에서는 괜찮지만 승인 경로에서는 그게 부정거래를 통과시키는 결정이 됩니다. 반대로 fail-close 하면 Redis 장애가 곧 결제 장애입니다
  • 막기 시작하면 오탐이 곧 매출 손실입니다. 그때부터 일부러 통과시켜 오탐율을 재는 홀드아웃이 필요해집니다. 지금은 아무것도 안 막아서 필요 없던 것이고요
  • “추가 인증” 등급을 받을 화면이 없습니다. 3DS2 흐름이 없어서 이 등급은 이름만 있습니다

이번에 닫은 건 “지연 때문에 못 한다”는 근거 없는 이유 하나입니다. 남은 셋은 그대로고, 그건 판정 속도보다 큰 일입니다.

사람에게 남긴 자리는 2인 승인으로 잠갔다

남긴 것 중에서도 강제취소는 따로 뒀습니다. 이미 승인된 결제를 운영자가 직권으로 되돌리는 일이라 사고의 크기가 다릅니다.

요청과 승인을 분리했습니다. ForceCancelRequest를 만드는 사람과 approve를 부르는 사람이 달라야 실행됩니다. 같으면 MAKER_CHECKER_VIOLATION으로 막힙니다.

public ForceCancelView approve(long requestId, String approver) {
ForceCancelRequest req = ...;
req.approve(approver); // 요청자 == 승인자면 MAKER_CHECKER_VIOLATION
... // 통과한 뒤에야 PaymentService.cancel 로 실제 취소
}

상태를 바꾸는 어드민 액션은 요청과 결과를 쌍으로 감사 로그에 남깁니다. 누가 눌렀는지가 안 남으면 사고를 되짚을 수 없습니다.

상황 2. 인스턴스를 둘로 늘리니 같은 정산을 두 번 집계했다

여기가 이 글의 본론입니다.

정산 배치는 하루에 한 번 전일 거래를 집계합니다. 두 번 돌면 가맹점 지급액이 두 배가 됩니다. 그래서 진입 지점에 가드를 걸어뒀습니다.

public Settlement settle(LocalDate date) {
if (settlementRepository.existsBySettlementDate(date)) {
log.info("정산 재실행 감지 → 건너뜀 date={}", date);
return null; // 멱등: 이미 그 날짜 정산이 존재
}
...
}

같은 인스턴스에서 배치가 두 번 돌면 이게 막습니다. 문제는 인스턴스가 두 대일 때입니다.

애플리케이션 가드는 동시 진입에 무력했다

모놀리스를 수평 확장하면 SettlementScheduler가 인스턴스마다 돕니다. 그러면 existsBySettlementDate가 동시 진입 레이스를 막지 못한다는 가설을 세우고 재현해 봤습니다.

전일 CONFIRMED 항목 2만 건을 시드하고, 같은 DB를 보는 인스턴스 두 개(8081, 8082)를 스케줄러를 켠 채 거의 동시에 기동했습니다. 배치가 2만 건을 집계하는 약 7초가 레이스 윈도입니다.

첫 틱에서 즉시 재현됐습니다.

[B :8082] 14:48:58.407 INFO 정산 배치 완료 date=2026-08-03 ... items=20000
[A :8081] 14:48:58.402 ERROR Duplicate entry '2026-08-03' for key 'settlements.uk_settlement_date'
[A :8081] 14:48:58.416 ERROR 정산 배치 실패 date=2026-08-03 DataIntegrityViolationException

두 인스턴스가 같은 틱에 settle(어제)에 들어갔습니다. 그 시점에 정산은 아직 없으니 existsBySettlementDate양쪽 모두 false입니다. 양쪽이 2만 건을 통째로 중복 집계한 뒤 5밀리초 차이로 커밋을 다퉜고, 늦은 쪽이 유니크 제약에 막혔습니다.

최후 방어선은 DB 유니크 제약이었다

돈은 두 번 나가지 않았습니다. 트랜잭션 전체 롤백과 settlement_date 유니크 제약이 받아냈습니다.

여기서 멱등키와 헷갈리면 안 됩니다. 결제의 멱등키는 일을 하기 전에 PROCESSING 행을 먼저 넣고, 그 INSERT에 성공한 쪽만 실제 작업으로 들어갑니다. 그건 진짜 실행권입니다. 정산은 반대입니다. 2만 건을 다 집계한 뒤에 저장하다 부딪힙니다.

유니크 제약은 실행권을 정하지 못했고, 중복 결과가 커밋되는 것만 막았습니다. 두 인스턴스가 비싼 집계를 각각 끝낸 뒤에야 승자가 갈렸으니, 이건 실행 제어가 아니라 정합성의 최후 방어선입니다. 정합성을 지킨 것과 실행 주인을 정한 것은 같은 일이 아니었습니다.

그러니 정합성은 지켜졌습니다. 그런데 가드가 일한 게 아니라 제약이 일했습니다. 실제로 벌어진 일은 이렇습니다.

  • 인스턴스 수만큼 2만 건을 전량 중복 집계합니다. 무의미한 SELECTUPDATE 시도입니다
  • 늦은 쪽은 매 주기 배치 실패 에러를 남깁니다. 정상 동작인데 로그는 장애처럼 보입니다
  • 인스턴스를 늘릴수록 낭비가 비례해서 커집니다

근본 원인은 스케일 단위와 배포 단위가 묶여 있다는 것입니다. 결제 트래픽 때문에 인스턴스를 늘리면 원하지도 않은 정산 스케줄러까지 복제됩니다. 오토스케일링과 양립하지 않습니다.

그런데 단일 인스턴스로 두면 배포마다 결제가 멈춘다

그럼 인스턴스를 하나로 두면 되나 싶어 그쪽도 재봤습니다. 체크아웃 30 VU를 3분간 깔고 60초 시점에 앱을 재시작했습니다. 알림 모듈 한 줄을 고쳐 배포하는 상황의 재현입니다.

항목
다운타임 (SIGTERM에서 헬스 UP까지)9초
실패 요청270건 / 9,870건 (2.7%)
실패 유형100% connection refused. 5xx는 0건

5xx가 0건인 게 중요합니다. 서버가 이미 받아들인 요청 중에 5xx로 끊긴 것이 없었고, 실패 270건은 전부 프로세스가 내려가 있어 수용 자체가 안 된 connection refused였습니다. 적어도 이번 실험에서는 20초 드레이닝이 인플라이트 결제를 지켰습니다. 다만 이건 단일 인스턴스 재시작이라 롤링 배포가 필요하다는 근거이지 정산 분리의 직접 근거는 아닙니다. 9초는 Gradle 데몬이 워밍된 로컬 최적값이라 실제 배포는 더 깁니다.

두 실측을 겹치니 지금 배포 구조에 문제가 있었습니다.

  • 배포 중단을 없애려면 인스턴스 두 대와 롤링이 필요합니다. 그러면 스케줄러 중복이 터집니다
  • 중복을 피해 한 대로 두면, 결제와 무관한 모듈을 고칠 때마다 결제가 전면 중단됩니다

모놀리스라서 막힌 건 아닙니다. 실행권만 떼어내는 방법이 여럿 있고, 그중 하나는 이 저장소에서 오늘 당장 됩니다.

방법이 저장소에서
배치 전용 프로필로 한 대만 실행지금 됩니다. 스케줄러 열 개가 전부 @ConditionalOnProperty 게이트라 API 쪽은 끄고 배치 한 대만 켜면 됩니다
집계 전에 (정산일, PROCESSING)을 먼저 넣어 선점코드 한 겹. 대신 실행 중 죽은 건을 되살릴 만료·재시도 정책이 따라붙습니다
ShedLock(여러 대 중 한 곳에서만 스케줄러가 돌게 막는 라이브러리)이나 DB advisory lock의존을 하나 들입니다
리더 선출, 외부 스케줄러(쿠버네티스 CronJob)운영 기반이 먼저 필요합니다

그러니 실측이 입증한 것은 “지금처럼 API와 스케줄러를 같은 실행 단위로 복제하면 중복 집계와 낭비가 난다” 까지입니다. “분리해야만 풀린다”는 입증하지 않았습니다.

그런데도 분리를 검토하게 된 이유는 따로 있습니다. 정산은 결제 API와 실행 주기도, 확장 단위도, 배포하는 이유도 달랐습니다. 중복만 막을 거면 위 표의 첫 줄로 충분합니다. 그걸 넘어 정산을 별도 실행 단위로 떼는 쪽을 검토하게 된 근거가 이 차이입니다. 코드베이스나 도메인까지 MSA로 쪼갠다는 뜻은 아닙니다.

세운 가설 하나는 기각했다

가설이 셋이었고, 하나는 재현되지 않았습니다.

같은 JVM과 같은 커넥션 풀을 쓰니 정산 배치가 결제 API 지연을 오염시킬 것이라고 봤습니다. 90초 베이스라인 뒤에 배치를 12초 간격으로 연속 트리거해 경합 구간을 만들었습니다.

구간p95p99
베이스라인 (배치 없음)64.3ms91.1ms
경합 구간 (배치 7연타)54.1ms68.0ms

배치는 7회 완주했는데 결제 지연은 흔들리지 않았습니다. 오히려 베이스라인이 높은데 초반 워밍업 때문으로 보입니다. 커넥션 풀 pending0이었습니다. 배치가 커넥션 하나를 10초씩 잡아도 풀에 여유가 남아 대기 큐 자체가 생기지 않았습니다.

이 규모에서는 근거가 아니므로 근거로 쓰지 않습니다. 풀을 줄이거나 항목이 수십만 건이 되면 달라질 수 있고, 그건 그때 다시 재야 합니다.

상황 3. 배치가 멈춘 것을 어떻게 알아차리나

배치는 조용히 멈춥니다. 요청이 없으니 에러율도 안 오르고, 응답 시간도 안 나빠집니다. 흔한 서버 지표만 보면 시스템은 건강해 보입니다.

그래서 지표를 직접 만들었습니다.

지표무엇을 말하나
payment.unknown.oldest.age가장 오래된 미확정 결제가 몇 초째 떠 있나
recon.pending.count사람 확인이 필요한 대사가 몇 건 밀려 있나
outbox.pending.oldest.age발행 못 한 이벤트가 몇 초째 남아 있나
outbox.pending.count밀린 이벤트 건수
password.hash.legacy.count아직 옛 해시로 남은 계정 수

앞의 셋이 요점입니다. 복구 배치가 죽으면 payment.unknown.oldest.age가 계속 올라갑니다. 결제는 정상으로 보이는데 미확정만 쌓이는 상태를, 이 지표 하나가 드러냅니다.

이 지표들에 임계를 걸어 알림을 코드에 박았습니다.

알림조건
UnknownPaymentAging미확정이 600초 넘게 방치
ReconPendingBacklog미해결 대사가 15분 넘게 존재
OutboxConsumptionStalled미발행 이벤트가 300초 넘게 대기
CompensationExhausted보상 재시도가 소진됨
PaymentSuccessRateLow승인 성공률 95% 미만
DeadlockRetrySpike데드락 재시도가 분당 10회 초과
PasswordHashMigrationStalled옛 해시가 7일째 남아 있음
GlobalRateLimitBinding전역 유입 제한이 15분째 걸림
FraudReviewBacklog이상거래 심사가 1시간 넘게 대기
AssistResidualNoSuggestion모델은 불리는데 제안이 30분째 0건
AssistUnsourcedFiguresSpike숫자 검증기에 걸리는 비율 20% 초과

사람이 봐야 하는 큐에도 같은 잣대를 댔다

여기까지 만들어 놓고 한참 뒤에 깨달은 게 있습니다. 알림이 전부 결제와 대사 쪽이었습니다.

payment.unknown.oldest.age는 「미확정이 오래 방치되면 위험하다」는 생각으로 만든 지표입니다. 그런데 이상거래 심사 큐에는 같은 논리를 적용하지 않았습니다. 심사가 밀리면 사기로 의심되는 결제가 그대로 정산으로 넘어가는데도요.

그래서 같은 모양으로 붙였습니다. 다만 건수가 아니라 나이를 봅니다.

Gauge.builder("fraud.review.oldest.age.seconds", ...)

큐 깊이만 보면 적체를 놓칩니다. 열 건이 방금 들어온 것과 한 건이 이틀 묵은 것은 위험이 다릅니다. 찾아보니 업계에서도 큐 알림은 「가장 오래된 항목이 임계를 넘을 때」로 잡습니다.

AI 쪽은 조용히 멈추는 것을 잡는다

AI를 붙인 자리에도 알림이 없었습니다. 이건 성격이 좀 다릅니다.

핀테크에서 프로덕션 LLM 실패의 대부분은 모델 실패가 아니라 관측 실패다. 거절률이 2주 동안 오른 걸 아무도 못 봤거나, 조용한 API 변경 뒤 품질이 떨어졌거나

가드에 다 걸려 제안이 0건이 되면 기능이 꺼진 것과 같은데 에러는 한 건도 안 납니다. 모델은 정상적으로 불리고, 가드도 정상적으로 걸러내고, 그냥 아무것도 안 나갑니다.

카운터는 이미 있었습니다. assist.residual.outcome에 기권·가드 차단·성공이 열 종류로 쌓이고 있었고, 없던 건 임계뿐이었습니다. 둘을 걸었습니다.

  • 모델은 불리는데 통과한 제안이 30분째 0건
  • 숫자 검증기에 걸리는 비율이 20%를 넘음 (이 가드는 원래 드물게 걸려야 합니다. 비율이 뛰면 프롬프트나 모델이 바뀐 것입니다)

CompensationExhausted는 임계가 0입니다. 보상이 재시도를 다 쓰고도 실패했다는 건 자동으로 풀 방법이 없다는 뜻이라, 한 건이라도 나면 사람이 봐야 합니다.

세 상황을 관통하는 것

배치를 만드는 일은 쉬웠습니다. @Scheduled를 붙이면 됩니다.

어려운 건 “이 일의 주인이 누구인가” 였습니다. 인스턴스가 한 대일 때는 물어볼 필요가 없던 질문인데, 두 대가 되는 순간 답이 없다는 게 드러났습니다. 제가 걸어둔 애플리케이션 가드는 그 질문에 답하지 못했고, 답한 것은 DB 유니크 제약이었습니다. 정합성은 지켰지만 그건 최후 방어선이 일한 것이지 설계가 일한 것이 아닙니다.

그리고 자동화는 멈춘 것이 안 보인다는 성질을 같이 가져옵니다. 사람이 하던 일은 안 하면 티가 나는데, 배치는 안 돌아도 아무 소리가 안 납니다. 그래서 옮긴 일마다 “이게 멈추면 어느 숫자가 움직이나”를 같이 정해야 했습니다.

다음 편은 자동화하지 못하고 사람에게 남긴 자리에서 세 번 놓친 이야기입니다.

프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.