"[260914] 외부 시세 데이터가 통째로 비어버린 날 - 유니버스 오염 사고"
시가총액 데이터가 전부 비어버리자 종목 구성 순위가 알파벳순으로 둔갑했고, 이를 막는 3중 방어층을 새로 넣었습니다.
시가총액이 전부 비어서 생긴 일
오늘 아침 매매 대상 종목 구성(유니버스)을 갱신하는 과정에서, 외부 시세 데이터 제공처가 시가총액 값을 전부 빈 값으로 돌려주는 장애를 일으켰습니다.
문제는 이 빈 값을 시스템이 그냥 통과시켰다는 데 있었습니다. 시가총액순 정렬이 무의미해지자, 원본 목록의 순서(알파벳순)가 그대로 "시가총액 상위 N종목"인 것처럼 오인됐습니다.
그 결과 코스닥 쪽은 거의 전체, 코스피 쪽도 절반 가까운 종목이 하루 만에 엉뚱하게 뒤바뀌었습니다.
발견 경위가 좀 민망합니다. 낯선 종목 하나를 실계좌가 매수한 걸 보고 "이 종목은 왜 산 거지?"라고 물어본 게 시작이었습니다.
그 질문을 따라가면서 원인을 찾아보니, 신호 없이 무작위로 끼어든 매매였다는 게 드러났습니다. AI에게 두 차례 자문을 구해 원인과 영향 범위를 정밀하게 좁혔습니다.
다행히 실제 피해는 제한적이었습니다. 코스피 쪽은 아침에 한 번 확정해서 쓰는 캐시 덕분에 무사했고, 오염은 코스닥 슬라이스에서만 발생했습니다.
오염된 신호로 나간 매매는 몇 건 안 됐고, 손실도 방향성 손실이 아니라 신호 없이 사고팔며 낸 수수료 수준이었습니다. 다만 엉뚱하게 사들인 종목 하나가 남아 있어서, 정상화 뒤 곧바로 청산했습니다.
3중 방어층을 새로 넣었다
즉시 조치는 관련 계좌 전체를 거래 중단시키는 것부터 시작했습니다. 그 사이 오염된 상태를 하루 전 정상 상태로 되돌리는 복구 작업을 진행했습니다.
복구 과정에서 한 가지 함정에 걸렸습니다. 아직 재시작 전이던 구버전 로직이 복구 도중 한 번 더 오염을 일으킨 겁니다. 재시작 순서를 앞당겨서 이 문제를 해결했습니다.
근본 대책으로는 시가총액 데이터의 품질을 검증하는 계층을 여러 겹으로 쌓았습니다. 데이터 소스 단계에서 결측 비율이 지나치게 높으면 아예 거부하도록 했고, 하루 사이 종목 구성이 비정상적으로 크게 바뀌면 경보와 함께 직전 상태를 유지하도록 하는 장치도 추가했습니다.
마지막으로 최종 확정 직전에 한 번 더 재검증하는 층을 뒀습니다. 세 계층 모두 데이터 소스, 갱신 로직, 최종 확정이라는 서로 다른 지점에서 같은 종류의 오염을 잡아내도록 설계했습니다.
같은 날 아침엔 GPU 리소스 충돌도 있었다
같은 날 아침, 별도로 진행 중이던 수동 성능 벤치마크가 그래픽카드 메모리를 많이 점유하면서, 정기적으로 도는 리포트 생성 작업이 세 차례 연속 실패하는 일도 있었습니다.
실패 알림 자체는 매번 정상적으로 발송됐지만, 마침 다른 사고 대응에 집중하느라 놓쳤습니다. 벤치마크를 종료해 즉시 해결했지만, 수동으로 실행하는 작업과 정기 작업 사이의 자원 충돌을 사전에 점검하는 절차는 아직 마련하지 못했습니다.
앞으로
오늘 사고로 데이터 소스 자체의 신뢰성 문제도 드러났습니다. 제공처에 이 결측 문제를 제보할지 검토 중입니다.
장중에 실시간으로 다시 조회하는 방식이 애초에 필요했는지도 다시 보고 있습니다. 이미 아침에 한 번 확정해서 쓰는 구조라면, 장중 재조회는 얻는 것 없이 오늘 같은 위험만 더하는 셈이기 때문입니다.