Notice
Recent Posts
Recent Comments
반응형
관리 메뉴

HM소프트

대시보드 집계 버그 잡기 — 소프트 삭제와 '삭제된 과제' (2) 본문

외주 개발일지

대시보드 집계 버그 잡기 — 소프트 삭제와 '삭제된 과제' (2)

HM소프트 2026. 6. 12. 01:21
반응형

소프트 삭제가 대시보드 집계에 일으킨 연쇄 버그 다섯 개를 추적한 디버깅 기록. 채점 큐가 항상 비어 보인 상태 필터 오류, 카드와 큐 모집단 불일치, V1·V2 화면 버그 복제, 라이프사이클 상태 집합과 포함·제외 목록 선택, 읽는 쪽이 비활성을 명시해야 하는 책임까지 Drizzle·PostgreSQL로 잡았다.

소프트 삭제가 만든 대시보드 집계 버그를 추적해 잡은 과정을 정리한다.

 

2편: 집계 버그

1편에서 "삭제 대신 상태(is_active)로 관리"한다고 했다. 이게 좋은 패턴인데, 집계할 때 그 상태를 빠뜨리면 통계가 줄줄이 어긋난다. 이번 편은 대시보드에서 연쇄적으로 터진 집계 버그들과, 거기서 얻은 교훈이다.

발단: 채점 큐가 항상 비어 보인다

대시보드의 채점 큐가 늘 비어 보이는 버그가 시작이었다. 채점할 제출물이 분명 있는데 큐에 안 나타났다. 원인은 과제 상태 필터가 너무 좁았던 것. 큐가 특정 상태의 과제만 보고 있어서, 실제 채점 대상이 걸러져 나갔다. 상태 필터를 실제 채점 대상이 포함되도록 확장해 해결했다.

이 하나를 잡고 나니, 같은 뿌리의 버그가 줄줄이 나왔다.

연쇄: 삭제/비활성 과제가 카운트에 섞인다

반대 방향의 문제였다. 여러 카운터가 삭제되거나 비활성화된 과제의 제출물까지 세고 있었다.

  • 채점 카운터가 삭제/비활성 과제의 제출물을 셈
  • V2 카운터·학생 채점 큐도 삭제/비활성 과제를 포함
  • "최근 과제(Recent Assignments)"에 삭제된 과제가 노출
  • 완료율이 published 과제만 집계해야 하는데 범위가 어긋남

1편에서 과제·루브릭을 "삭제 대신 비활성화"로 관리한다고 했다. 데이터가 실제로 남아 있으니, 집계 쿼리가 명시적으로 제외하지 않으면 그대로 세어진다. soft-delete의 대표적인 함정이다.

// 문제: 모든 제출을 셈 (삭제/비활성 과제 포함)
const count = await countSubmissions();

// 해결: 활성·공개 과제로 한정
const count = await countSubmissions({
  assignmentStatus: ["published"],   // 비활성·삭제 제외
  excludeDeleted: true,
});

진짜 교훈: soft-delete는 "읽는 쪽"이 책임진다

이 버그들의 공통 뿌리는 하나다. 데이터를 진짜 지우지 않으면, 그 데이터를 빼는 책임은 전부 "읽는 쪽(쿼리)"으로 넘어간다.

hard-delete(진짜 삭제)라면 지워진 데이터는 어디서도 안 보인다. 하지만 soft-delete(상태로 비활성)는 데이터가 남아 있어서, 모든 조회·집계가 일일이 "비활성은 빼고"를 명시해야 한다. 한 군데라도 빠뜨리면 그 화면만 숫자가 틀린다.

대시보드의 카운터가 여러 개였고, 각각이 따로 쿼리를 짰기에, 누락이 분산돼 나타난 것이다.

어떻게 재발을 막았나

땜질로 카운터를 하나씩 고치는 데서 그치지 않고, "활성 과제 집합"을 일관된 기준으로 쓰도록 정리했다. 집계의 기준이 되는 필터(공개 상태 + 비활성/삭제 제외)를 공통화해, 새 카운터가 추가돼도 같은 기준을 따르게 했다.

정리하며

  • soft-delete의 비용은 "읽는 쪽"에 있다 — 모든 조회가 비활성을 명시적으로 제외해야 한다.
  • 한 카운터의 버그는 대개 같은 뿌리의 형제 버그들을 동반한다 — 하나 찾으면 주변을 훑어라.
  • 집계 기준(활성 과제 집합)을 공통화해 누락 재발을 막았다.

"삭제 대신 상태로"는 좋은 패턴이지만, 그 대가로 읽는 쪽의 규율이 필요하다. 다음 마지막 3편에서는 Docker 배포와 운영(이미지 정리, 다국어)을 다룬다.

반응형