기술 검토 및 스택 선정 - 100-hours-a-week/KTB4-5th-wiki GitHub Wiki

백엔드 기술 선정 및 스택 검토

서비스의 데이터 정합성, 응답 시간, 장애 대응, 운영 부담을 기준으로 기술을 선정한다. 성능 개선 효과는 실제 부하에서 검증하며, 추가 인프라는 요구사항과 측정 결과에 따라 도입한다.

목차

  1. 언어 및 실행 환경
  2. 프레임워크
  3. 관계형 데이터베이스
  4. 인메모리 캐시
  5. 비동기 처리 및 배치

1. 언어 및 실행 환경

1.1 Java·JVM 선정 근거

정적 타입 검사를 통한 변경 안정성과 JVM의 동시 처리·성능 진단·메모리 최적화 기능을 주요 선정 근거로 검토한다.

항목 기대 효과 고려 사항
가상 스레드 DB·외부 API 응답처럼 I/O를 기다리는 요청이 많을 때, OS 스레드 점유 부담을 줄이면서 순차적인 코드 구조를 유지할 수 있다. 개별 외부 호출 시간이 단축되거나 DB 연결·외부 서버의 처리 한도가 늘어나는 것은 아니다. 동시 호출 제한은 별도로 관리해야 한다.
JFR CPU 사용, 객체 할당, GC, 락 대기, I/O 등을 같은 시간축에서 기록해 지연 원인을 분석할 수 있다. 실제 수집 항목과 부하는 JDK 버전 및 기록 설정에 따라 달라진다.
GC 선택 처리량과 응답 지연 등 서비스의 우선순위에 따라 메모리 회수 방식을 선택할 수 있다. Parallel GC는 처리량, G1은 처리량과 정지 시간의 균형, ZGC·Shenandoah는 짧은 정지 시간에 초점을 둔다. CPU·메모리 비용과 함께 비교해야 한다.
정적 타입 검사 타입 불일치를 컴파일 단계에서 발견하고, 메서드 변경의 영향을 확인하기 쉬워 협업과 유지보수에 도움이 된다. 비즈니스 로직의 정확성까지 보장하지는 않으므로 테스트가 필요하다.
Compact Object Headers 객체 헤더를 줄여 작은 객체가 많은 작업의 힙 사용량과 GC 비용을 줄일 가능성이 있다. JDK 25에서는 별도 옵션으로 활성화한다. 객체 구성과 정렬에 따라 절감 효과가 달라지므로 실측이 필요하다.

1.2 프로젝트 적용 및 검증

본 프로젝트에서는 조회 요청이 집중될 때의 I/O 대기와 객체 생성 증가를 주요 검증 대상으로 삼는다.

가상 스레드의 동시 처리 효과와 Compact Object Headers의 메모리 절감 효과는 동일한 부하에서 다음 지표를 비교해 판단한다.

  • 처리량과 p95·p99 응답 시간
  • 객체 할당량과 힙 사용량
  • GC 비용과 CPU 사용량

가상 스레드·JFR·GC·Compact Object Headers는 Kotlin 등에서도 활용할 수 있는 JVM 기능이다. 따라서 Java와 Kotlin을 구분하는 근거보다는 JVM 실행 환경의 장점과 최적화 선택지로 평가한다.


2. 프레임워크

2.1 Spring 생태계 선정 근거

업무 로직의 분리와 테스트 용이성, 일관된 트랜잭션·보안 정책, 운영 지표 수집을 주요 선정 근거로 검토한다.

항목 기대 효과 고려 사항
Spring Framework — DI·IoC 객체의 생성·연결·생명주기를 컨테이너에서 관리해 업무 정책과 외부 연동 구현을 분리하기 쉽다. 대체 구현체를 주입해 실제 외부 호출 없이 업무 로직을 테스트할 수 있다. 역할 분리와 테스트 용이성은 의존성을 적절히 설계해야 얻을 수 있으며, 프레임워크 도입만으로 보장되지는 않는다.
Spring Framework — 트랜잭션 @Transactional로 함께 성공하거나 실패해야 하는 DB 작업의 경계와 롤백 정책을 선언해 반복적인 처리 코드를 줄인다. 기본 프록시 방식에서는 같은 객체 내부의 직접 호출에 별도 트랜잭션 설정이 적용되지 않는다. 외부 알림 발송·AI 호출은 DB 롤백 대상이 아니다.
Spring Boot — 의존성 관리·자동 구성 관리 대상 라이브러리의 버전 조합과 조건별 기본 구성을 제공해 초기 설정 및 구성요소 연결 부담을 줄인다. 관리 버전을 개별 변경하면 호환성을 추가로 확인해야 한다. 자동 구성의 적용 조건을 이해하고 필요한 설정을 조정해야 한다.
Spring Boot — 운영 지표 Actuator·Micrometer 통합으로 HTTP 요청 지연과 DB 커넥션 풀 사용 상태 등 기본 지표 수집 및 모니터링 연동을 간소화한다. 수집 범위는 라이브러리와 설정에 따라 달라진다. 알림 실패율·AI 작업 완료 시간 등 서비스별 지표는 별도로 계측해야 한다.
Spring Security 보안 필터 체인을 통해 인증 결과와 API 접근 정책을 일관되게 관리한다. OAuth2 Resource Server 기능으로 JWT 검증 기반을 활용할 수 있다. 냉장고 멤버 여부처럼 개별 자원에 대한 업무 권한은 별도로 정의하고 인가 검사에 연결해야 한다.

2.2 프로젝트 적용 범위

본 프로젝트에서는 다음 영역에 Spring 생태계를 활용한다.

  • 알림 발송·AI 연동 구현과 업무 정책의 분리
  • 여러 DB 변경에 대한 트랜잭션 처리
  • 인증 및 냉장고 접근 권한 검사
  • HTTP 요청과 DB 커넥션 등 운영 지표 수집

외부 연동 실패 대응, 자원별 접근 권한, 알림 중복 처리 등 서비스 고유의 정책은 별도 설계와 검증이 필요하다.

2.3 배치 프레임워크 도입 범위

현재 배치는 Spring Scheduler와 MySQL 작업 기록을 사용한다.

Spring Batch는 대용량 청크 처리와 단계별 실행 이력·재시작이 필요해질 때 검토하며, 현재는 도입을 보류한다. 상세 기준은 비동기 처리 및 배치에 정리한다.


3. 관계형 데이터베이스

3.1 MySQL·InnoDB 선정 근거

재고 차감과 조리 기록의 원자적 처리, 동시 요청에 대한 재고 보호, 중복 요청 제어를 주요 근거로 MySQL 8.0과 InnoDB를 선정한다.

항목 기대 효과 고려 사항
트랜잭션 재고 차감과 조리 기록을 하나의 작업으로 묶어 모두 반영하거나 취소함으로써 일부 변경만 남는 상태를 방지한다. 업무 규칙 검증과 트랜잭션 경계를 적절히 설계해야 한다. 인메모리 캐시 등 DB 외부의 변경은 함께 롤백되지 않는다.
장애 복구 언두 로그로 실패한 변경을 되돌리고, 리두 로그와 WAL을 통해 데이터 파일에 반영되지 않은 변경을 장애 이후 복구할 수 있다. 커밋 결과의 보존 수준은 로그 기록·동기화 설정과 저장장치의 동작에 영향을 받는다.
REPEATABLE READ·MVCC 같은 트랜잭션의 일반 조회에서 일관된 데이터 버전을 읽으면서 읽기와 쓰기의 잠금 충돌을 줄인다. 스냅샷 조회는 최신 값이나 동시 차감 방지를 보장하지 않는다. 장시간 트랜잭션은 언두 기록의 정리를 지연시킬 수 있다.
SELECT FOR UPDATE 차감할 재료의 현재 데이터를 잠근 뒤 수량을 검증하고 변경해 동시 요청의 충돌을 제어한다. 조회와 변경을 같은 트랜잭션에서 처리해야 한다. 잠금 순서를 통일하고 데드락 발생 시 전체 트랜잭션을 재시도할 정책이 필요하다.
UNIQUE 멱등키 동일한 요리 적용 요청이 동시에 들어와도 중복 키 저장을 DB에서 차단한다. 멱등키 등록과 재고 차감을 같은 트랜잭션에 포함해야 한다. 기존 결과 반환과 동일 키의 요청 내용 불일치 처리는 별도로 구현해야 한다.
B+Tree 인덱스 조회 조건에 맞는 탐색 범위를 줄여 조회 성능을 높이고, 잠금 읽기·변경 작업의 잠금 범위를 줄이는 데 활용한다. 실제 쿼리와 실행 계획을 기준으로 설계해야 한다. 과도한 인덱스는 저장 공간과 쓰기 비용을 증가시킨다.

3.2 요리 적용의 정합성 관리

요리 적용은 다음 작업을 하나의 트랜잭션으로 처리하는 구조를 기준으로 한다.

  1. 멱등키 등록
  2. 재료를 일정한 순서로 잠금
  3. 현재 재고 검증 및 차감
  4. 조리 기록 완료
  5. 전체 변경 커밋 또는 롤백

중복 요청은 재고를 다시 차감하지 않고 기존 처리 결과를 반환하도록 설계한다.

3.3 재료명 검색 방식

재료명 검색은 냉장고별 최대 100종이라는 요구사항을 전제로, 인덱스로 해당 냉장고의 재료를 찾은 뒤 LIKE '%검색어%'로 검색한다.

부분 문자열 조건 자체가 B+Tree 범위 탐색을 활용하는 것은 아니며, 냉장고 조건으로 검사 대상을 줄이는 방식이다.

검토 대상 적용 기준
범위 제한 + 부분 문자열 검색 현재 냉장고 내부의 제한된 재료를 검색할 때
MySQL ngram 전문 검색 현재 검색 방식이 성능 목표를 충족하지 못할 때
OpenSearch 오타 허용·자동 완성 등 고급 기능이나 독립적인 검색 확장이 필요할 때

전환 여부는 데이터 건수만이 아니라 실제 검색 지연, DB 부하, 요구 기능을 기준으로 판단한다.


4. 인메모리 캐시

4.1 버전별 선정 근거

V1의 반복 조회 비용 감소, V2의 다중 인스턴스 간 캐시 공유, V3의 추천 결과 재사용을 기준으로 캐시 구성을 단계적으로 확장한다. 반드시 보존해야 하는 데이터는 MySQL을 기준으로 관리한다.

버전 선택 활용 및 선정 근거 고려 사항
V1 Spring Cache + Caffeine 반복 조회 결과를 애플리케이션 메모리에서 재사용한다. 네트워크 통신과 별도 캐시 서버 운영 없이 응답 시간과 DB 조회 비용을 줄일 수 있다. 앱 종료 시 캐시가 사라지고, 여러 인스턴스 사이에 값이 공유되지 않는다. 용량·만료 설정과 성능 검증이 필요하다.
V2 애플리케이션 다중 인스턴스 + Redis 여러 앱 인스턴스가 같은 캐시를 사용하고, 앱만 배포할 때도 기존 캐시를 재사용한다. V3의 추천 기능까지 고려해 Redis를 도입한다. 단일 앱 장애의 영향을 줄이는 것은 다중 인스턴스 구성의 역할이다. Redis 장애 시 MySQL 조회로 대체할 수 있어야 하며, Redis 고가용성은 별도로 검토해야 한다.
V3 Redis + MySQL Redis에 추천 결과와 생성 중 상태를 두어 결과를 공유하고 중복 생성을 줄인다. 마지막 추천과 일일 생성 횟수는 MySQL에 영속 저장한다. 캐시 유실이 추천 소실이나 생성 한도 초기화로 이어져서는 안 된다. 동시 생성 제어와 한도 검증은 별도로 설계해야 한다.

4.2 Redis 선정 이유

Redis는 공유 캐시와 재시작 후 복구 선택지를 함께 제공한다는 점에서 우선한다.

Memcached도 캐시 공유 요구를 충족하지만, Redis는 RDB·AOF에 기록된 데이터를 복구에 활용할 수 있다. 다만 영속화에는 디스크·쓰기·복구 비용이 추가되며, 만료·퇴출된 값이나 저장되지 않은 최근 변경까지 보존하는 것은 아니다.

4.3 추천 캐시 관리 원칙

  • 캐시 존재와 추천 유효성을 구분한다. TTL이 남아 있어도 재고가 변경되었다면 재고 지문·추천 옵션·만료 재료 포함 여부 등을 기준으로 재사용 가능성을 판단한다.
  • 캐시 미스가 곧 AI 재생성을 의미하지 않는다. MySQL에 재사용 가능한 추천이 있으면 이를 조회해 캐시를 다시 채우며, 복원은 새 생성 횟수로 계산하지 않는다.
  • 서비스 정책은 영속 데이터로 유지한다. 마지막 추천과 냉장고별 하루 최대 10회 생성 한도는 MySQL을 기준으로 관리한다.

4.4 검증 및 미정 사항

MySQL만으로도 기능 요구는 충족할 수 있으므로, 캐시의 가치는 응답 시간·DB 조회 감소 효과가 추가 메모리와 운영 비용을 정당화하는지로 판단한다.

현재 개선 폭은 미측정이다. 실제 결과 크기·재사용 빈도·캐시 적중률·DB 부하를 확인한 뒤 다음 설정을 결정한다.

  • 캐시 TTL
  • 최대 메모리
  • RDB 저장 주기 및 AOF 디스크 동기화 주기

5. 비동기 처리 및 배치

5.1 처리 방식 선정 기준

응답 전에 결과를 확정해야 하는지, 작업 유실을 허용할 수 있는지, 사용자 요청 없이 실행해야 하는지를 기준으로 처리 방식을 구분한다.

현재는 애플리케이션 내부 비동기 실행과 Spring Scheduler, MySQL 작업 기록을 활용하고 별도 메시지 브로커와 Spring Batch는 도입하지 않는다.

작업 선정 방식 선정 근거 고려 사항
재고 차감·조리 기록, 냉장고 권한 변경 동기 처리 데이터와 권한 변경이 확정된 뒤 성공을 응답해야 한다. 함께 반영해야 하는 변경을 같은 트랜잭션으로 관리한다.
AI 추천 생성 애플리케이션 내부 비동기 실행 오래 걸리는 외부 호출을 요청에서 분리하고 이전 추천이나 처리 중 상태를 먼저 응답한다. 앱 종료 시 대기·실행 중 작업이 유실될 수 있다. 실패 후 다음 요청에서 다시 시작할 수 있다는 정책을 전제로 한다.
임박·만료 알림 대상 탐색, 정기 정리 Spring Scheduler 사용자 요청 없이 정해진 시각이나 주기에 작업을 시작한다. 스케줄러만으로 작업 완료·재시작을 보장하지 않으므로 필요한 진행 상태와 실패 처리를 별도로 관리한다.
푸시 알림 발송 MySQL 작업 기록 + 백그라운드 워커 발송 대상과 상태를 보존해 외부 호출 실패나 앱 재시작 후 미완료 작업을 다시 처리한다. 작업 조회·상태 갱신이 DB 부하를 늘릴 수 있다. 외부 발송 성공 후 결과 기록에 실패하면 중복 발송이 가능하므로 중복·재시도 정책이 필요하다.

5.2 버전별 적용 계획

버전 적용 내용
V1 Spring Scheduler로 알림 대상을 탐색하고, MySQL에 기록한 푸시 작업을 백그라운드에서 발송한다.
V2 같은 구성을 다중 인스턴스로 확장한다. 공통 작업 상태는 MySQL에서 관리하고 Redis는 캐시 공유에 사용한다. 동일 작업의 동시 처리를 막기 위해 작업 선점과 중복 등록 방지를 설계한다.
V3 AI 추천 생성을 요청과 분리한다. Redis는 추천 결과와 생성 중 상태를 공유하고 MySQL은 마지막 추천과 일일 생성 횟수를 보존한다. 추천 푸시는 기존 MySQL 작업 처리 구조를 재사용한다.

5.3 추가 기술 도입 기준

기술 현재 판단 도입 검토 조건
Spring Batch 도입 보류 대용량 청크 처리, 여러 단계의 실행 이력, 중단 지점부터의 재시작이 필요할 때
RabbitMQ 미도입 MySQL 작업 조회가 병목이 되거나 워커를 애플리케이션과 독립적으로 배포·확장해야 할 때
Kafka 미도입 이벤트 장기 보관·재처리와 여러 소비자 그룹의 독립적인 처리가 필요할 때

5.4 공통 운영 원칙

비동기 실행 자체가 작업 보존이나 중복 방지를 보장하지는 않는다. 다음 항목을 작업 특성에 맞게 함께 설계한다.

  • 작업별 유실 허용 범위와 복구 방식
  • 실패 시 재시도 및 중복 처리 정책
  • 다중 인스턴스의 작업 선점과 중복 실행 제어
  • 동시 실행 제한과 작업 적체 모니터링