일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- branch 전략
- 쿠키
- AWS
- 주식
- 숫자 블록
- docker
- 결제서비스
- gRPC
- 검색어 추천
- 백준
- prg 패턴
- jwt 표준
- 이분탐색
- 깊게 생각해보기
- 카카오
- 완전탐색
- spring event
- 프로그래머스
- 레디스 동시성
- 구현
- JPA
- piplining
- 객체지향패러다임
- 디버깅
- 누적합
- 알람 시스템
- BFS
- 트랜잭샨
- 좋은 코드 나쁜 코드
- 셀러리
- Today
- Total
코딩관계론
트리맵의 최적화 본문
모름지기 주식 관련 서비스라면 시장 정보를 한눈에 파악할 수 있는 트리맵(TreeMap) 기능이 필수적입니다. 하지만 내가 원하는 트리맵은 **"테마별 시장 상황"**을 중점으로 하기에, 이를 위해 데이터 갱신 주기와 속도 최적화가 필요했습니다.
실시간 갱신 부담 문제와 해결
실시간 갱신은 시스템 리소스에 큰 부담이 될 수 있으므로, 5분 주기의 갱신을 선택했습니다. 그러나 초기 구현 시 문제가 발생했습니다. 초기 트리맵 생성에 7분이 소요되었는데, 이는 5분 주기 갱신 기준으로 터무니없는 시간이었습니다.
초기 트리맵 생성 문제 분석
처음 작성한 코드는 아래와 같았습니다.
Map<ThemaInfo, List<Stock>> groupingThema = new HashMap<>();
themas.forEach(thema -> {
ThemaInfo themaInfo = thema.getThemaInfo();
Stock stock = thema.getStock();
if (groupingThema.containsKey(themaInfo)) {
groupingThema.get(themaInfo).add(stock);
} else {
groupingThema.put(themaInfo, new ArrayList<>(List.of(stock)));
}
});
return groupingThema;
이 코드는 각 테마 정보를 기반으로 주식 데이터를 그룹화하는 로직이었지만, 성능 문제가 발생했습니다. 원인은 HashMap의 키 비교 과정에서 발생한 비효율성이었습니다.
구체적으로, Stock 엔터티의 equals 메서드에서 불필요한 필드까지 동등성 비교에 사용했기 때문에 추가적인 쿼리 호출이 발생했고, 이로 인해 속도가 매우 느려졌습니다.
Objects.equals(themas, stock.themas)를 비교하기 위해서 테마를 데이터베이스에서 로딩해야 하는 숨겨진 작업이 있었습니다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stock {
@OneToMany(mappedBy = "stock", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@BatchSize(size = 100)
private List<Thema> themas = new ArrayList<>();
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Stock stock))
return false;
return issuedShares == stock.issuedShares && price == stock.price && Objects.equals(id, stock.id)
&& Objects.equals(code, stock.code) && Objects.equals(name, stock.name)
&& market == stock.market && Objects.equals(href, stock.href) && Objects.equals(
marketCapitalization, stock.marketCapitalization) && Objects.equals(themas, stock.themas)
&& Objects.equals(raiseReasons, stock.raiseReasons);
}
}
해결방안 : equals는 비지니스 로직의 동등성만 비교한다.
이 문제를 해결하기 위해서 엔터티의 equals는 어떻게 정의할까를 찾아봤습니다. 대부분의 사람들이 추천하는 방법은 비지니스 로직에서 동등성을 확인할 수 있는 필드로만 설정하는 것이 좋다고 한다. 저의 경우에는 stockCode만으로 비지니스 로직의 동등성을 파악할 수 있기 때문에 equals는 code만 줬다.
이를 적용한 후, 트리맵 생성 속도는 7분 → 5분으로 단축되었습니다.
네트워크 호출 최적화
다음으로 확인한 문제는 불필요한 네트워크 호출이었습니다. 아래와 같은 방식으로 각 주식에 대한 차트 데이터를 개별적으로 불러오고 있었습니다. 이 방식에서는 주식의 상장 개수만큼 네트워크 호출이 발생했고, 이는 큰 병목으로 작용했습니다.
List<StockChart> stockCharts = stocks.stream()
.map(t -> stockChartRepositoryPort.loadStockChartWithCode(t.getCode()))
.toList();
SQL IN 절을 활용한 네트워크 부하 감소
문제를 해결하기 위해 SQL IN 절을 활용하여 필요한 차트 데이터만 한 번에 가져오도록 개선했습니다.
List<StockChart> stockCharts = stockChartRepositoryPort.loadStockChartInStockCode(
stocks.stream().map(Stock::getCode).toList());
단, 한 번에 모든 차트 데이터를 가져올 경우 네트워크 부하 및 메모리에 부하가 커질 수 있기 때문에 계산에 필요한 최소한의 데이터만 불러오도록 설계했습니다. 저의 경우에는 3일치의 차트데이터만 필요했기 때문에 3일치의 차트데이터만 가지고 올 수 있도록 했습니다.이 방식으로 최종 수행 시간은 5분 → 1분으로 줄었습니다.
데이터 캐싱과 요청 처리 구조 개선
최적화된 트리맵을 사용자 요청 시마다 실시간으로 생성하는 것은 비효율적이었습니다. 이를 해결하기 위해 다음과 같은 전략을 도입했습니다.
- 갱신된 트리맵 데이터를 Redis에 저장
5분 주기로 생성된 트리맵을 Redis에 업로드하여 캐싱했습니다. - 사용자 요청 시 Redis에서 트리맵을 조회하여 제공
실시간 트리맵 생성 부담을 제거하고, 사용자 요청에 대해 즉시 응답할 수 있도록 개선했습니다.
이러한 구조로 변경한 결과, TPS를 지속적으로 확장할 수 있는 기반을 마련했습니다.
결론 및 배운 점
- 비즈니스 로직에 맞는 equals와 hashCode 정의가 중요
불필요한 필드를 비교 대상으로 포함할 경우 성능 저하와 쿼리 호출 증가로 이어질 수 있습니다. - 네트워크 호출은 배치 처리로 최적화
개별 호출이 많을 경우, SQL의 IN 절을 활용한 배치 처리를 통해 네트워크 부하를 줄일 수 있습니다. - 캐싱을 통한 시스템 부하 감소
반복적인 데이터 요청에 대해서는 Redis와 같은 캐시 솔루션을 활용하여 실시간 연산 부담을 줄일 수 있습니다.
이 경험을 통해 주식 테마 트리맵의 빠른 갱신과 요청 처리라는 두 가지 목표를 모두 달성할 수 있었습니다.
'개발 > Hot-Stock' 카테고리의 다른 글
비용최적화 (0) | 2025.01.02 |
---|---|
주식 트렌드 추적하기 (feat-검색량 추정) (0) | 2024.12.20 |
증권 뉴스 분석 자동화 사례: TOT 방식 적용과 정확도 향상 과정 (4) | 2024.12.18 |
AWS - cpu utilization over 80%(search service가 자꾸 죽어요...!) (1) | 2024.12.18 |
중복 뉴스 필터링을 위한 최적화 방안 (3) | 2024.12.09 |