백엔드 기술 검토 및 선정 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

Devths의 특징

  • 인증/인가의 복잡성 : Google OAuth2 소셜 로그인 + 우리 서비스의 개별적 Access Token, Refresh Token 사용
    • Google의 Access Token, Refresh Token도 별도로 사용 및 관리
  • 외부 API를 사용(Google Calendar API, Google Tasks API, FastAPI 기반 AI 서버의 OCR API, 마스킹 API 등)
  • AI 기능의 경우 비동기 아키텍처가 요구됨
    • OCR 처리 및 분석, 마스킹 등에 오랜 시간이 소요
    • AI 챗봇 응답을 스트리밍으로 내려줘야 하는 요구사항 존재
  • 실시간성이 중요한 도메인 존재
    • 실시간 채팅, 알림 센터, 푸시 알림 등
  • 용도별 저장소 분리가 필요
    • RDB, NoSQL, 캐시/토큰 등

언어

Java vs. Kotlin

👉 전제

자바와 코틀린은 모두 JVM을 기반으로 하는 언어

상호 운용성이 100% → 같은 프로젝트에서 혼용 가능(호환성 문제가 없음)

👉 언어 철학 차이

구분 Java Kotlin
설계 철학 보수적, 명시적 생산성, DX
언어 사양의 변경 속도 느리지만 안정적 빠름
언어 일관성(예측 가능성) 높음 비교적 낮음
편리성(Null-safety, Data class 등) 비교적 낮음 Java에 비해 높음

👉 동시성에서의 차이

구분 Java Kotlin
실시간 채팅 - 스프링 MVC 및 WebSocket와 궁합이 좋음
- 운영 레퍼런스가 많음
- 장애/성능 이슈를 예측 가능함
- Coroutine 기반 핸들러 로직이 간결(단, Non-Blocking 설계가 전제)
알림 - 작업 큐 컨슈머 및 배치 처리에서 표준 패턴이 많음
- 관측과 디버깅이 직관적임
- 알림 병렬 발송/타임아웃/취소를 구조적으로 묶기 쉬움(Coroutine Scope 설계 필요)
AI 작업 비동기 처리 - 전통적인 워커 모델(스레드풀/배치)이 단순
- 성능 튜닝 및 운영 경험 참고 자료가 많음
- 동시에 여러 단계를 처리하는 파이프라인을 다룰 때 Kotlin에서 제공하는 구조적 동시성이 편리함

🔥 의사 결정

  • Kotlin만의 장점들이 우리 서비스(Devths)에 기술적으로 적합한가?
    • 동시성 부분에서는 일부 적합하지만, Java와 비교했을 때 대부분 큰 차이가 없다.
    • Devths의 동시성 관련 요구사항은 실시간성과 비동기 처리가 혼합된 구조이기 때문에 Kotlin에서 지원하는 기능들이 효과적일 것이나, Java의 풍부한 레퍼런스로부터 확보 가능한 운영 안정성과 장애 대응성을 더 중요하다고 판단했다.
  • Kotlin을 채택함으로써 갖는 이점들이 트레이드 오프(Java 대비 낮은 숙련도로 인한 개발 속도 지연)를 감당할 정도인가?
    • 그렇지 않다고 판단했다.
  • Java를 선택했을 때 이득은 어떤 것이 있나?
    • 언어 자체의 숙련도가 Kotlin에 비해 압도적으로 높다.
    • Kotlin에 비해 레퍼런스가 많다.
    • 팀 내에도 Kotlin 숙련자가 전무, 피어 리뷰 진행 시 Java 기반 코드가 훨씬 이해하기 편하다.
  • 현재 개발 일정에서 새로운 언어를 배워 사용할 수 있나?
    • 각 버전(V1, V2, V3) 별 개발 스프린트 단위가 2주이므로 불가능할 것으로 예상된다.

⇒ 사용 언어 : Java 채택

Java 17(JDK 17) vs. Java 21(JDK 21) vs. Java 25(JDK 25)

👉 동시성 모델 관점

Java 17

  • 동시성 기본 모델 : OS 스레드 + 스레드 풀
  • 고동시성 대응 방법
    • 스레드 풀 튜닝
    • 큐 기반 비동기 분리
    • 경우에 따라 WebFlux 사용
  • Devths에 대입
    • 구조적으로 문제는 없음
    • 그러나 실시간 연결 수 및 외부 API 대기가 증가하면 스레드 풀 관리 부담이 커질 가능성 존재

Java 21

  • Java 17과 비교해 동시성 모델에서의 핵심 차이점 : Virtual Thread
  • 특징
    • 스레드 수 제약이 크게 완화
    • Java 17에서 리스크가 존재했던 실시간 연결 수 및 외부 API 대기가 증가 시나리오에 비교적 유리
  • Devths에 대입
    • 동시성이 필요한 도메인(채팅/알림/외부 API 대기 등)에서 구조적 강점 적용 가능 → I/O 대기로 인한 스레드 점유 최소화를 통한 스레드 풀 고갈 완화 등
    • 기존 MVC 코드 스타일을 유지 가능

👉 비동기 작업 처리 관점

Java 17

  • 특징
    • 메시지 큐 컨슈머 기반 처리
    • 비동기 워커가 스레드 풀을 기반으로 함
    • 안정적이고 예측 가능

Java 21

  • 특징
    • 비동기 워커 로직도 Virtual Thread를 활용 가능
    • 대량 작업을 (Java 17에 비해) 스레드 수 걱정 없이 병렬 처리 가능
    • 특히 외부 API 호출이 포함된 워커나 I/O 비중이 높은 AI 파이프라인에서 이점 존재
  • Devths에 대입
    • CPU 연산 위주 작업에서는 큰 차이 없지만, 우리 서비스에서는 I/O 대기 비중이 크므로 Java 21이 유리

👉 운영 안정성 관점

Java 17

  • 특징
    • 검증 끝난 LTS(Long Term Support)
    • 운영 관련 레퍼런스 매우 풍부 → 서비스 성능 이슈 및 장애 예측 가능성 최상

Java 21

  • 특징
    • Java 17과 동일하게 LTS지만 상대적으로 최신
    • Virtual Thread 관련 레퍼런스는 아직 축적 중

🔥 중간 의사 결정

  • Java 17을 선택했을 때 서비스 내 기능 구현에 문제가 생기는가?
    • 그렇지 않다.
    • 그러나 신규 프로젝트인 점, 실시간성과 비동기성이 혼합된 요구사항이 많은 점을 비롯하여 장기적 확장성까지 고려했을 때 Java 21을 고르는 것이 더 합리적이라고 판단된다.
  • Java 21에 추가된 Virtual Thread 적용 시점은?
    • 초기는 서비스 규모가 크지 않으므로 기존 스레드 풀 방식을 사용한다.
    • 서비스의 규모가 커져 성능이 저하되거나 장애가 생긴다면 필요한 곳에 부분적으로 Virtual Thread를 적용한다.
    • Virtual Thread 관련 레퍼런스가 부족함을 고려했을 때 보수적으로 적용 범위를 넓혀가는 것이 서비스 운영 관점에서 안전할 것이라 판단된다.
  • Java 21을 선택했을 때 발생할 수 있는 이슈에는 어떤 것이 있는가?
    • Virtual Thread 사용 중 로직에서 JNI나 synchronized가 호출되면 Carrier Thread(OS Thread)가 Blocking될 수 있다. (Pinning 이슈)

👉 Java 25에서 달라진 점

주요한 변경점

  • 언어 및 컴파일러 레벨
    • 원시 타입을 패턴 매칭에 바로 사용 가능
    • import module 구문을 통해 모듈 전체를 갖고 올 수 있음
    • 클래스 선언 없이 void main() 메서드만으로 프로그램 실행 가능
    • 더 이상 super()를 첫줄에 선언하지 않아도 됨
  • API 개선 사항
    • 기존 ThreadLocal(스레드 별로 존재하는 공유 데이터)의 대안으로 ScopedValue(불변의 읽기 전용 값)가 정식 기능으로 추가
    • 관련된 여러 스레드를 단일 작업으로 처리 가능한 구조적 동시성 제공
    • 키 유도 함수(KDF)를 위한 표준 API 제공
  • 기타 변경 사항
    • 32비트(x86) 하드웨어 및 운영체제에 대한 지원 중단
    • CPU가 실제로 일하는 시간만을 측정하는 기능 추가
    • 자바 객체가 차지하는 헤더 크기 축소
    • Shenandoah GC의 장점인 짧은 Stop-The-World는 유지하면서 단점이었던 처리량 개선을 위해 Generational 방식 도입

🔥 최종 의사 결정

  • Java 21만으로는 구현하기 힘들거나, Java 25를 도입했을 때 현저히 좋아지는 부분이 있는가?
    • 기능 자체는 Java 21에서도 모두 구현 가능한 것으로 파악되었다.
    • 핵심 기능들에 대한 이득(외부 API 대기, 동시성 등)은 Java 21에서 이미 확보 가능하다.
    • 단, 운영 규모가 커질수록 Java 25의 개선점(메모리, GC 등)이 유효할 것으로 보인다.
    • 해당 부분들이 현재 서비스 예상 규모로는 필수적이지는 않다고 판단된다.
  • Java 21에서 발생하던 Virtual Thread의 Pinning 문제가 해결된 부분에서 도입의 여지는 없는가?
    • 해당 부분은 통제 가능한 비용이라고 생각했기에 MVP 및 초기 운영 단계에서는 21을 통한 안정화를 최우선순위로 두는 것이 합리적일 것이라고 판단했다.

⇒ Java 버전 : 21 채택

백엔드 프레임워크

Spring Boot vs. Django

👉 DI(Dependency Injection) 방식 관점

구분 Spring Boot Django
DI 컨테이너 있음(ApplicationContext) 없음
의존성 주입 기본 설계 철학 패턴/관례 수준
객체 생명주기 프레임워크가 관리 개발자가 관리

Spring Boot의 경우 객체 생성과 주입, 제거 등 생명 주기 전반을 프레임워크가 관리

Django의 경우 객체의 생명 주기를 개발자가 관리해야 함: DI 대신 모듈 import나 전역 설정, 함수 인자 전달 등의 방식으로 해결(암묵적 의존성)

👉 AOP(Aspect Oriented Programming) 방식 관점

구분 Spring Boot Django
AOP 개념 있음 없음
적용 방식 프록시 기반(CGLIB) Decorator / Middleware
선언적 적용 기능 매우 강력 제한적

Spring Boot에서는 프록시 객체를 통해 메서드 호출을 가로채 횡단 관심사를 핵심 로직으로부터 분리, 어노테이션 방식 사용(선언적)

Django에서는 AOP라는 개념 자체가 희미한 대신 Decorator, Middleware 등의 함수를 사용, 코드 레벨에서 비교적 명시적이고 직관적

🔥 의사 결정

  • 객체 지향 철학의 근본적 차이
    • Spring Boot : 객체 간 관계를 프레임워크가 관리한다. 횡단 관심사 또한 외부에서 주입한다. 프레임워크에서 제공해주는 DI, AOP를 개발자가 선택적으로 활용할 수 있다.
    • Django : 프레임워크가 구조를 강하게 제시한다. DI, AOP 같은 추상화를 개발자에게 굳이 노출하지 않는다. 프레임워크 레벨에서 제시한 구조를 개발자가 따르는 방식이다.
  • Devths에서는?
    • 인증, 트랜잭션, 로깅과 같은 횡단 관심사가 많다.
    • 도메인이 복잡하다.
    • Python 기반 프레임워크로 백엔드를 개발해 본 경험이 전무하다.

⇒ 사용 프레임워크 : Spring Boot 채택

Spring Boot 3.5.x vs. Spring Boot 4.0

👉 Boot 4.0에서의 변경점

  • Java 25를 1st-class로 지원(동시에 Java 17까지 호환 유지)
  • Null-safety 강화(Spring Framework 7 기반)
  • Native Image 지원 강화
  • 핵심 모듈을 더 작은 단위로 분리
  • API Versioning 지원

👉 Devths에 대입

  • Devths에서 현재 중요도가 높은 기능들(외부 API 호출, 동시성 도메인 등)은 Boot 3.5.x(+ Java 21)로도 충분히 구현 가능

🔥 의사 결정

  • 현재 초기 목표는 기능 완성 및 MVP 안정화이며, 사용 Java 버전은 21이다.
    • Boot 4.0의 대표 강점들이 Devths에 필수적인 부분은 아니라고 판단된다.
    • 즉, 4.0을 채택해도 문제는 없지만 ROI가 낮다고 판단된다.
  • 또한 Boot 4.0은 비교적 새롭게 출시된 메이저 라인이다.
    • 레퍼런스가 3.5.x보다 현저히 적을 수밖에 없다.
    • 팀 프로젝트에서 문제가 발생할 시 품질 저하 및 개발 일정 지연으로 직결될 수 있다.
  • 안정성을 위해 Boot 3.5.x로 시작하되, Deprecated 경고를 최대한 만들지 않는 등 추후 4.0 마이그레이션을 염두에 두고 개발을 진행한다.

⇒ Spring Boot 버전 : 3.5.x 채택

빌드 방식

Gradle vs. Maven

Gradle

  • build.gradle 파일에 빌드 로직을 코드로 작성
  • 증분 빌드, 빌드 캐시, 병렬 실행 등으로 빌드 속도에서 이점을 챙길 수 있음
  • 멀티 모듈에서 특히 해당 이점이 커짐
  • 테스트, 품질 관련 파이프라인을 코드로 묶기 편함

Maven

  • pom.xml 파일에 모든 것을 선언
    • 빌드 로직이 복잡해지면 유지보수가 힘들 수 있음
  • Gradle만큼 빌드 속도에서 빠르지는 않지만 동작이 비교적 단순해 이해가 편함

🔥 의사 결정

  • Devths는 기능이 계속 늘어날 가능성이 작지 않다.
  • 테스트 및 품질을 신경 써야 한다.
  • Maven의 경우 사용 경험 부재로 인한 숙련도 이슈가 존재한다.

⇒ 빌드 방식 : Gradle 채택

문서화

Swagger vs. Rest Docs

👉 Swagger

  • 어노테이션 기반으로 컨트롤러 코드에 메타데이터를 작성해두면 자동으로 문서가 생성됨
    • 별도의 테스트 없이도 API 명세 문서를 생성할 수 있음
  • 초기 세팅이 매우 쉽고 문서 작성 속도가 빠르다는 장점이 있음
  • 단, 명세를 설명하는 메타데이터는 직접 수정해야 하며 어노테이션으로 인해 프로덕션 코드가 지저분해질 수 있음

👉 Rest Docs

  • 테스트 기반으로 해당 API에 대한 테스트가 성공해야 문서가 생성됨
    • API 수정 시 테스트와 문서가 함께 깨짐
    • 명세와 실제 API가 항상 동일함을 보장할 수 있음
  • 초기 세팅이 Swagger에 비해 어렵고 문서 작성 속도가 비교적 느리다는 단점이 있음

🔥 의사 결정

  • Devths는 개발 스프린트 단위를 2주로 잡았기 때문에 애자일하고 빠르게 개발해야 하며, 진행 간 변화에 유연하게 대응할 필요가 있다.
    • 해당 관점에서 Rest Docs보다는 Swagger가 더 적절하다고 판단된다.

⇒ 문서화 도구 : Swagger 채택

인증/인가

Spring Security

👉 장점

  • 인증/인가의 표준 아키텍처를 제공
    • Filter Chain을 기반으로 요청 흐름에서 인증/인가를 일관되게 처리 가능
    • URL 또는 메서드 단위 권한 제어(SecurityFilterChain, @PreAuthorize)가 명확
  • 자체 구현의 취약점 회피 가능
  • 확장 가능 포인트가 다수 존재
    • JWT 검증을 위한 커스텀 필터, 예외 처리, 인증 성공/실패 핸들러 등 서비스 요구사항을 끼워 넣기 좋음

👉 단점

  • 설정이 많고 디버깅이 까다로움

🔥 그럼에도 불구하고 채택한 이유

  • 인증/인가의 복잡도는 회피하거나 제거할 수 없다.
    • 프레임워크 표준 아키텍처의 ‘검증된 구조’를 사용하면 복잡도로 인한 실수 가능성이 줄어든다.
  • Devths는 인증이 거의 모든 기능에 걸리는 핵심 인프라 중 하나이다.
    • 처음에 세팅이 까다롭더라도 이후 유지보수 측면의 안정성을 확보하는 것이 중요하다.
  • 인증/인가를 커스텀으로 만들면 팀 수준에서의 개발 용이성 및 유지보수성이 저하될 것으로 판단했다.

OAuth2

👉 장점

  • 사용자 경험(회원가입 및 로그인 측면)의 개선
  • 비밀번호 보관/검증 책임의 최소화
    • 서비스가 비밀번호를 직접 다루지 않거나 비중을 줄일 수 있음
  • 외부 리소스 연동의 자연스러운 기반 제공
    • OAuth2는 로그인뿐 아니라 권한 위임의 표준이기도 함

👉 단점

  • 사용자 동의, 토큰 갱신, 권한 범위의 관리까지 고려해야 함

🔥 그럼에도 불구하고 채택한 이유

  • Devths에서는 Google Calendar 같은 외부 API 연동 기능을 제공한다.
    • 사용자 ‘대신’ 캘린더에 접근할 권한을 동의 기반으로 받아야 하는데 OAuth2가 가장 표준적인 선택지라고 판단했다.
    • 이 부분을 비표준 방식으로 우회하면 추후 보안 정책이나 연동 기능 확장에서 더 큰 비용이 발생할 것으로 예상한다.

RDB

MySQL vs. PostgreSQL

👉 기능 및 아키텍처 비교

구분 MySQL PostgreSQL
ACID/트랜잭션 안정적 매우 강력
JSON 지원 JSON 타입 제공(단, 제한적) JSONB 타입 제공
확장성 상대적으로 제한적 Extension 생태계가 풍부
표준 SQL 준수 보통 높음
복제 및 샤딩 단순 구성 쉬움 구성이 유연하나 러닝 커브 존재
동시성 제어 쓰기 Lock 빈도가 높을 수 있음 MVCC를 통한 높은 동시성을 보장

👉 성능 관점

읽기 중심 CRUD라면

  • MySQL도 충분히 빠르고 튜닝 난이도가 낮음
  • 단순 조회 + 인덱스 기반 패턴에서 체감 성능이 뛰어난 편

복잡한 쿼리 및 분석성 쿼리

  • PostgreSQL이 유리
    • 다중 JOIN, 집계, JSON 필터링 등에서 강점

MySQL과 PostgreSQL의 MVCC 차이

  • MySQL : Undo Log 방식
    • 데이터 수정 시 원본 데이터는 제자리에서 덮어 쓰고, 이전 데이터는 Undo Log라는 별도의 영역에 보관
    • 읽기 작업이 들어오면 격리 수준에 따라 커밋된 데이터 또는 Undo Log에 존재하는 데이터를 보여줌
  • PostgreSQL : Copy-on-Write 방식
    • 데이터 수정 시 원본 행을 건드리지 않고 새로운 행을 추가
    • 쓰기 작업 중에도 읽기 일관성이 강력하게 보장됨
    • 단, 업데이트가 잦으면 쓸모 없는 행(Dead Tuple)이 많아져 주기적으로 정리해주는 VACUUM 프로세스가 필수적

🔥 의사 결정

  • Devths에는 외부 API 연동 데이터가 존재한다.(AI 분석 결과 등)
    • 이 데이터들은 JSON 중심이다.
    • PostgreSQL의 경우 JSONB + 인덱스로 저장 및 조회/가공이 용이하다.
    • MySQL의 경우 JSON 타입의 조회/가공 제약이 존재한다.
  • 비동기 작업 상태 테이블도 존재한다.
    • PostgreSQL의 경우 CHECK 제약 조건 + ENUM으로 상태값을 단순 문자열이 아니라 의미 있는 타입으로 취급할 수 있다.
    • 또한 여러 상태 중 자주 조회되는 상태에만 인덱스를 걸 수 있다. (부분 인덱스)
    • 즉, MySQL에 비해 개발 생산성 측면에서 뛰어나다고 판단된다.
  • Dead Tuple이 계속 생기는 PostgreSQL의 특성상 주기적인 VACUUM 처리가 필요하지만, 이러한 운영적 어려움보다 개발적인 이득이 더 클 것으로 예상한다.

⇒ RDB : PostgreSQL 채택

문서/이벤트/분석성 데이터 저장(NoSQL)

MongoDB vs. Cassandra

👉 기본 철학 차이

구분 MongoDB Cassandra
핵심 철학 개발 생산성 + 유연한 구조 초고성능 + 무한 수평 확장
데이터 모델 Document(JSON-like) Key-Value에 가까운 Wide Column
일관성 Strong / Eventual 선택 가능 Eventual Consistency 중심
설계 난이도 비교적 쉬움 어려움(쿼리부터 역설계 필요)

MongoDB

  • 데이터를 JSON 형태로 저장하며, 개발자가 생각하는 객체 구조를 그대로 DB에 반영 가능
  • 장점
    • 유연한 스키마 : AI 분석 결과물 저장에 유리
    • 강력한 쿼리 : 인덱싱 기능이 매우 뛰어나며, 복잡한 검색 및 집계 작업이 수월
    • 낮은 러닝 커브
  • 단점
    • 메모리 의존도가 높음 : 성능을 위해 메모리를 많이 소모
    • 단일 마스터 : 쓰기 작업은 마스터 노드에서만 가능하므로 쓰기 부하가 심한 환경에서는 확장이 어려움

Cassandra

  • 무중단 및 무한 확장을 설계 철학으로 내세움
  • 장점
    • 쓰기 성능 최적화 : 모든 노드가 마스터 역할을 하므로 쓰기 속도가 압도적
    • 고가용성 : 노드 몇 개가 죽어도 서비스가 중단되지 않음
    • 무한 확장 : 서버를 추가하는 것만으로 용량과 성능이 늘어남
  • 단점
    • 쿼리 제한 : JOIN 불가
    • 설계 난이도 : 데이터를 어떻게 읽을지 미리 결정하고 테이블을 설계해야 함

👉 Devths에 도입하는 경우

  • Devths는 AI 결과 및 비정형 데이터를 저장할 때 유리
  • Cassandra는 대규모 서비스에 적합
    • 로그가 초당 수만 건씩 쌓이는 경우
    • 수십만 명이 알림과 채팅을 쏟아내는 경우

🔥 의사 결정

  • Devths에는 채팅 메시지, AI 분석 결과 등의 데이터가 존재한다.
    • MongoDB가 갖는 특징인 유연한 스키마, 강력한 쿼리 등의 기능으로 미루어 보았을 때 우리 서비스에는 Cassandra보다 MongoDB가 더 적합하다고 판단된다.
  • 단, MVP나 서비스 초기 단계부터 도입하는 것은 부적절하다고 판단했다.
    • 운영 비용 측면에서 관리 포인트가 더 늘어나 좋지 않고, 개발 복잡도 또한 늘어나기 때문이다.
    • 초기에는 PostgreSQL의 JSONB 타입만으로도 충분히 MongoDB 대체가 가능할 것으로 예상한다.
    • 모든 데이터를 하나의 DB에서 관리해 개발 속도와 데이터 정합성, 서비스 안정성을 확보하는 것이 최우선이라고 판단했다.

⇒ NoSQL : MongoDB 채택(단, NoSQL 도입 필요 시)

캐시/토큰/세션성 데이터 저장

Redis vs. Memcached

👉 핵심 차이점 비교

구분 Redis Memcached
역할 철학 인메모리 저장소 분산 키-값 캐시
지원 데이터 타입 문자열, 리스트, 셋 등 다양함 오직 문자열만 지원
지속성(Persistence) 스냅샷 등 파일 저장 가능 재시작 시 모든 데이터 휘발
스레드 싱글 스레드 멀티 스레드 (멀티 코어 활용 효율 높음)
복제 및 고가용성 자체 지원 (Master-Slave, Cluster, Sentinel) 자체 지원 안 함 (별도 구현 필요)
주요 기능 Pub/Sub, 트랜잭션, 루아 스크립트 등 단순 캐싱 최적화

👉 Devths에 도입하는 경우

  • Memcached는 단순 캐시용으로만 적합
    • 순수 캐시 성능으로만 보면 오버헤드가 적은 Memcached가 더 빠를 수 있음
  • Devths에는 캐시 뿐만 아니라 추가적인 기능이 필요

🔥 의사 결정

  • Devths는 단순 캐시 사용 이외에도 실시간 처리, 인증/인가 과정에서의 사용자 RT 저장 등이 필요하다.
    • 서비스 내의 다양한 데이터 구조 활용
    • Refresh Token 저장의 지속성 및 만료 정책 실행
    • 실시간 기능 구현
    • 비동기 처리
    • 등을 위한 전략적 보조 인프라로서 Redis가 적합하다고 판단했다.

⇒ 캐시/토큰/세션성 데이터 저장소 : Redis 채택

실시간 통신

Socket.IO vs. WebSocket(STOMP) vs. SSE(Server Sent Event)

👉 핵심 차이점 비교 : WebSocket vs. SSE

구분 WebSocket(STOMP) SSE
통신 방향 양방향 단방향(서버 → 클라이언트)
연결 방식 지속 연결 HTTP 스트림
재연결 처리 직접 구현 필요 브라우저 레벨에서 자동 재연결
메시지 순서 보장(구현에 따라 다를 수 있음) 기본적으로 보장
서버 구현 상대적으로 복잡 단순

기본적으로 WebSocket은 실시간 ‘양방향’ 통신, SSE는 실시간 ‘단방향’ 통신에 사용

  • WebSocket
    • 세션 관리 및 연결 끊김 처리 구현이 필요
    • Scale-Out 시 Redis 등의 브로커 필요
  • SSE
    • 단순 HTTP 스트림
    • 연결이 끊겼을 때 브라우저에서 자동 재연결을 지원하므로 비교적 부담이 적음
    • HTTP 기반이라 로드밸런싱이 용이함

👉 Devths에 대입

  • Devths에는 실시간 양방향 통신과 실시간 단방향 통신 모두가 존재
    • 사용자 간 실시간 채팅(양방향), 비동기 작업 알림 및 AI 챗봇 응답(단방향)

🔥 중간 의사 결정

  • WebSocket과 SSE 둘 중 하나만 사용해서 우리 서비스의 실시간 통신 기능을 구현할 수 있는가?
    • WebSocket을 사용한다면 가능하다.
    • 그러나 사용자 간 채팅을 제외하고는 실시간 상호작용 UX가 존재하지 않거나 거의 중요하지 않다.
    • 즉, 알림 및 AI 챗봇 응답과 실시간 채팅의 성격이 서로 다르다.
  • 메시지를 계속 주고받는 경우처럼 상호 작용이 많은 게 아닌 이상, SSE가 더 가볍고 합리적이다.
    • WebSocket 방식에서는 서버가 각 연결에 대해 read, write 상태를 들고 있어야 하는 등 리소스가 크다.
  • 모든 실시간 통신을 WebSocket으로 구현하는 것보다는, 필요에 따라 WebSocket과 SSE를 적절히 사용하는 것이 효율적일 것이라 판단된다.
  • 그렇다면 실시간 양방향 통신 구현에 WebSocket 이외에 다른 방식은 없을까?
    • Socket.IO도 해당 기능 구현에 자주 사용된다.

👉 핵심 차이점 비교 : WebSocket(STOMP) vs. Socket.IO

구분 WebSocket(STOMP) Socket.IO
표준 여부 RFC + STOMP Spec 비표준
상호운용성 높음(언어/플랫폼에 독립적) 낮음
클라이언트 다양성 높음 낮음(주로 JS)

WebSocket(STOMP)

  • WebSocket은 전송 계층
  • STOMP는 그 위의 메시징 프로토콜
  • 명시적이고 표준화되어 있음
  • Client - WebSocket - STOMP - Server 구조
  • 연결 관리, 재연결 및 에러 처리 등을 직접 설계해야 하므로 상대적으로 복잡함
  • Scale-Out 시 브로커가 필수지만 Redis, RabbitMQ와 같은 브로커와 연계가 자연스러움

Socket.IO

  • WebSocket을 내부 구현 중 하나로 사용함
  • 자체 추상화된 이벤트 기반 프로토콜
  • 표준이 아님
  • Client - Socket.IO - Server 구조
  • 프론트엔드 개발에서 흔히 사용되는 이벤트 모델을 거의 그대로 사용(socket.on(), socket.emit())하기 때문에 JS 기반 프론트엔드 개발자에게 직관적임
  • 재연결, heartbeat, fallback이 자동 → 구현 난이도가 비교적 낮음
  • Scale-Out 시 내부 동작에 대한 이해가 필요함

👉 Devths에 대입

  • Devths는 Java + Spring Boot 기반의 백엔드 서버를 사용
  • Redis도 도입 예정

🔥 최종 의사 결정

  • Socket.IO를 도입했을 때 장점이 단점을 뛰어 넘을 수 있는가?
    • WebSocket(STOMP)에 비해 빠른 MVP 개발이 가능해진다.
    • 그러나 Socket.IO는 JS 기반의 백엔드 서버에 적합하며, 추후 서버 확장(Scale-Out) 시 까다롭다는 단점이 있다.
    • 실시간 양방향 통신을 도입하는 가장 큰 이유인 사용자 간 채팅은 우리 서비스의 핵심 기능 중 하나이다.
    • 이러한 이유로 MVP 개발을 빠르게 하는 것도 중요하지만, 안정성과 확장성을 고려했을 때 WebSocket(STOMP)를 도입하는 것이 옳다고 판단했다.

⇒ 실시간 통신 : SSE(단방향) + WebSocket(양방향) 채택

비동기/이벤트(메시지 큐)

Kafka vs. RabbitMQ vs. AWS SQS

👉 핵심 차이점 비교

구분 Kafka RabbitMQ Amazon SQS
메시지 성격 이벤트 로그 작업(Job) 작업(Job)
메시지 보존 여부 O X O
재처리 기능 강력 제한적 제한적
순서 보장 파티션 단위 큐 단위 FIFO 큐 사용시 보장

Kafka

  • 메시지를 로그처럼 저장함
  • 높은 처리량
  • 파티션 기반 선형 확장 가능
  • 초기 구축 및 모니터링, 장애 대응 등 운영적인 측면에서 큰 비용(어려움) 발생 가능

RabbitMQ

  • 브로커의 성격이 강함
    • 메시지가 소비되면 저장되지 않고 사라짐
  • 처리량이 Kafka에 비해 낮은 대신 짧은 지연 시간을 가짐

Amazon SQS

  • 확장성을 AWS에 위임
    • 운영 측면에서 부담을 최소화할 수 있음
  • 내부 처리 방식이 블랙박스에 가까움
    • 큐 내부를 숨기고 대략적인 처리 성공/실패 횟수만 알 수 있음
    • 큐 레벨에서의 튜닝이 불가능하므로 성능 개선 수단이 Consumer 쪽으로 한정됨

👉 Devths에 대입

  • SQS는 섬세한 성능 튜닝이 힘들다는 점이 치명적이므로 고려 대상에서 제외
  • Kafka와 RabbitMQ의 근본적인 차이
    • Kafka = 이벤트 로그를 ‘기록’해둠
    • RabbitMQ = 작업을 처리하고 나면 해당 작업은 기록되지 않고 사라짐
  • 비동기 처리의 본질은 이벤트가 아닌 작업
    • ‘이 일을 누가 처리하는가’가 핵심임
    • 해당 부분에서는 RabbitMQ가 설계 자체가 최적화되어 있음
  • 채팅/알림 서비스에서는 소비자가 늘어날 가능성이 큼
    • Kafka에서는 같은 이벤트를 여러 소비자가 각자 읽는 구조가 기본값
    • 알림 센터 적재, 푸시 발송, 누락 알림 재전송 등
    • 특히 누락된 이벤트 재처리(이벤트 유실 이슈 해결)가 구조적으로 가능

🔥 최종 의사 결정

  • Devths에서 채팅 및 알림 기능은 시간이 갈수록 후속 소비자가 늘어난다.
    • 장애/버그/정책 변경 시 누락 복구에 대한 요구가 자주 생길 것으로 예측된다.
    • Kafka는 “이벤트 원장”이 가능하므로 이런 확장에 구조적으로 강하다고 판단했다.
  • 비동기 작업 처리는 RDB 기반 Tasks 테이블 + 폴링 워커로 대체 가능할 것으로 판단된다.
    • 작업량 폭증 시 Kafka를 작업 큐처럼 활용

⇒ 메시지 큐 : Kafka 채택

푸시 알림

FCM vs. Web Push

👉 기술적 성격 차이

FCM(Firebase Cloud Messaging)

  • Google이 제공하는 푸시 알림 인프라
  • Android / iOS / Web 통합 지원
  • 서버는 토큰 기반으로 푸시 요청만 전송
  • 즉, 푸시를 서비스의 한 기능으로 사용하는 관점

Web Push

  • 브라우저 표준 API
  • Service Worker 필수
  • 브라우저마다 제약이 상이함
  • Web만 지원(iOS의 경우 지원은 하나 제약이 많음)
  • 푸시를 웹 기술로 구현하는 관점

👉 구현 및 운영 난이도 차이

FCM

  • 서버는 단순히 HTTP API를 호출
  • 토큰 관리(디바이스와 유저를 매핑) 필요
  • 운영 난이도가 상대적으로 낮음

Web Push

  • Service Worker 구현이 필요
  • VAPID 키 관리 필요
  • 브라우저별 이슈 대응 필요
  • 디버깅 난이도가 높음
  • 프론트엔드/백엔드 협업 비용이 큼

👉 Devths에 대입

  • Kafka를 도입하게 된다면 확장성이 좋음
    • 알림 센터 적재 실패 시, 푸시 알림 누락 시 등 실패 케이스에 대한 재시도 로직

🔥 최종 의사 결정

  • 현재 Devths에서 푸시 알림은 핵심 기능의 보조 수단이다.
    • AI 기능 비동기 처리 완료, 채팅 알림 등
  • 추후 모바일 앱 확장 가능성과 알림 전달의 신뢰성, 도입 가능성 높은 Kafka와의 연계를 고려하여 FCM을 주요 푸시 채널로 선택하는 것이 옳다고 판단된다.

⇒ 푸시 알림 : FCM(Firebase Cloud Messaging) 채택

외부 API 연동

WebClient vs. RestClient

👉 핵심 차이점 비교

RestClient

  • Spring에서 외부 API 동기 호출을 위한 최신 클라이언트
  • 호출 스레드가 응답을 기다리는 동기/블로킹 모델
  • Spring MVC 기반 서비스에서 일반적인 외부 API 호출에 가장 자연스러움
  • 비동기 처리가 필요한 경우 @Async, 큐 기반 비동기(Kafka, RabbitMQ 등), 스케줄러/워커 등을 통해 호출 자체를 백그라운드 작업으로 바꾸는 방식을 사용
  • 동기 코드 스타일이 유지되므로 러닝 커브가 낮음

WebClient

  • 논블로킹/리액티브 모델
  • 스트리밍 응답(SSE)과 비동기 조합에 강함
  • Reactive 타입(mono/flux)와 에러 처리/타임아웃/리트라이 패턴을 익혀야 함
  • WebClient를 동기처럼 사용(.block())하는 것도 가능하지만 논블로킹의 이점을 잃고 설정만 복잡해지는 경우가 많음

👉 Devths에 대입

  • 비동기 처리가 핵심인 AI 기능들은 다음과 같이 나뉨
    • OCR 후 리포트 분석
    • 첨부파일 마스킹
    • AI 챗봇 응답(스트리밍)

🔥 최종 의사 결정

  • OCR + 리포트 분석과 첨부파일 마스킹은 RestClient + Job Queue 패턴을 선택하는 것이 합리적이라고 판단된다.
    • 이 작업들은 처리 시간이 상대적으로 길고 실패 시 재시도가 필요하다.
    • 그러므로 비동기를 작업 실행 흐름(큐 + 워커)으로 구현하는 것이 적절하다고 판단했다.
    • 다만 MVP 및 서비스 초기 단계에서는 Job Queue를 RDB 기반 Tasks table로 대체한다.
  • AI 챗봇 응답은 실시간 스트리밍으로 제공되니 WebClient를 선택하는 것이 합리적이라고 판단된다.
    • UX 관점에서도 사용자가 질문했을 때 바로 토큰이 조금씩 출력되는 것을 기대할 것이다.
    • 해당 작업에 Job Queue 패턴을 적용하게 되면 UX가 깨진다.

⇒ 외부 API 연동 : RestClient(OCR+리포트 분석 및 첨부파일 마스킹) + WebClient(AI 챗봇 스트리밍)

⚠️ **GitHub.com Fallback** ⚠️