기술

저장은 무조건, 색인은 뒤에서 — 색인 실패를 상태 칸에 남겼습니다

2026.10.069분 읽기

사내 문서 지식그래프 검색 도구에는 사람이 직접 쓰는 글이 다섯 종류 있습니다. 사내업무·메모·회의록·업무일지·댓글입니다. 글을 저장하면 문서 엔진에 색인을 보내고, 그 색인이 나중에 채팅과 검색의 근거가 됩니다. 쓰는 사람 입장에서 색인은 보이지 않는 뒷일이고, 화면에서 하는 일은 글을 쓰고 저장 버튼을 누르는 것뿐입니다.

개발 경과를 이 도구 안에 사내업무 글로 남기기로 하면서 글 저장 흐름을 들여다봤습니다. 그때 색인 실패를 다루는 방식이 표마다 제각각이라는 것이 드러났습니다. 한 코드베이스 안에서 같은 사람이 만든 것인데도, 표를 하나씩 붙일 때마다 그때그때 다른 선택을 해 놓은 상태였습니다.


같은 일인데 다섯 자리에서 다섯 번 다르게 처리했습니다

다섯 표의 처리가 둘로 갈려 있었습니다. 사내업무와 메모는 저장과 색인이 한 트랜잭션이었습니다. 엔진이 502 를 돌려주면 트랜잭션이 되돌려져 글 자체가 저장되지 않았습니다. 검색 기능이 멈춘 것이 아니라 글쓰기가 멈추는 구조였습니다.

회의록·업무일지·댓글은 반대였습니다. 색인 호출이 실패해도 예외를 잡아 그대로 넘어갔습니다. 글은 정상 저장되고, 화면에도 아무 표시가 없고, 검색에서만 빠집니다. 쓴 사람도 읽는 사람도 그 글이 검색에 없다는 사실을 알 방법이 없었습니다.

바로 전날 감시 스크립트가 바쁜 엔진을 멈추게 한 일이 있었습니다. 그래서 '엔진이 멈춘 날'은 설계 문서에 적어 두는 가정이 아니라 이미 겪은 날이었습니다. 그날 사내업무 글을 쓰려던 사람은 저장 버튼을 눌러도 글을 남기지 못했을 겁니다.

색인 실패를 막는 방식과 삼키는 방식이 같은 병이라는 것을 좌우로 대비한 그림


막는 쪽과 삼키는 쪽은 정반대가 아니라 같은 병입니다

고칠 방향을 정하기 전에 선택지 셋을 나란히 놓고 봤습니다.

한 트랜잭션

실패 삼킴

저장 독립 + 상태 칸

엔진이 멈췄을 때

글을 못 쓴다

글은 써지고 검색에서 빠진다

글은 써지고 반영 중으로 보인다

실패를 누가 아나

쓰는 사람(에러 화면)

아무도 모른다

화면 표시로 누구나

복구

사람이 다시 쓴다

방법 없음

잡이 주기적으로 자동

상태의 정직함

항상 맞는다

항상 색인됨으로 보인다

대기가 진실을 말한다

첫째 줄과 둘째 줄은 정반대로 보이지만 같은 병입니다. 부수 작업의 실패를 본 저장의 문제로 만들거나, 아예 없는 일로 만들거나입니다. 어느 쪽이든 실패가 데이터로 남지 않습니다. 셋째 줄만 실패를 데이터로 남깁니다. 그래서 복구할 수 있고 사람 눈에 보일 수 있습니다.

삼키는 쪽이 막는 쪽보다 나쁘다고 판단했습니다. 막으면 사람이 그 자리에서 압니다. 삼키면 상태가 '색인됨'인 채로 굳고, 나중에 되살리려 해도 어느 행이 실패했는지가 어디에도 남아 있지 않습니다. 실제로 이번에도 옛 실패분은 복구하지 못했습니다.


색인 요청을 한 자리로 모으고 상태 칸을 뒀습니다

처방은 네 조각입니다.

다섯 표의 색인 요청을 하나의 payload 로 만드는 함수 하나로 모았습니다. 표마다 흩어져 있던 코드를 공통 층으로 올린 것입니다

저장은 무조건 성공시킵니다. 색인이 실패하면 그 행의 색인 상태 칸을 '대기'로 남깁니다. 값은 '색인됨'과 '대기' 둘뿐이고, 다섯 표 모두에 이 칸을 넣었습니다(마이그레이션 1건)

저장 직후 한 번 바로 색인을 시도합니다. 실패한 것만 2분 주기 재색인 잡이 다시 밀어 넣습니다

상태가 '대기'인 글에는 화면에 '검색 반영 중' 표시가 붙습니다

규칙을 문서에 적는 대신 자리를 하나로 만드는 쪽을 골랐습니다. 다섯 자리에 색인 코드를 다섯 번 붙였기 때문에 다섯 번 다른 선택이 나온 것이지, 규칙을 몰라서 그런 것이 아니었습니다. 같은 종류의 일이 여러 자리에 흩어져 있으면 처리는 반드시 갈립니다.


잡이 멈춰도 표시는 남습니다

네 조각 중에서 마지막 것이 가장 중요합니다. 상태는 컬럼으로 남기고, 복구는 잡이 하고, 관측은 화면이 합니다. 셋이 다 있어야 합니다.

잡만 있으면 그 잡이 멈췄을 때 아무도 모릅니다. 실패의 실패가 다시 조용해집니다

화면 표시만 있으면 사람이 손으로 다시 밀어야 합니다

상태 칸만 있으면 조회하는 사람만 알게 됩니다

'검색 반영 중' 표시는 잡이 아니라 상태 칸을 보고 붙습니다. 그래서 재색인 잡 자체가 멈춰 있어도 표시는 그대로 남고, 글을 보던 사람이 며칠째 같은 표시가 붙어 있는 것을 알아챕니다. 부수 시스템의 실패를 잡는 장치가 또 실패할 수 있다는 것을 전제로 둔 자리입니다.

예외가 나지 않았다는 것과 일이 실제로 됐다는 것은 다른 이야기입니다.

저장과 색인을 분리하고 상태 칸·재시도 잡·화면 표시를 얹은 처리 흐름도


같은 날 이 설계가 못 잡는 실패가 두 개 나왔습니다

배포한 날 곧바로 반례가 나왔습니다. 설계가 잡는 실패의 범위가 어디까지인지 그 자리에서 드러났습니다.

첫째, 검색 사본의 id 가 내용 해시였습니다. 내용이 같은 글 두 개가 사본 하나를 나눠 가진 상태였고, 그중 하나를 지우자 남은 글이 검색에서 빠졌습니다. 그 글의 상태 칸은 여전히 '색인됨'이었습니다. 색인 시점의 실패가 아니라 색인이 성공한 뒤에 벌어진 일이라 '검색 반영 중' 표시도 재색인 잡도 이것을 모릅니다. 상태 칸은 '우리가 보낸 것이 받아들여졌나'까지만 말하고 '지금도 거기 있나'는 말하지 않습니다. 이 자리는 주기적 대조 같은 별도 정합 검사가 필요합니다.

색인 대상의 id 를 무엇으로 잡느냐가 이렇게 나중에 돌아옵니다.

색인 구조를 어디에 두느냐로 검색 경로가 달라진 이야기도 같이 보면 맥락이 잡힙니다.

둘째, 상태 어휘가 실제 결과를 못 덮었습니다. 첨부 색인에서 엔진이 새로 '보류(크기 상한 초과)'라는 결과를 돌려주기 시작했는데, 앱은 성공과 실패 둘만 알고 있었습니다. 그래서 그 첨부는 '색인 중'에 영원히 머물렀습니다. 상태 칸의 값 집합은 부수 시스템이 낼 수 있는 결과를 전부 덮어야 합니다. 값이 둘뿐인 설계는 상대가 셋째 값을 내는 순간 조용해집니다.

상태 칸이 잡는 실패와 못 잡는 실패의 경계를 나눈 그림


배치로 가는 조건은 코드 머리말에 적어 뒀습니다

네 번째 선택지는 배치 색인이었습니다. 저장할 때는 큐에만 넣고 모아서 한 번에 미는 방식입니다. 이번에는 가지 않기로 했습니다. 처음부터 큐와 배치를 깔면 그 인프라 자체가 새로운 실패 지점이 됩니다. 실시간을 기본으로 두고 실패한 것만 배치로 다시 미는 지금 구조가 부품이 더 적습니다.

대신 언제 이 결정을 뒤집어야 하는지를 신호 셋으로 정리해 색인 층 코드 머리말에 적어 뒀습니다.

하루 색인 요청이 수백 건 규모로 올라올 때

엔진이 429(레이트리밋)를 돌려주는 일이 저장 흐름을 막기 시작할 때

같은 글을 반복 수정해 색인 요청이 몰려들 때

지금은 셋 다 해당하지 않습니다. 사용자와 글 수가 늘면 첫 번째 신호가 먼저 올 것으로 보고 있습니다. 나중에 이 결정을 뒤집을 사람이 근거를 찾을 자리를 코드 안에 남겨 둔 셈입니다.

확인은 새 프로세스의 pid 와 마이그레이션 head 까지 봤습니다. 응답이 오느냐가 아니라 새 프로세스가 응답하느냐를 봅니다. 테스트는 업무 화면 929 개와 문서 엔진 836 개가 통과했습니다.


부수 작업을 붙일 때 점검한 것

본 저장이 성공하는 데 부수 시스템이 필요조건인가 — 그렇다면 부수 시스템의 가동률이 본 시스템의 가동률이 됩니다

부수 작업이 실패했다는 사실이 데이터로 남는가 — 로그만 남으면 나중에 어느 행인지 찾지 못합니다

그 상태가 사람 눈에 보이는가 — 복구 잡이 멈췄을 때 알아챌 방법이 화면에 있어야 합니다

상태 값 집합이 부수 시스템이 낼 수 있는 결과를 전부 덮는가 — 성공과 실패 둘로는 부족한 날이 옵니다

성공 이후에 깨지는 경우를 누가 보는가 — 상태 칸은 그 시점까지만 말합니다


마무리

본 저장에 딸린 부수 작업은 색인만이 아닙니다. 알림 발송, 외부 시스템 동기화, 캐시 갱신이 전부 같은 자리에 있습니다. 이번 정리에서 남은 판단 기준은 하나입니다. 저장은 독립으로 무조건 성공시키고, 부수 작업의 실패는 상태 칸에 데이터로 남기고, 그 상태를 화면에 보이게 합니다.

그리고 이 설계에도 경계가 있습니다. 상태 칸은 우리가 보낸 것이 받아들여진 시점까지만 증언합니다. 그 뒤에 부수 시스템 안에서 벌어지는 일은 주기적 대조라는 다른 장치가 봐야 합니다. 재시도 잡도 원인을 고치지는 못합니다. 엔진이 계속 멈춰 있으면 '대기'가 쌓일 뿐이고, 그 쌓임을 알리는 별도 알림은 아직 붙이지 않았습니다.

#색인#비동기처리#재시도#실패처리#트랜잭션경계#관측성