2025 게으른 개발자 컨퍼런스 제 2회 후기


게으르다면서 세상 부지런한 개발자 컨퍼런스에 다녀왔다 🏃♀️
2025 게으른 개발자 컨퍼런스는 테크살롱에서 진행되었다. 몰랐는데 우아한형제들이 만든 곳이라고.
후원사가 한빛미디어와 넥스트스텝이었다. 백엔드 컨퍼런스가 많지 않아서인지(?) 티켓팅 경쟁이 꽤나 치열했는데, 운 좋게 아는 분에게 양도 받아서 다녀왔다.
컨퍼런스 순서는 아래와 같다.
24/7 끊기지 않는 잔고 서비스 개발기 (황보성우님)
Go 서버 메모리 누수 버그 개선 (김환석님)
실시간 광고 사용자 ID 매핑 시스템 구축 (김소현님)
24/7 끊기지 않는 잔고 서비스 개발기
개인적으로 가장 인상 깊었던 세션이다.
개요
데이터 정합성과 트랜잭션 무결성 고성능 처리가 필요한 상황에서의 잔고 서비스 아키텍처 구축을 다룬다.
과거 서비스 → 단일 인스턴스, 레코드 락으로 인한 병렬 처리 불가 → 확장 불가능한 구조
목표 → 전체 성능 향상 (TPS 개선), Kafka를 이용한 비동기 메세지 처리 도입, 병렬 처리 추가, 레코드 락 제거 및 배치 처리 적용
기술 스택
Java21 / Spring Boot(Kafka, JPA) / Protobuf
→ 여기서 Avro를 안쓰고 Protobuf를 쓴 것이 특이했다. 질문 시간에 질문하려 했는데 다른 분이 먼저 질문을 해주셨음Protobuf → 데이터 사이즈 최적화, 일관된 메세지 포맷 유지, 스키마 기반 데이터 구조라는 특징을 가짐. Avro와 비슷하지만 Avro의 스키마 관리법이라던지 기타 등이 맞지 않아서 Protobuf를 채택했다고 한다.
Kafka → 카프카의 여러 특징들 (서비스간 결합도 낮춤, 이벤트 기반, 비동기 처리 등) 때문에 카프카를 채택했다고.
Kafka
사용자 ID를 기반으로 파티셔닝해서 컨슈머 그룹 내 각 컨슈머는 고유한 사용자만 처리하는 구조로 만들었다. 여기까지는 카프카에 관심이 있다면 대부분 아는 사실.
메세지 실패 전략 → Protobuf를 통해 입력 메세지 타입을 엄격이 검사하고 잘못된 데이터 입력을 방지함. 처리 실패시 제한적 재시도 후 즉시 중단, 잔고 반영 순서가 중요해 DLQ 사용 하지않음.
Kafka 컨슈머 최적화를 위해 수동 ACK 설정과 FETCH_MIN_BYTES_CONFIG, MAX_POLL_RECORDS_CONFIG 설정으로 배치 처리 성능 향상과 크기 조정을 했다.
→ 수동 ACK의 경우 권장하지 않는다고 어디서 봤던 것 같은데 (정확하지 않음) 수동 ACK로 컨트롤하고 있어서 신기했고 어떤 식으로 최적화해 나갔는지 궁금해졌다. 이 부분은 질문 못함
→ ACK를 분실하는 경우에 대해 질문 → 어짜피 재처리를 하면서 멱등성 테이블(뒤에 나옴)에서 처리되었기 때문에 방어가 가능하다. 사실 멱등성 키를 확인하는 구조를 얼마나 잘 짜느냐가 중요한 것.각 컨슈머들은 사용자 ID 별로 병렬 스레드를 돌려서 메모리에서 처리해 성능을 좀 더 높혔다. Parallel Consumer와 유사한 방식이라고 함.
→ 병렬 스레드를 돌리는데 어떻게 순서가 유지되나 의문이 들었는데 사용자 ID 별로 돌린다고 하니 이해가 되었다.요청 처리 만큼 응답 메세지도 중요해서 응답 메세지가 유실되면 결과 반영 실패 처리
Outbox Pattern을 이용해서 트랜잭션과 Kafka 전송을 완전히 분리해 안정성을 확보했다.
전체 요청을 무조건 잔고 서비스에서 받도록 해 레코드 락을 제거할 수 있었고, 비관적 락을 적용하지 않고 바로 업데이트 하도록 변경
단 건 요청 당 하나의 트랜잭션을 사용했던 것에서, Kafka로 들어온 메세지를 배치로 묶어 한번에 처리해 요청 당 부하가 감소함.
JDBC 배치 설정을 통해 대량의 INSERT 성능 최적화를 함
무결성 보장
각 메세지의 Type, Key를 가지고 미리 저장되어 있는 테이블에 비교. 멱등성 키 테이블에 없는 데이터들만 INSERT 후 사용함
→ 멱등성 키 테이블도 결국 디비 접근인데, 캐시 서버로 이루어진건지 질문했다. 단건이 아니고 배치 처리라서 성능이 괜찮았고 캐시 서버를 두면 결국 네트워크를 또 타야하는데 안정성을 위해 어느정도 희생한 부분이 있다고 답변을 해주심.잔고 변동 처리 전에 필요한 데이터를 미리 조회해 트랜잭션 중 추가 조회 없이 메모리에서만 처리 가능하게 했다. DB 접근 적게해 성능 최적화
엔티티 업데이트. JPA의 Auto Increment로 인해 매번 INSERT 후 UPDATE가 발생해서 AI를 모두 삭제하고 복합기를 유일 키로 설정해서 불필요한 UPDATE문을 줄여 성능 향상을 했다.
배포 전략
- 기침 이슈로 잠깐 나가있느라 자세한건 못 듣고 k8s에서 Recreate 방식을 사용한다고 함. 다른 방식은 서버가 순차적으로 교체될 때 이전 서버와 현재 서버가 동시에 떠있으면 데이터 정합성이 맞지 않을 수 있어서 순단 현상이 발생하더라도 Recreate 방식을 사용한다고 했다.
카프카 개념을 전반적으로 생각하게 하면서, MSA 설계도 같이 고민하게되는 세션이었다. 제일 흥미로웠고, 사이드 프로젝트에도 관련 개념을 적용해보려고 생각 중이다.
여기부터는 잘 모르는 내용이라 세션 내용을 짧게 적었다.
Go 서버 메모리 누수 버그 개선
개요
Go에는 동시성 개발에 사용되는 Gorouine이 있는데 고루틴 내부에는 Channel이라는 MQ와 비슷한 개념이 존재한다.
unbuffered channel / buffered channel 로 나뉘는데 un의 경우 consumer가 받을 때 blocking 된다는 특징이 있고 buffered는 비동기 넌블로킹으로 메세지 큐와 흡사하다.
메모리 누수 문제와 분석
메모리 80% 이상인 경우 슬랙 알림이 오도록 설정해놓았는데 특정 인스턴스가 특정 시간에 단기간에 급격히 증가하는 추세를 보였다.
분석을 위해 3가지 방법을 시도 했다.
서버 동작 구조 시각화
- 내부 구조도와 요청 흐름을 도식화해 전체 동작 맥락을 파악했다.
로그 분석 및 proof(go profiler) 활용
특정 로그만 반복적인 패턴이 나타남.
고루틴의 수와 Heap 메모리 사용량을 분석했다
테스트 코드로 재현 시도
- 버그 상황을 테스트 코드로 재현해 문제 원인을 검증했다.
→ 결론: 무한 블로킹이 원인이었음
- 블락 되는 케이스가 두가지가 있는데 Channel이 꽉차서 Producer가 blocking 되거나 (MQ와 비슷), Consumer가 사라진 경우 Producer가 사라진 걸 인지하지 못하고 계속 blocking 상태가 된다고 한다.
해결방안
MQ 처럼 Pub/Sub 구조로 갈 수 없을지 고민하다가 Watermil 라이브러리 사용
P/S 구조를 사용하면서 Go의 Channel을 지원해 Kafka와 같은 별도의 미들웨어를 두지 않고도 해결함.
질문
Q. Go가 In-memory 방식인데 데이터가 유실되어도 괜찮은지?
A. 스푼라디오(음성) 서비스라 데이터 유실이 되어도 문제 없는 서비스라 괜찮았다.Q. Channel을 분리하는 기준이 궁금하다
A. 아직 필요 없어서 서비스를 분리하지는 않았다.들으면서 내가 궁금했던 것은, Producer가 Consumer가 사라진 것을 인지하지 못할 때 blocking 된다고 했는데, Consumer가 사라지지 않도록 방지하는 방법에 대해서만 설명하고 Consumer가 실제로 사라지게 되면 (종료되면) 다시 똑같은 일이 발생할 수 있는건지가 궁금했는데, 질문은 하지 않고 넘어갔다. 쓰다보니까 더 궁금하다.
Go에 대해서는 잘 모르지만 MQ 개념이 나와서 이해하는데 어려움은 크게 없었던 세션이었다.
실시간 광고 사용자 ID 매핑 시스템 구축
이 세션은 나한테 좀 어려웠다.
개요
- 유저 당 발행되는 광고 아이디, 브라우저 단위로 발생하는 아이디 등등 하나의 유저에게 여러개의 아이디가 발행될 수 있는데, 문제는 이 아이디들이 한 사용자라는 것을 모르는 것이다. 그래서 광고 사용자 ID 매핑이 필요하다.
배치에서 스트리밍으로
기존 배치의 경우 아래와 같은 단점이 있었다.
일괄 처리로 사용자 ID 수집
조건문 기반의 ID 매핑 로직
따라서 매핑 결과의 지연 발생
확장성 또한 부족
그래서 Spark와 Kafka를 이용한 구조로 확장/변경했다.
ID 매핑 알고리즘
ID 타입의 우선 순위를 정의했다
알고리즘의 트리 구조를 이용함.
잘 모르겠어서 이해가 안가기 시작한다.
시스템 설계
아이디만 필요해서 추출하는 스파크 모듈이 존재한다.
저장시 인메모리에도 저장하고 카프카에도 보낸다.
micro batch로 중복을 제거한다.
백엔드의 도메인이 워낙 넓다보니 백엔드 컨퍼런스에 개발, 데이터, Devops 등등이 포함될 수 밖에 없는 상황이다. 그래서 데이터 도메인 쪽 얘기를 듣다보니 이해를 많이 하지 못했다. 다만 한가지 아쉬운 점은 발표자 분의 설명이 좀 더 근본적인 핵심을 얘기하면 좋았을 것 같다는 생각이 들었다. 예를 들면 카프카를 썼어요 / 카프카를 이러이러한 이유로 썼어요는 차이가 있는데 전자에 가까운 설명을 해서 관련 도메인 지식이 없는 상태에서 들으니까 이해가 더 어려웠던 것 같다.
백엔드 컨퍼런스가 귀한데(?) 좋은 기회로 다녀오게 되서 좋았고, 재미도 있었다. 운영진 분들의 사비로 운영되는 컨퍼런스라는데 후원사가 좀 더 붙고 흥했으면 좋겠다. 후기 끝!
Subscribe to my newsletter
Read articles from 𝙇𝙮𝙜𝙞𝙖 directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

𝙇𝙮𝙜𝙞𝙖
𝙇𝙮𝙜𝙞𝙖
Back-end Engineer, Clarity in code. Calm in process.