Skip to content

stringbuckwheat/yogimukja

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

목차

  1. 요기먹자?
  2. 주요 기능
    • 대용량 INSERT/UPDATE 작업을 위한 Spring Batch 구현
      • 511,273건의 데이터 Batch 작업에 4.72분 소요
      • Batch 처리 과정
    • Redis 캐싱을 사용한 빠른 응답
    • Scheduler를 활용한 점심 추천 식당 리스트 비동기 알림 전송
  3. 트러블 슈팅
    • Batch 속도 향상 리팩토링
    • Batch 작업 중 발생한 동시성 문제 해결

요기먹자🍜🍣🥗

요기먹자는 위치 기반 맛집 추천 및 리뷰 서비스의 백엔드 API입니다. (API 문서)

서울시 일반음식점 인허가 정보 API를 기반으로,

  • 위치 기반 추천: 사용자의 위치 정보를 기반으로 가깝고 평점이 좋은 식당을 추천합니다.
  • 식당 리뷰: 사용자는 식당 리뷰를 작성하고 별점을 매길 수 있습니다.
  • 점심 추천 기능: 매일 11시 30분에 사용자 위치 근방의 식당을 추천합니다.

기술 스택

  • Spring Boot(3.3)
    • Webflux
    • Spring Batch
    • Spring Security
    • JWT
    • proj4j
  • JPA/Hibernate, QueryDsl
  • Postgres(16.4), Redis

ERD

yogimukja_erd


주요 기능🛠️

1) 대용량 INSERT/UPDATE 작업을 위한 Spring Batch 구현

Spring Batch, Webflux, Scheduler를 활용하고, 매일 오전 3시에 스케줄러를 실행하여 최신 데이터를 보장합니다.


⭐️ 511,273건의 데이터 Batch 작업에 4.72분 소요

스크린샷 2024-10-03 15 50 59 스크린샷 2024-10-03 15 55 20

(신규 INSERT 시(빈 테이블)의 소요 시간입니다.)


⚙️ Batch 처리 과정

  • Reader

    • 서울시 일반음식점 인허가 정보 API 사용
    • Webflux를 사용하여 비동기로 데이터 가져오기
    • 최초 요청으로 전체 API 데이터 범위를 알아낸 후 ConcurrentLinkedQueue에 범위 정보를 저장하는 방식으로 동시성 문제 해결
  • Processor

    • API에서 가져온 데이터 가공
    • 유효하지 않은(ex. 폐업한지 5년 이상된 가게 혹은 키즈카페 등의 음식점) 데이터 필터링
    • 위치 정보 WGS84 좌표계 변환, 폐업일 파싱 등을 거쳐 RestaurantPayload 객체 생성
  • Writer

    • 가공한 데이터로 Bulk INSERT/UPDATE 수행
    • 간략한 기존 음식점 데이터(관리 ID와 API 업데이트 시점)를 existingRestaurantMap에 저장
      • 이를 통해 신규 추가/업데이트할 음식점 구분
    • 한 번의 Chunk에서 받은 식당 데이터를 순회하며
      • existingRestaurantMap에 해당 관리ID가 없으면 신규 식당으로 간주, 삽입 목록에 추가
      • map에 정보가 존재하지만 기존 식당 데이터의 수정시각보다 api 수정 시각이 최근일 경우, 업데이트 목록에 추가
    • 데이터를 모두 분류한 후, JdbcTemplate을 사용하여 각 목록을 bulk insert/update
  • RestaurantReclassificationTasklet

    • 특정 단어, 브랜드명을 포함한 식당명을 기반으로 식당 카테고리 재분류
    • ex) '투썸 플레이스', '빙수' 등을 포함하는 식당명은 '카페/디저트' 카테고리로 UPDATE

2) Redis 캐싱을 사용한 빠른 응답

사용자가 자주 접근하는 정보와 변하지 않는 정보를 Redis에 캐싱하여 조회 시 응답 속도 개선 캐시 별 유효기간 세분화로 최적화

  • 최근 조회수가 높은 식당 상세 정보
    • 식당 상세 조회 시 Redis에 조회수를 기록하고, 1시간 내에 10회 이상 조회 시 식당 상세 정보 캐싱
    • 상세 조회 시 캐시된 데이터를 우선적으로 반환하고, 없을 시에만 DB에서 조회
    • 유효기간: 1H
  • '어제' 리뷰 점수가 높은 식당 10개 리스트
    • 메인 페이지 '랭킹'에 띄울 목적이라 자주 접근하는 정보라고 판단
    • 유효기간: 24H
  • 서울 행정구역 정보 캐싱
    • 변경 가능성이 낮기 때문에 캐시 유효기간을 두지 않아 성능 최적화

3) Scheduler를 활용한 점심 추천 식당 리스트 비동기 알림 전송

매일 11시 30분에 사용자 위치에 따른 점심 식당 추천 리스트 전송

lunch_recommend

  • Flux, Mono를 사용하여 각 사용자의 점심 추천 메시지를 비동기/병렬 전송
    • 사용자별 점심 추천 내역 추출 후,
      • 지정된 위치에서 500m 이내, 술집/디저트 제외 후 별점 순 정렬, 5개
    • 비동기/병렬 처리한 후 Discord 전송

💡 트러블 슈팅

1) Batch 속도 향상 리팩토링

batch_refactoring

  • 배경
    • 기존 Batch 프로세스가 17여분 소요되는 문제 발생
  • 원인
    • OpenAPI 데이터를 받아오는 작업에서 단일 스레드, 동기식 수신
    • OpenAPI와 기존 테이블 데이터를 비교해서 수정/삽입 대상 판별 로직에서 빈번한 SELECT 발생
    • JPAsaveAll()을 사용하였으나 Bulk Insert/Update 쿼리가 아닌, 개별 Insert/Update 발생
      • @Id에 auto_increment 속성 사용 시, Bulk Insert/Update가 작동하지 않는다는 사실 인지
  • 해결
    • Batch 스레드 병렬 처리 및 OpenAPI 데이터를 비동기 방식으로 가져오도록 수정
    • 수정/삽입 대상 판별 로직 최적화
      • 배치 프로세스 시작 시점에 '관리ID(지자체 발급), OpenAPI 수정일' Map에 저장
      • Map에 존재하지 않으면 Insert, 존재한다면 OpenAPI 수정일을 비교하여 Update
    • JDBC Template를 도입하여 여러 SQL 쿼리를 한 번에 묶어서 실행하는 것을 보장

2) Batch 작업 중 발생한 동시성 문제 해결

  • 배경/원인
    • API 데이터를 받아오는 작업을 병렬 처리하기 위해 TaskExecutor를 사용하도록 구현
    • 이때, 동일 범위에 대한 요청이 여러 번 발생하는 문제 발생
    • 작업 범위를 관리하는 동시성 제어가 제대로 이루어지지 않아서 생긴 문제
  • 해결: ConcurrentLinkedQueue 도입
    • 초기 요청으로 전체 API 데이터 범위를 알아낸 후, 이를 통해 Range를 생성하여 ConcurrentLinkedQueue에 저장
    • 각 스레드는 작업 시작 시 Queue에서 Range를 하나씩 가져와(poll()) 해당 범위에 대한 API 요청을 수행하도록 수정하여 해결

Releases

No releases published

Packages

No packages published

Languages