(주)사운드마인드
오디야 위치 수집 서버
30초 간격으로 폴링하는 단말들이 한꺼번에 위치를 밀어 넣습니다. 한 건도 잃지 않고, 한 건 때문에 멈추지도 않는 저장 경로를 만드는 일이었습니다.
- 역할
- 위치 수집 파이프라인 백엔드
- 기간
- 2025.07 ~ 현재
- 사용 기술
- Spring BootJava 17RedisMariaDBFlywayShedLock
개선 결과
0건
원자 pop 전환 이후 부하 테스트에서의 데이터 유실
60초 / 5,000건
드레인 주기와 틱당 저장 상한
01문제
단말들이 올리는 위치를 요청마다 바로 DB에 쓰면 쓰기 부하가 몰릴 때 서버가 함께 무너집니다. 그래서 Redis 큐에 쌓았다가 나눠 저장하는 구조인데, 여기에 두 가지 함정이 있었습니다. 큐에서 꺼내는 방식이 원자적이지 않으면 두 프로세스가 같은 데이터를 나눠 갖다 잃어버리고, 깨진 데이터 한 건이 배치에 섞이면 묶음 전체가 롤백되어 모든 사용자의 위치가 쌓이지 않게 됩니다.
02결정
꺼내는 경로를 원자적 일괄 pop 하나로 통일했습니다. 범위 조회 후 잘라내는 방식과 스크립트 기반 방식을 모두 돌려봤지만 부하 테스트에서 유실이 실측되어 버렸습니다. 저장에 실패하면 꺼낸 순서를 뒤집어 큐 앞에 되돌리고, 재저장 시 중복은 걸러냅니다. 파싱과 검증은 저장 전에 미리 해서 깨진 건만 따로 격리하고 자동으로 재처리합니다.
- 원자적 일괄 pop 전용, 60초마다 최대 5,000건씩 분할 저장
- 실패 시 역순 복원과 중복 제거로 최소 한 번 저장을 보장
- 손상 건은 격리 테이블로 빼고 주기적으로 재주입, 재처리 불가 건은 제외
- 서버 명령은 업로드 응답에 실어 내리는 단방향 채널로 설계
- 모든 스케줄러에 단일 실행 잠금을 걸어 무중단 배포 겹침 구간의 이중 실행 차단
03레거시 톰캣에서 무중단 배포로
물려받은 배포 방식은 외장 톰캣에 war를 올리는 구조였습니다. 배포는 서버를 내리고, 파일을 바꾸고, 다시 올리는 순서입니다. 그 사이 수집이 통째로 멈추고, 그때 자녀 단말이 올린 위치는 재시도 큐에 쌓이거나 버려집니다. 위치 서비스에서 배포 시간은 곧 부모가 아이를 볼 수 없는 시간입니다. 게다가 빌드를 서버에서 직접 돌리다 보니 빌드가 실패하면 서버가 반쯤 죽은 채로 남았습니다.
배포 중에도 수집은 멈추지 않는다
점검에 실패하면 트래픽은 blue에 그대로 남습니다
- 산출물을 실행 가능한 단일 jar로 통일하되, 기존 war 경로도 한동안 함께 만들어 뒀습니다. 배포 방식과 애플리케이션 구조를 동시에 갈아엎지 않기 위해서입니다
- 서버에서는 절대 빌드하지 않는다를 원칙으로 세웠습니다. 로컬에서 이미지를 만들어 압축한 뒤 SSH로 스트림 전송해 서버에서는 받아 올리기만 합니다. 빌드 실패가 운영 서버에 닿을 일이 없어집니다
- 슬롯을 둘로 나눠, 비활성 슬롯에 새 버전을 먼저 띄웁니다. DB와 Redis 연결까지 확인하는 준비 상태 점검을 통과해야만 앞단 프록시가 트래픽을 넘기고, 그 뒤에 구 슬롯을 내립니다. 사용자 입장에서 끊기는 순간이 없습니다
- 운영 환경에는 점검을 한 겹 더 뒀습니다. 로그인 화면이 정상 응답하는지, 본인인증 리다이렉트가 https로 나가는지까지 확인합니다. 예전에 리다이렉트가 http로 나가 앱 웹뷰가 차단당한 사고가 있었기 때문입니다
- 이미지에 롤백 태그를 붙여, 문제가 보이면 이전 버전으로 즉시 되돌립니다. 되돌리는 데 다시 빌드가 필요하지 않습니다
- 두 슬롯이 잠시 함께 도는 구간이 생기므로, 모든 주기 작업에 단일 실행 잠금을 걸어 배치가 이중으로 돌지 않도록 했습니다. 무중단 배포가 만든 새 위험을 같이 막은 것입니다
04결과
부하 테스트에서 실측되던 유실이 사라졌고, 깨진 데이터가 들어와도 해당 건만 격리될 뿐 전체 적재는 멈추지 않습니다. DB 장애가 나도 커넥션 대기를 짧게 잘라 서버 전체로 번지지 않으며, 배포는 세 환경 모두 트래픽 전환 방식이라 수집이 끊기지 않습니다.