title: “AI 서비스 운영 중 배운 것: 데이터 흐름을 바꿀 때 놓치기 쉬운 것” date: 2026-05-06 tags: [AI, DevOps, 운영, 교훈] —

AI 서비스 운영 중 배운 것: 데이터 흐름을 바꿀 때 놓치기 쉬운 것

광고를 클릭해주시면 블로그운영에 큰 힘이 됩니다.

상황

학생들이 AI와 대화하는 서비스를 운영하고 있습니다. 매일 자정마다 크론 잡이 돌면서 전날 AI 사용량(활성 사용자 수, 토큰 수, 비용)을 집계해 별도 통계 테이블에 저장합니다.

어느 날 채팅 기록 저장 방식을 개선했습니다. 기존에는 AI 요청 하나하나를 요청 테이블에 기록했는데, 대화 맥락을 더 잘 관리하기 위해 대화 테이블 + 메시지 테이블 구조로 전환한 것입니다. 기능적으로는 더 나은 구조였고, 전환 후 채팅도 잘 동작했습니다.

문제는 다음 날 발견됐습니다.

[aggregate-usage] date=2026-05-05 active=0 total=1050 costPerUser=$0.00000000

활성 사용자가 0명, 비용도 $0. 실제로 수십 명의 학생이 AI를 사용했는데도 말입니다.


원인

집계 크론은 요청 테이블을 기준으로 동작하고 있었습니다.

[기존 흐름]
학생 → AI 요청 → 요청 테이블 (uid, 토큰, 비용 저장)
                      ↑
               집계 크론이 여기를 읽음

채팅 저장 방식을 바꾼 후:

[변경 후 흐름]
학생 → AI 요청 → 대화 테이블 / 메시지 테이블 (uid만, 비용 없음)
                      
요청 테이블 ← 더 이상 신규 데이터 없음
      ↑
집계 크론은 여기만 계속 읽고 있음 → active=0

![01-data-flow-problem.png](/assets/img/e67900ad4202443084a065f129ab3b75/01-data-flow-problem.png)

채팅 저장 방식을 바꾸는 목표에만 집중하다 보니, 그 테이블을 읽고 있던 다른 코드를 미리 파악하지 못했습니다.


왜 놓쳤을까

변경 전에 자문해야 할 질문은 이것이었습니다.

“이 테이블을 지금 누가 읽고 있는가?”

단순한 grep 한 줄이면 됐습니다.

grep -rn "요청_테이블명" apps/ --include="*.ts"

결과를 보면 크론, 어드민 API, 집계 로직 등이 모두 나왔을 것이고, 각각 영향 여부를 검토할 수 있었을 것입니다. 하지만 이 단계를 건너뛰었습니다.


수정

두 가지 수정이 필요했습니다.

1차 수정: 활성 사용자 집계를 새 테이블 기준으로 전환

-- 기존: 요청 테이블만
SELECT COUNT(DISTINCT uid) FROM 요청_테이블
WHERE date = yesterday AND status = 'done'

-- 수정: 메시지 테이블 기준으로
SELECT COUNT(DISTINCT c.uid)
FROM 메시지_테이블 m
JOIN 대화_테이블 c ON c.id = m.conversation_id
WHERE date = yesterday AND m.role = 'assistant'

→ 활성 사용자는 복원됐지만 비용은 여전히 $0.

2차 수정: 메시지 테이블에는 비용 컬럼이 없다는 걸 발견. 두 테이블을 UNION해서 집계

SELECT
  COUNT(DISTINCT uid) AS active_users,
  SUM(cost_usd)       AS total_cost
FROM (
  -- 구버전: 비용 정보 있음
  SELECT uid, cost_usd FROM 요청_테이블
  WHERE date = yesterday AND status = 'done'
  
  UNION ALL
  
  -- 신버전: 비용 정보 없음(0으로)
  SELECT c.uid, 0 AS cost_usd
  FROM 메시지_테이블 m
  JOIN 대화_테이블 c ON c.id = m.conversation_id
  WHERE date = yesterday AND m.role = 'assistant'
) combined

![02-union-fix.png](/assets/img/e67900ad4202443084a065f129ab3b75/02-union-fix.png)


배운 것

테이블/컬럼을 변경하기 전 반드시 확인할 것

확인 항목 방법
이 테이블을 읽는 코드가 어디 있는가 grep -rn "테이블명" apps/
크론/배치 잡이 이 테이블에 의존하는가 크론 목록 전체 확인
집계/통계 로직이 영향 받는가 집계 관련 코드 확인
어드민/모니터링 화면이 영향 받는가 어드민 API 확인

데이터 흐름 전환 시 체크리스트

□ 기존 테이블을 읽는 모든 코드 파악 완료
□ 각 코드가 신규 테이블로 함께 전환됐는가, 또는 영향 없는가 확인
□ 새 테이블에 기존 테이블과 동등한 컬럼(특히 집계에 필요한 것)이 있는가
□ 전환 후 첫 집계 실행 결과 검증

![03-checklist.png](/assets/img/e67900ad4202443084a065f129ab3b75/03-checklist.png)

근본 원인

기능 변경의 목표(“채팅 저장 구조 개선”)에만 집중하고, 변경의 다운스트림 영향을 사전에 체계적으로 점검하지 않았습니다. 변경 전 5분의 grep이 수정-배포-재수정 사이클 몇 시간을 아낄 수 있었습니다.


결론

AI 에이전트가 코드를 작성할 때도, 사람이 코드를 작성할 때도 마찬가지입니다. “이게 잘 동작하는가”와 “이걸 바꿀 때 다른 무엇이 깨지는가”는 별개의 질문입니다. 후자를 먼저 묻는 습관이 안정적인 운영의 기본입니다.

Share: Twitter Facebook
김민석's Picture

About 김민석

항상 공부가 부족한 개발자, 항상 시간이 부족한 딸바보, 항상 체력이 부족한 부족한남편, 그리고 고양이 집사

JungNangGu, Seoul, Korea Rep https://reddol18.pe.kr

Comments