직접 겪은 "푸시 0건" 버그를 Go 동시성 시나리오로 재현하고, race detector로 검증한 뒤 수정한 학습 프로젝트.
Java/Spring으로 만든 복약 관리 서비스에서, 다른 유저가 같은 푸시 토큰을 재등록하면 소유권이 이전되지 않아 새 유저에게 알림이 0건이 되는 버그를 겪었다. 같은 문제를 Go로 옮겨 재현하고, 테스트로 고정한 뒤 고쳤다.
커밋 히스토리가 순서 그대로 서사다.
- naive store: goroutine 100개가 동시 등록하면
go test -race에서 DATA RACE 검출, 재등록 시 소유자가 갱신되지 않는 버그를 테스트로 고정 sync.Mutex보호 + 명시적 소유권 이전(이전 소유자를 반환값으로 보고)으로 수정,-race통과- channel 큐 + 워커 풀 + 가짜 Sender로 발송 경로 구성, 50건 전량 발송 검증
go test -race ./...
- in-memory 저장소. 주제가 저장소가 아니라 동시성이라서
- 실제 FCM 연동 없음.
Sender인터페이스와 가짜 구현으로 대체 - 백프레셔(큐 가득 시 503), graceful shutdown은 다음 단계
- 상태 보호는 Mutex, 작업 전달은 channel. 토큰 맵을 채널 액터(소유자 goroutine + 요청 채널)로 만드는 대안을 검토했지만, 읽기마다 답장 채널을 동봉하는 왕복 비용과 코드량 대비 얻는 것이 없어 기각
- 인터페이스는 Sender 하나만. 테스트 대역이 실제로 필요한 자리에만 두고, 구현이 하나뿐인 저장소 인터페이스는 만들지 않았다
- 워커 종료는 close + WaitGroup 드레인. 테스트에서 time.Sleep 동기화는 쓰지 않는다
Claude Code와 함께 개발했다. 설계 결정과 검증 시나리오는 직접 정했고, 조립 성격의 코드는 AI 초안을 검토 후 채택했다. 초안을 뒤집은 예: AI는 조회 메서드를 공개(UserOf)로 제안했지만, 단일 패키지라 노출할 이유가 없어 비공개(userOf)로 유지했다.
- 아는 것: goroutine, channel과 Mutex의 용도 구분, WaitGroup, race detector, 테이블 드리븐 테스트
- 아직 모르는 것: GC와 스케줄러 내부, pprof 프로파일링, 제네릭 실전, 대규모 Go 코드베이스의 관례