기술 검토 및 선정 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

언어 : JAVA21

  • Record 패턴 지원으로 간편한 DTO 설정 가능

    • Record 패턴이란? 데이터만을 포함하는 불변한 객체로 선언할 수 있게 해주는 패턴
    • Record 클래스는 14부터 사용 가능하나 패턴으로 사용 가능한건 21부터
  • LTS 중 최신으로 2031년까지 지원 예정

  • Virtual Thread 지원

    JDK 21의 신기능 Virtual Thread 알아보기 (안정수 James)

    Core Libraries

    [Project Loom] Virtual Thread에 봄(Spring)은 왔는가 | 카카오페이 기술 블로그

  • Virtual Thread(가상 스레드, 경량 스레드)란?

    • (JAVA21 official docs) 특정 OS 스레드에 종속되지 않는 인스턴스, OS 스레드와 다수의 Virtual Thread가 매핑됨
    • 기존 스레드는 실제 OS 스레드와 1:1 매핑되어 컨텍스트 스위칭 시 OS가 개입하는 등 비용이 높았음
    • JVM 자체적인 스케쥴링으로 컨텍스트 스위칭 비용이 줄어 효율적으로 운영할 수 있음
    • 블로킹 I/O 작업에 적합함
  • Virtual Thread 사용 시 유의사항(JAVA21 official docs)

    • 가상 스레드 풀링 금지
      • 가상 스레드는 자원의 개념보다는 작업의 개념으로 봐야 함. 플랫폼 스레드를 사용할 때는 자원을 미리 확보한다는 개념으로 풀링했지만, 가상 스레드는 풀링하면 비효율적임. 한 작업에 한 가상스레드!
    • 제한적인 리소스 접근 및 사용 시 Semaphore를 사용한다.
      • 세마포어 : 한 리소스에 최대 n개의 접근만 허용하는 기법
      • DB의 경우 커넥션 풀이 세마포어의 역할을 함
    • 재사용 가능 객체 유지하지 말 것
      • 가상 스레드는 한 작업에 하나 할당됨. 캐싱하는 것은 해당 패턴에 위배
    • 길고 빈번한 pinning 지양
      • pinning : 가상 스레드가 특정 플랫퐄 스레드에 고정되어 스레드 하나를 점유하는 현상
  • 무조건적인 Virtual Thread 도입이 아님, MVP 구축 후 병목 구간을 식별하여 도입하기 위해 필요

    • 예상되는 사용 가능 구간 : 좌표를 kakao api로 행정구역으로 변환하는 과정
    • official docs에 따르면, 10,000개 이상의 Virtual Thread를 만들 상황이 아니라면 Virtual Thread의 이점을 가질 수 없다고 함
  • SNS 특성 상 조회가 빈번할 것으로 예상됨, 이에 따른 대응책 구비 필요

  • JAVA 17과 비교

    항목 Java 17 Java 21
    출시 시점 2021.09 2023.09
    지원 종료 2029 2031 ✅
    LTS 여부 ✅ LTS ✅ LTS
    기본 GC G1 GC G1 GC
    Generational ZGC 추가    
    Virtual Threads ❌ 미지원 ✅ 정식 지원
    Record Patterns ✅ 정식 지원
    Spring Boot 호환성 ✅ 안정 ✅ 최적
  • 유저 한명이 최대 100장의 사진을 올릴 수 있고 AI서버 모델은 임베딩 2초, 후속작업(태깅, 중복사진 여부, 흔들린 사진 여부, 품질점수 측정) 1~2초 정도 소요

  • 유저가 많아질 수록 처리해야하는 사진 수가 많아짐. ⇒ 대용량 처리 기술 도입 필요

  • AI서버는 비즈니스 로직에서 가장 긴 시간을 소모하여 DB접근과 동시에 병목이 예상되는 부분이므로 향후 AI서버 확장 시 Kafka로 병렬적이 확장 가능

API Docs : SpringDoc

  • 자동화된 문서 생성으로 별도 문서생성 없이 컨트롤러 분석으로 API 스펙문서 작성 가능
  • 자동으로 Swagger UI와 연동하여 FE와 협업 효율 증가
  • 어노테이션 추가로 코드 작성하듯이 문서 생성 가능
    • 컨트롤러에 어노테이션 기반 설명으로 주석으로 설명하는 것보다 가독성 향상

      @Operation(summary = "유저 프로필 조회", description = "AccessToken을 기반으로 유저 정보를 조회합니다.")
      @ApiResponses({
          @ApiResponse(responseCode = "200", description = "성공"),
          @ApiResponse(responseCode = "401", description = "Access Token이 유효하지 않음")
      })
      @GetMapping("/api/user/{userId}")
      public ResponseEntity<UserResponse> getUserInfo() {
          ...
      }
      
# 언어 : JAVA21
  • Record 패턴 지원으로 간편한 DTO 설정 가능

    • Record 패턴이란? 데이터만을 포함하는 불변한 객체로 선언할 수 있게 해주는 패턴
    • Record 클래스는 14부터 사용 가능하나 패턴으로 사용 가능한건 21부터
  • LTS 중 최신으로 2031년까지 지원 예정

  • Virtual Thread 지원

    [JDK 21의 신기능 Virtual Thread 알아보기 (안정수 James)](https://www.youtube.com/watch?v=vQP6Rs-ywlQ)

    [Core Libraries](https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html)

    [[Project Loom] Virtual Thread에 봄(Spring)은 왔는가 | 카카오페이 기술 블로그](https://tech.kakaopay.com/post/ro-spring-virtual-thread/)

  • Virtual Thread(가상 스레드, 경량 스레드)란?

    • (JAVA21 official docs) 특정 OS 스레드에 종속되지 않는 인스턴스, OS 스레드와 다수의 Virtual Thread가 매핑됨
    • 기존 스레드는 실제 OS 스레드와 1:1 매핑되어 컨텍스트 스위칭 시 OS가 개입하는 등 비용이 높았음
    • JVM 자체적인 스케쥴링으로 컨텍스트 스위칭 비용이 줄어 효율적으로 운영할 수 있음
    • 블로킹 I/O 작업에 적합함
  • Virtual Thread 사용 시 유의사항(JAVA21 official docs)

    • 가상 스레드 풀링 금지
      • 가상 스레드는 자원의 개념보다는 작업의 개념으로 봐야 함. 플랫폼 스레드를 사용할 때는 자원을 미리 확보한다는 개념으로 풀링했지만, 가상 스레드는 풀링하면 비효율적임. 한 작업에 한 가상스레드!
    • 제한적인 리소스 접근 및 사용 시 Semaphore를 사용한다.
      • 세마포어 : 한 리소스에 최대 n개의 접근만 허용하는 기법
      • DB의 경우 커넥션 풀이 세마포어의 역할을 함
    • 재사용 가능 객체 유지하지 말 것
      • 가상 스레드는 한 작업에 하나 할당됨. 캐싱하는 것은 해당 패턴에 위배
    • 길고 빈번한 pinning 지양
      • pinning : 가상 스레드가 특정 플랫퐄 스레드에 고정되어 스레드 하나를 점유하는 현상
  • 무조건적인 Virtual Thread 도입이 아님, MVP 구축 후 병목 구간을 식별하여 도입하기 위해 필요

    • 예상되는 사용 가능 구간 : 좌표를 kakao api로 행정구역으로 변환하는 과정
    • official docs에 따르면, 10,000개 이상의 Virtual Thread를 만들 상황이 아니라면 Virtual Thread의 이점을 가질 수 없다고 함
  • SNS 특성 상 조회가 빈번할 것으로 예상됨, 이에 따른 대응책 구비 필요

  • JAVA 17과 비교

    항목 Java 17 Java 21
    출시 시점 2021.09 2023.09
    지원 종료 2029 2031
    LTS 여부 ✅ LTS ✅ LTS
    기본 GC G1 GC G1 GC
    Generational ZGC 추가
    Virtual Threads ❌ 미지원 ✅ 정식 지원
    Record Patterns ✅ 정식 지원
    Spring Boot 호환성 ✅ 안정 ✅ 최적

프레임워크

Spring Boot 3.4

  • JAVA21 호환성 최적
    • 3.2와 비교 시 Virtual Thread 지원 개선(Micrometer Metric 시스템, Undertow 서버같은 구성요소가 Virtual Thread에서 제대로 동작하도록 개선) ⇒ Virtual Thread에 대한 호환성 강화
  • Spring Framework 6.x 기반으로 jakarta.* 공식 지원(3.x부터 지원하긴 함)
  • HTTP 통신기능 향상
    • 자동 HTTP 클라이언트 팩토리 선택 로직이 개선됨(Apache, Jetty, Netty 설정방식 통합)
    • 별도 의존성을 추가하지 않으면 JDK 기존 HttpClient를 사용하도록 Default Inversion
  • 테스트 편의성 증가
    • Test Container로 DB를 띄울 때 @AutoConfigureTestDatabase 를 통해 유연한 테스트db 사용 가능

Spring Security

  • OAuth 사용 시 간편한 연동 가능
  • JWT 사용 시 Bearer Token을 필터에서 처리 가능
  • 사용자 권한 체크 한 줄로 표현 가능, 비밀번호 암호화 알고리즘 내장 등 보안에 유용한 도구 사용 가능

라이브러리 선정

롬복

  • 보일러플레이트 코드 제거를 통한 유지보수성 극대화
  • 단순 반복 제거로 생산성 향상
  • 설계 의도를 어노테이션으로 표현 가능

DB

Spring Data JPA

항목 Spring Data JPA MyBatis
추상화 수준 높음 (ORM) 중간 (SQL 매핑)
Spring 연동성 기본 연동 (Spring Data JPA) 사용 가능
튜닝 자유도 제한적 (QueryDSL 사용 필요) SQL 직접 제어
러닝 커브 이미 사용경험 있어 학습 난이도 낮음 SQL 직접 제어로 난이도 높을 것으로 예상
특징 객체지향적인 설계, 연관관계 자동 관리 SQL 중심, XML/어노테이션 매핑
  • JPA 구현체 중 하나, Spring Data JPA와 기본 연동됨
  • SQL을 직접 제어하지 않아 성능 튜닝에 제한적이지만 QueryDSL로 커버 가능
  • 객체지향적 설계 지원으로 타 ORM 프레임워크와 비교할 때 러닝 커브 적을 것으로 예상
  • 스프링부트와의 자연스러운 통합과 높은 호환성으로 엔티티 사용 시 에러이슈가 적을 것으로 예상됨

MySQL

항목 MySQL PostgreSQL
주 사용처 ✅ 대규모 읽기 트래픽 복잡한 비즈니스 및 데이터 모델
쿼리 성능 단순 SELECT문에서 빠름 ✅ 쿼리 복잡할수록 유리함
인덱스 일반 인덱스가 빠름
Partial, Expression 인덱스 가능하지만 유연하지 않음 복합, Partial, Expression 인덱스 가능
  • 현재 온기서비스는 복잡한 비즈니스 로직 없음
  • SNS 특성상 읽기 쿼리가 많을 것으로 예상됨 ⇒ MySQL이 SELECT문에서 빠름
    • 앨범 조회, 앨범 피드 조회 등등..
    • 무한스크롤을 통해 반복적인 읽기 api 호출 → 잦은 SELECT 쿼리 발행

Redis(Refresh Token 저장)

  • 수명이 짧은 access token 특징 상 인증 및 인가 흐름에서 refresh token의 유효성 검사는 자주 일어나므로 낮은 지연시간이 중요함
  • Redis의 경우 실제 요청 처리 시 디스크 대신 메모리에서 조회하므로 응답속도가 향상됨
  • Key별로 TTL(Time To Live) 설정 가능하여 refresh token 만료시간 효율적으로 설정 가능

Caching

Redis

구분 Redis Memcached
범용성 높음 (올인원) 낮음 (단일 목적)
성능 다양한 기능 + 빠름 초경량 + 매우 빠름
확장성 클러스터, HA 지원 단순 분산 (client-side)
데이터 유지 영속 가능(AOF, RDB) 휘발성 전용
  • Redis는 Memcached와 다르게 다양한 데이터 타입을 넣을 수 있음
  • 장애상황 대비하여 영속성 일부 유지 가능(AOF, RDB 활용)
  • 클러스터링 및 분산 구성이 잘되어 있어 수평 확장에 용이함
    • 향후 여러 인스턴스가 리프레시 토큰을 공유해야할 때 중앙 집중형 캐시 서버로 Redis 사용 가능
  • 본 프로젝트 사용 시나리오
    • 팔로우/언팔로우

      • 내가 팔로우한 유저 ID, 나를 팔로우한 유저 ID를 SET으로 Redis 저장

        Key: follow:following:<내_유저ID>
        Type: Set
        Value: [내가 팔로우한 유저 ID들]
        예: follow:following:1001 → {2002, 2003, 2004}
        
        Key: follow:follower:<내_유저ID>
        Type: Set
        Value: [나를 팔로우한 유저 ID들]
        예: follow:follower:2002 → {1001, 1010}
      • 팔로우/팔로우 취소 시 DB에 반영 후 Redis에 캐싱

      • 팔로우/팔로워 리스트 조회 시 Redis에서 먼저 조회 → 없으면 DB에서 조회

    • 유저별 월별 방문 장소, 등록 사진 수

      • 유저 월별 일간 사진 수와 전체 사진 수 Hash, String으로 저장

        Key: stat:picture:daily:<userId>:2025-04
        Type: Hash
        Value: {
          "2025-04-01": 3,
          "2025-04-02": 7,
          ...
        }
        
        Key: stat:picture:total:<userId>
        Type: String
        Value: "153"
      • 유저 월별 장소 및 전체 방문 장소 수 Hash, String 저장

        Key: stat:place:monthly:<userId>:2025-04
        Type: Hash
        Value: {
          "강원도/횡성군/횡성읍": 10,
          "서울특별시/마포구/서교동": 5,
          ...
        }
        
        Key: stat:place:total:<userId>
        Type: String
        Value: "27"
        
    • 피드 좋아요 표시

      • 피드 좋아요 수와 좋아요 누른 유저 캐싱(INT, SET)

        Key: like:feed:<feedId>
        Type: Set
        Value: [userId, userId, ...]
        
        Key: like:feed:count:<feedId>
        Type: String or Integer
        Value: 좋아요 수 (INT)

알림 : Redis Stream

  • 서비스 도입 시 Redis Stream과 Redis pub/sub 비교

    구분 Redis Stream Redis pub/sub
    메시지 저장 메시지 저장(디스크 보관 가능) 저장하지 않음(즉시 소멸)
    유저별로 구분 key 기반 유저별 큐 분리 가능 채널 단위 브로드캐스트(전체공지만 가능)
    읽음 여부 관리 추적 가능(XACK, XPENDING) 불가능
    오프라인 수신 여부 온라인일 때 수신 가능 온라인 아니면 메시지 손실
    • 추가로, 이미 캐싱 목적으로 Redis를 도입했기 때문에 Redis Stream으로 기능확장을 하는 것이 올바른 선택
  • Kafka는 도입 고려안한 이유?

    [Redis Stream 적용기](https://dev.gmarket.com/113)

    • Redis Stream 적용기를 찾다가 발견한 지마켓 테크블로그에서 kafka를 고려한 내용이 나온다.
    • 지마켓도 kafka나 MQ를 고려했으나 개발 공수와 리소스가 많이 소모될 것이라고 판단하여 선택지에서 제외한다.

대용량 사진처리(AI pipeline 구축) : Kafka(추후 도입 예정)

  • Kafka vs Redis Stream 비교

    항목 Kafka Redis Stream
    메시지 순서 보장 파티션 내 순서 보장 ID 기반 순서 보장
    재시도 / 실패 복구 offset 기반 재처리 가능 XPENDING/XACK으로 처리
    운영 복잡도 높음 (Zookeeper, 설정 필요) 낮음 (기존 Redis 사용 시 간단)
    모니터링 도구 풍부함 (Kafka UI, Grafana 등) 제한적 (XINFO 등 직접 구현)
    적합한 메시지 크기 수 MB 이상 가능 수 KB ~ 수십 KB 적절
    파티션 기반 분산 자동 분산 없음
    병렬 소비 확장 컨슈머 수 제한 거의 없음 그룹 수 늘어나면 병목 가능
    처리량 증가 시 브로커/파티션 확장으로 해결 키 분할 등 수작업 필요
  • 유저 한명이 최대 100장의 사진을 올릴 수 있고 AI서버 모델은 임베딩 2초, 후속작업(태깅, 중복사진 여부, 흔들린 사진 여부, 품질점수 측정) 1~2초 정도 소요

  • 유저가 많아질 수록 처리해야하는 사진 수가 많아짐. ⇒ 대용량 처리 기술 도입 필요

  • AI서버는 비즈니스 로직에서 가장 긴 시간을 소모하여 DB접근과 동시에 병목이 예상되는 부분이므로 향후 AI서버 확장 시 Kafka로 병렬적이 확장 가능

API Docs : SpringDoc

  • 자동화된 문서 생성으로 별도 문서생성 없이 컨트롤러 분석으로 API 스펙문서 작성 가능
  • 자동으로 Swagger UI와 연동하여 FE와 협업 효율 증가
  • 어노테이션 추가로 코드 작성하듯이 문서 생성 가능
    • 컨트롤러에 어노테이션 기반 설명으로 주석으로 설명하는 것보다 가독성 향상

      @Operation(summary = "유저 프로필 조회", description = "AccessToken을 기반으로 유저 정보를 조회합니다.")
      @ApiResponses({
          @ApiResponse(responseCode = "200", description = "성공"),
          @ApiResponse(responseCode = "401", description = "Access Token이 유효하지 않음")
      })
      @GetMapping("/api/user/{userId}")
      public ResponseEntity<UserResponse> getUserInfo() {
          ...
      }
⚠️ **GitHub.com Fallback** ⚠️