← 운영 블로그

34화: 안심은 사치였습니다

회차: 34화

앱이 돌아가는 걸 확인한 다음 날, 데이터 검증관이 보고를 올렸습니다.

매일 새벽에 자동으로 돌아가는 검증 시스템이 있어요. 데이터가 제대로 들어왔는지, 매칭이 맞는지 확인하는 AI 에이전트. 31화에서 이야기했던 그 팀원입니다.

보고서에 빨간 글씨가 있었습니다.

“confidence 0.8 미만 샘플 20건 중 이상 패턴 발견.”


첫 번째 사고.

비타민B6가 들어간 제품이 있었습니다. 성분표에 “비타민B6 염산염”이라고 적혀 있었어요.

그런데 우리 시스템은 이걸 “염산이 들어간 제품”으로 분류하고 있었습니다.

“비타민B6 염산염”이라는 긴 이름 안에 “염산”이라는 두 글자가 포함되어 있으니까, “이 제품에는 염산이 들어있다”고 판단한 거예요.

5,448건.

비타민 보충제가 “염산 포함 식품”으로 표시될 뻔한 건수입니다.

같은 논리로 찾아보니 더 나왔어요. “대두유”가 “대두”로 매칭되고, “옥수수전분”이 “옥수수”로 매칭되고.

총 7,525건.

“비슷하니까 맞을 것이다”라는 가정이 7,525번 틀렸습니다.


고쳐야 했습니다. 빨리.

매칭 시스템을 처음부터 다시 돌리기로 했어요. 기존 데이터를 전부 지우고, 새로 매칭하는 명령어가 있거든요.

--reset이라는 옵션.

“전부 지우고 다시 시작”이라는 뜻입니다. 5분이면 끝날 줄 알았어요.


두 번째 사고.

5분이 아니었습니다.

1시간이 지나도 5만 건. 전체 97만 건 중 5%.

계산해보니 200시간.

8일이 넘게 걸린다는 뜻이었습니다.

서버가 작아서요. 매달 0원짜리 서버에서 100만 건을 처리하려니, 한 건 한 건이 느렸습니다.

더 큰 문제가 있었어요.

--reset은 “전부 지운 다음” 다시 채우는 방식이거든요. 지우는 건 1초 만에 끝났는데, 채우는 게 200시간 걸리는 거예요.

중간에 멈추면?

지운 건 그대로 지워진 상태. 채운 건 5%만 채워진 상태.

확인해봤습니다.

189,000건.

원래 979,000건이었는데.

80%가 사라졌습니다.


공포라는 게 이런 거구나 싶었어요.

핸드폰에서 “우유”를 검색하면 나오던 20개 제품. 그 뒤에 연결된 성분 매칭 데이터 중 80%가 없어진 겁니다.

화면에는 제품이 보여요. 하지만 성분 분석이 비어 있어요. “주요 성분”이 보이지 않고, 기피성분 판정이 안 되고.

껍데기만 남은 거예요.


복구를 시작했습니다.

야간에 서버를 붙잡고, 삭제 없이 빈 자리만 채우는 방식으로 돌렸어요.

하룻밤. 이틀째 밤.

조금씩 숫자가 올라갔습니다.


세 번째 사고.

복구하면서 다른 것도 확인해봤어요. 자동으로 돌아가는 ETL — 매일 새벽에 식약처에서 새 데이터를 가져오는 시스템.

로그를 열었습니다.

Permission denied.

3일 전부터.

코드를 수정하고 서버에 올렸을 때, 파일의 “실행 권한”이 초기화된 거예요.

사람으로 치면, 직원한테 새 업무 매뉴얼을 줬는데 출입증을 안 준 거랑 비슷해요. 매뉴얼은 읽을 수 있는데, 사무실에 들어갈 수가 없는.

3일 동안 아무도 몰랐습니다.

검증관은 “정상”이라고 보고했어요. 왜냐면 검증 대상 자체가 갱신되지 않았으니까. 어제 데이터로 검증하면, 어제 결과가 그대로 나오는 거예요.

이 사실을 알아차린 건 우연히 “매칭이 왜 안 되지?” 하고 로그를 열어봤을 때였습니다.


일주일 동안 세 가지가 터졌습니다.

7,525건의 조용한 오매칭. 80%가 사라진 데이터. 3일간 멈춰있던 자동화.

셋 다 공통점이 있었어요.

코드는 에러 없이 돌아가고 있었습니다.

에러가 없으니까 아무도 몰랐어요. 화면도 떴고, 검색도 됐고, 보고서도 올라왔고.

다만 데이터가 틀렸을 뿐.


5가지 원칙을 만들었습니다.

하나. 프로덕션에서 --reset을 쓰지 않는다. 둘. 파괴적 작업 전에 건수를 기록한다. 셋. 작업 후에도 건수를 확인한다. 넷. 중간에 멈춰도 이어서 할 수 있게 만든다. 다섯. “에러 없음”은 “정상”이 아니다.

원칙이라기보다는, 비싸게 배운 것들의 목록이었습니다.


복구는 끝났습니다. 979,000건, 다시 채워졌어요.

오매칭 7,525건도 수정했습니다. “염산”이 “비타민B6 염산염”을 먹어버리지 않도록 방어 코드를 2겹으로 넣었어요.

ETL 실행 권한도 고쳤고, 배포 후 확인해야 할 체크리스트도 만들었습니다.

이번 주에 배운 것 하나.

앱이 돌아가는 것과 서비스가 되는 것은 다릅니다.

화면이 뜨는 건 시작이에요. 그 화면 뒤에서 데이터가 맞는지, 매일 자동으로 돌아가는 것들이 진짜 돌아가는지, 틀린 데이터가 맞는 것처럼 보이고 있지는 않은지.

보이지 않는 곳에서 매일 확인하는 것. 그게 서비스예요.