유랑하는 나그네의 갱생 기록

だけど素敵な明日を願っている -HANABI, Mr.children-

반응형

Devlog/SpringBoot 12

같은 데이터, 다른 처리 - Spring Batch로 주간/월간 랭킹 집계하기

tl;dr - 지난 포스트에서 Redis ZSET 기반 일간 실시간 랭킹을 구축했습니다. 주간/월간도 같은 방식으로 하면 될 줄 알았는데, 비선형 스코어(log1p)가 발목을 잡았습니다. ZINCRBY로 7일치를 누적하면 취소 시 역산이 불가능합니다. 결국 Spring Batch로 원본 데이터에서 처음부터 다시 계산하는 배치 집계를 도입했고, Materialized View(MV) 테이블에 TOP 100을 적재하는 구조로 해결했습니다. 1. ZINCRBY로 주간 랭킹을 만들 수 없는 이유이전 포스트에서 작성한 일간 랭킹은 잘 동작하고 있었습니다. product_daily_metrics 테이블에 일간 집계 데이터가 쌓이고, 5분 주기 스케줄러가 이걸 읽어서 Redis ZSET에 반영합니다. 조회는 ZRE..

Devlog/SpringBoot 2026.04.17

double 하나에 4차원을 우겨넣기 - Redis ZSET 실시간 랭킹의 스코어 인코딩

tl;dr - Redis ZSET의 score는 double 하나다. 여기에 메인 점수(주문+조회+좋아요), 전일 계승, 그리고 동점 해소를 위한 3축 타이브레이커까지 - 4개 차원을 정수부와 소수부로 나눠 positional encoding했다. log1p로 주문 금액 100배 차이를 1.25배로 압축하고, z-score와 sigmoid로 이질적인 축을 0~999로 정규화했다. DB를 SSOT로 삼아 멱등성과 영속성을 확보하되, 읽기 경로는 Redis만 사용해 500 VU에서 p95 1. 1위가 50개면 랭킹이 아니다예전에 Kafka 이벤트 파이프라인을 구축했었습니다. 유저가 상품을 조회하거나 좋아요를 누르거나 주문하면 이벤트가 발행되고, commerce-streamer가 소비해서 product_m..

Devlog/SpringBoot 2026.04.10

대기열의 Thundering Herd를 2단계로 제어해보자 - 배치 크기 산정과 Jitter

tl;dr - Redis 대기열에서 배치 단위로 토큰을 발급하면 Thundering Herd가 발생한다. 1단계로 HikariCP 역산 기반의 배치 크기 산정(Macro 제어)으로 동시 요청 규모를 시스템 용량 안에 가두고, 2단계로 Jitter + 적응형 폴링(Micro 제어)으로 배치 내 요청을 시간축에 흩뿌렸다. k6로 배치 크기별 부하를 측정한 결과를 함께 정리했다. 1. 대기열은 문제를 해결했지만, 새로운 문제를 만들었다..커머스 시스템에 대기열을 도입한 이유는 단순합니다. 인기 상품 오픈 시 수천 명이 동시에 주문 API를 호출하면 DB 커넥션이 고갈되니까, 앞단에서 흐름을 제어하자는 것이었습니다. Redis Sorted Set으로 대기열을 구현했습니다. ZADD NX로 진입 순서를 보장하고..

Devlog/SpringBoot 2026.04.03

주문은 저장됐는데 Kafka에는 안 갔다? - Dual Write에서 멱등 Consumer까지

Introtl;dr - DB 커밋과 Kafka 발행은 원자적이지 않다. Outbox로 유실을 막으면 중복이 오고, 중복을 막으면 동시성(Lost Update)이 터진다. 세 가지 문제를 하나씩 해결한 과정을 정리해보자. 본론1. 이벤트를 발행하는 가장 단순한 방법커머스 시스템에서 주문이 생성되면 여러 곳에서 이 사실을 알아야 합니다. 상품별 판매량 집계, 조회수 카운팅, 좋아요 메트릭 갱신 - 이런 작업들을 주문 서비스가 직접 하기엔 책임이 너무 커지니까, 이벤트를 발행하고 별도 Consumer가 처리하는 구조를 택했습니다. 가장 먼저 떠오르는 흐름은...@Transactionalpublic Order createOrder(...) { Order order = orderRepository.save(..

Devlog/SpringBoot 2026.03.27

상품 목록 조회 병목 개선기 - 인덱스 최적화와 부분 캐싱

Introtl;dr 좋아요 수 기반 정렬 시 필연적으로 발생하는 DB 병목을 1단계 비정규화와 2단계 복합 인덱스로 해결하여 100만 건 기준 약 141배, 1,000만 건 스트레스 테스트에서 약 2,717배의 조회 성능 개선을 달성했습니다. 또한, Redis 부분 캐싱(Partial Caching)과 이벤트 성격별 무효화 전략을 도입해 조회 API의 안정성을 확보한 과정을 정리했습니다. 커머스 서비스에서 상품 목록 조회는 전체 트래픽의 대다수를 차지하는 Read-heavy 워크로드입니다. 현재 진행 중인 프로젝트에는 '특정 브랜드의 상품 목록을 좋아요 순으로 정렬'하여 반환하는 API가 존재합니다. 만약 데이터 정규화 원칙을 엄격하게 고수하여 정렬 기능을 구현한다면, products 테이블과 likes..

Devlog/SpringBoot 2026.03.13

재고는 왜 음수가 됐을까? - 비관적 락, 낙관적 락, 그리고 트랜잭션이 지켜주지 못하는 것들

Introtl;dr 동시성 문제는 @Transactional 만으로 해결되지 않습니다. 커머스 주문 시스템에서 재고, 쿠폰, 포인트의 동시 접근을 어떻게 제어했는지, 세 가지 동시성 전략의 선택 기준과 판단 과정을 정리해봅시다. 트랜잭션이면 과연 안전할까?주문 API를 구현하고 통합 테스트를 돌렸을 때, 단일 스레드 환경에서는 예측한 대로 완벽하게 동작했습니다. 재고 10개짜리 상품에 10번 주문하면 재고가 0이 되고, 11번째는 정상적으로 예외가 발생했습니다. 하지만 10개의 스레드가 동시에 주문을 요청하는 상황을 테스트하자 문제가 발생했습니다. 트랜잭션 A: SELECT stock FROM options WHERE id = 1; → stock = 10트랜잭션 B: SELECT stock FROM ..

Devlog/SpringBoot 2026.03.06

도메인 순수성을 포기하고 얻은 것들 (순수 POJO vs Rich Domain Model)

Introtl;dr 순수 POJO와 JPA Entity를 완벽히 분리하려다 도메인 클래스에 @Entity를 허용하도록 전면 롤백했고, 프레임워크 종속성은 타협했지만, 비즈니스 로직을 내부에 응집시킨 'Rich Domain Model'의 본질은 완벽하게 지켜냈다-?! 좋은 아키텍처는 결정을 미루는 것이다. 클린 아키텍처나 DDD를 공부하다 보면 귀에 못이 박히도록 듣는 말입니다. 프레임워크나 DB 기술에 종속되지 않는 '순수 도메인'을 만들어야 한다는 뜻이죠. 2026.02.06 - [Devlog/SpringBoot] - 테스트 코드가 알려주는 객체의 책임과 구조의 미학2026.02.13 - [Devlog/SpringBoot] - HOW적 사고에서 벗어나기 - Why? (의도가 있는 설계법) 이전 포스트에..

Devlog/SpringBoot 2026.02.27

HOW적 사고에서 벗어나기 - Why? (의도가 있는 설계법)

Intro설계 문서는 작업 지시서가 아니다과거 프로젝트를 진행하며 작성했던 설계 문서는 단순 기능 구현 목록에 가까웠습니다. 비즈니스의 복잡성을 어떻게 해결할 것인지에 대한 고민보다는, "이 필드는 VARCHAR(100)으로 할지 1000으로 할지", "API 경로는 /users인지 /members인지"와 같은 구현 상세(HOW)에만 집중했습니다. 심지어 통신 패러다임이 완전히 다른 웹소켓(WebSocket)의 Pub/Sub 라우팅 주소까지 REST API 명세서 표에 포함하기도 했습니다. 프로토콜의 특성이나 아키텍처적 의도(WHY)는 고려하지 않은 채, 기존 문서 양식의 빈칸을 채우는 데 급급했던 결과입니다. 이러한 방식은 유지보수 과정에서 큰 비용을 발생시켰습니다. 개발 중 변경 사항이 생기면 문서..

Devlog/SpringBoot 2026.02.13

테스트 코드가 알려주는 객체의 책임과 구조의 미학

Intro좋은 소프트웨어란 무엇인가?얼마 전까지 저는 테스트 코드에 대한 의문을 품고 있었습니다. "이미 예외 케이스까지 생각해서 기능 구현은 다 해놨는데, 예측이 되는 결과에 대해서 테스트 코드를 대체 왜 작성해야 하는거지?""개발자가 예측하지 못하는 케이스는 악의적인 유저의 행동 패턴이 대부분 아닐까?" 이전에도 테스트 코드를 작성해 본적은 있지만, 사실 개발을 도와주는 도구라기보다는 유지보수의 짐에 가까웠습니다. 부끄럽지만 그 당시 작성했던 코드를 살짝 훑어보겠습니다. (백엔드 개발에 처음으로 발을 들였던 그 당시의 코드라 정말 이상한 코드..) 1. 외부 의존성과의 강한 결합 (Dependency Hell)가장 큰 문제는 테스트가 실제 DB와 프레임워크에 끈적하게 달라붙어 있었다는 점입니다.@S..

Devlog/SpringBoot 2026.02.06

[Spring Boot] Properties Encryption

Properties 암호화의 중요성? 첫 번째 프로젝트 발표를 마쳤을 때, 그동안 했던 프로젝트를 GitLab에서 GitHub로 미러링 하는 과정에서 GitGuardian으로부터 메일이 왔었습니다. 외부로 공개되어서는 안 될 value가 노출이 되었던 겁니다. 첫 번째 프로젝트에서는 application.properties에 담겨있는 value들에 대해 암호화 처리를 하지 않았었죠.. 첫 프로젝트라 다들 어수선하기도 했고 미러링에 문제가 생기자 팀원 중 한 명이 properties 파일을 제거해 버렸습니다..  처음부터 properties에 대해 암호화를 적용시켰더라면 이러한 불상사를 막을 수 있었을 텐데..라는 생각이 들었습니다. 그래서? 두 번째 프로젝트부터는 인프라 세팅을 하면서 스켈레톤 프로젝트의..

Devlog/SpringBoot 2024.07.18
반응형
TOP