Skip to content

정리: 탈퇴 계정 드레인(SCHEMA §7-7)과 24시간 마감 지표 - #57

Merged
mack-erel merged 2 commits into
mainfrom
account-drain
Oct 19, 2026
Merged

mack-erel merged 2 commits into
mainfrom
account-drain

Conversation

@mack-erel

Copy link
Copy Markdown
Contributor

요약

account-delete는 status=2로 바꾸기만 했고, SCHEMA §7-7이 약속한 드레인이 없었습니다. 그래서 탈퇴한 계정의 메일·본문 색인·자격증명·sieve가 영구히 남았습니다. 회원 공통 기준(즉시 비노출 + 24시간 안 드레인 + 넘기면 알림)에 맞춥니다.

  • 리퍼가 tick마다 삭제 중인 계정을 오래된 것부터 지우고, 마지막에 accounts 행까지 없앱니다(store/account-drain.ts).
  • 마이그레이션 030: accounts.deleting_since. 24시간 마감을 여기서부터 잽니다.
  • 지표: ionosphere_account_drain_pending, ionosphere_account_drain_overdue(0보다 크면 경보, max로 걸 것).
  • 일부러 남기는 것: identity_claims(SSO sub 재사용 방지), addresses(알리아스 소유), 블롭 파일(블롭 GC가 회수).

리뷰에서 고친 것

  • 쪽마다 다른 테넌트의 search_index·blob_refs까지 훑던 전체 스캔 → 계정 조건 추가
  • 정지·재활성이 삭제(status=2)를 덮어쓰던 경합 → 조건부 UPDATE, 0행이면 conflict
  • 열린 JMAP 세션의 늦은 쓰기가 계정 행 삭제 뒤 고아로 남던 문제 → 삭제 뒤 2분 유예
  • 삭제 중 계정의 툼스톤 메일함이 다른 계정의 메일함 수거를 굶기던 문제 → 활성 계정만 선택

테스트

  • npm run verify 초록
  • 드레인 테스트를 SQLite·PostgreSQL·MySQL에서 실행

배포 시 주의

  • 운영 DB(10-19 읽기 전용 조회)에는 status=2 계정이 0건입니다. 배포 첫 tick에 한꺼번에 지워질 데이터가 없습니다.
  • 마이그레이션 030이 포함됩니다.
  • 남은 일: 디렉터리 연결 묘비(SSO처럼 같은 외부 키 재연결 금지)는 별도 PR로 합니다.

🤖 Generated with Claude Code

mack-erel and others added 2 commits October 19, 2026 17:36
왜: SCHEMA §7-7은 "계정 삭제 = status=2 + 전 메일함 + credentials/sieve 정리"를 약속했지만
그 드레인이 어디에도 없었다. account-delete는 status=2로 바꾸기만 했고 리퍼는 메일함 툼스톤만
수거해서, 탈퇴한 계정의 메일·본문 텍스트·검색 색인·앱 비밀번호·sieve가 영구히 남았다.
회원 공통 기준(2026-10-19 P8)은 "즉시 비노출 + 24시간 안 드레인 + 넘기면 알림"이다.
즉시 비노출은 이미 성립했고(로그인·열린 연결·웹 세션·SSO·수신이 status=1만 받는다), 이번에
지우는 쪽과 마감 관측을 더한다.

- store/account-drain.ts: 메시지를 98통씩 한 배치로, 비면 계정 단위 정리 한 배치로 지우고
  마지막에 accounts 행까지 없앤다. 모든 문장이 멱등한 DELETE라 중간에 죽어도 다음 tick이 이어 간다.
  change_log는 남기지 않는다(알릴 클라이언트가 없다). identity_claims·addresses·블롭 파일은 남긴다.
- 마이그레이션 030: accounts.deleting_since. 24시간 마감을 여기서부터 잰다. 이미 status=2인
  계정은 created_at으로 채워 곧바로 마감 초과로 보이게 한다(늦게 잡혀 경보가 안 울리는 것보다 낫다).
- 지표 ionosphere_account_drain_pending·_overdue. overdue>0이면 경보(서버마다 같은 값이라 max로).

리뷰에서 막은 사고:
- search_index·blob_refs 삭제에 계정 조건이 없어 쪽마다 모든 테넌트를 훑었다(message_id 인덱스 없음).
  MySQL에서는 훑은 행 잠금으로 다른 계정의 수신이 막힌다 → account_id를 앞에 건다.
- account-suspend·activate가 확인 뒤 무조건 UPDATE라, 사이에 삭제가 끼면 반쯤 지운 계정이
  "활성"으로 되살아났다 → status!=2 조건부 UPDATE, 0행이면 conflict.
- 열린 JMAP 세션(인증 캐시 30초)의 vacation·push·sieve 쓰기가 계정 행 삭제 뒤 들어와 고아가 될 수
  있었다 → 삭제 뒤 2분 유예 후 드레인.
- 삭제 중 계정의 툼스톤 메일함이 listReapableMailboxes의 LIMIT을 차지해 다른 계정 수거를 굶겼다
  → 활성 계정만 고른다.

검증: npm run verify 초록. 드레인 테스트를 SQLite·PostgreSQL·MySQL에서 실행.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
왜: 마지막 글자를 늘 "a"로 바꿔 틀린 비밀번호를 만들었는데, 생성된 비밀번호의 끝이 이미
"a"면 같은 값이 되어 로그인이 성공했다. #57 CI에서 이 우연으로 verify가 실패했다.
원래 글자와 다른 글자로 바꾼다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mack-erel
mack-erel merged commit 6b2d695 into main Oct 19, 2026
9 checks passed
@mack-erel
mack-erel deleted the account-drain branch October 19, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant