백엔드 개발자 류승엽

병목을 구조적으로 분석하고 성능·안정성·운영 비용을 고려해 시스템을 설계합니다.
Go, Rust, Java의 특성과 트레이드오프를 이해하고, 문제 상황에 맞는 기술을 선택합니다.
💻 기술 스택
Backend
  • Java (Spring Boot) — 대규모 서비스 또는 장기 유지보수가 요구되는 환경
  • Go (Fiber) — 빠른 개발 속도와 동시성이 필요한 API 서버
  • Rust (Axum, Actix) — 메모리 안정성과 예측 가능한 레이턴시가 요구되는 고성능 서버
Infra
  • AWS
  • Docker, Terraform, Nginx
Database
  • MySQL, PostgreSQL, Redis
Queue
  • Kafka
📁 프로젝트

Crypto Stream (개인, 2026.05 ~ 2026.06)

Rust와 Kafka로 구축된 실시간 암호화폐 가격 스트리밍 API

Kafka 기반 서비스 분리와 SSE 팬아웃 구조 개선으로 평균 지연시간을 11.9초 → 28ms로 단축하고, 장애 발생 시 4초 내 자동 복구되는 구조를 구축했습니다.
Rust Actix Docker Kafka
백엔드 (Rust + Actix)
  • 서비스 간 Kafka 토픽을 유일한 통신 수단으로 강제 — Binance 장애가 SSE 클라이언트까지 전파되지 않는 장애 격리 구조 설계
  • symbol 파티션 키로 동일 코인 이벤트를 단일 파티션에 수렴 — 분산 환경에서 체결 순서 보장
  • sliding window 기반 급등락 감지를 별도 alerting 서비스로 분리 — 감지 로직이 스트리밍 레이턴시에 영향을 주지 않는 구조
  • 모든 서비스를 Docker Compose로 통합해 Kafka, topic 초기화, Rust 서비스 3개 빌드 및 실행 단순화
🔍 문제 인식

1. Binance tick 데이터를 직접 SSE로 연결하면 Binance 장애 시 클라이언트도 끊김

  • Binance WebSocket 수신과 SSE 응답이 직접 연결되면, Binance 연결 지연·재연결·장애가 그대로 클라이언트 응답 중단으로 전파
  • 수집 로직과 클라이언트 전달 경로가 결합되어 collector 장애 시 SSE 스트리밍 안정성도 함께 흔들림

2. 다수 SSE 클라이언트에 tick을 동시 전달할 때 브로드캐스트 구조 없이는 N번 복사 발생

  • 클라이언트마다 개별 consumer나 처리 루프를 두면 동일 tick을 클라이언트 수만큼 반복 역직렬화·전송
  • SSE 클라이언트가 늘어날수록 tick 처리 비용이 클라이언트 수에 비례해 증가

3. 급등락 감지 로직이 스트리밍 경로에 포함되면 레이턴시 오염

  • tick 스트리밍 경로 안에서 100틱 윈도우 계산과 급등락 판단을 함께 수행하면 /ticks 응답 latency에 탐지 비용이 직접 반영
  • alert 판단이 느려지거나 복잡해질수록 실시간 tick 전달 성능도 함께 저하
⚙️ 해결 과정

1. Kafka 기반 수집/전달 경로 분리

  • collector는 Binance tick을 Kafka price-tick 토픽에 publish하는 역할만 담당
  • processor는 Kafka에서 tick을 consume해 SSE로 전달하므로, 수집 장애와 클라이언트 fanout 경로를 분리
  • collector 장애 시에도 processor/SSE 서버는 유지되며, Compose restart 정책으로 collector를 자동 복구

2. Broadcast channel 기반 fanout

  • processor가 Kafka tick을 한 번만 consume한 뒤 내부 broadcast 채널로 여러 SSE 클라이언트에 전달
  • 동일 tick의 중복 소비·중복 처리 비용을 줄이고, 50개 클라이언트 동시 연결에서도 단일 fanout 경로로 처리

3. Alerting 서비스 비동기 분리

  • alerting-service를 별도 Kafka consumer로 분리해 price-tick을 독립적으로 consume
  • 급등락 감지 결과만 price-alert 토픽에 publish하고, processor는 /ticks와 /alerts를 각각 SSE로 fanout
  • tick 전달 경로는 단순 스트리밍에 집중하고, 탐지 로직은 별도 파이프라인에서 비동기 처리
🎯 개선 포인트

Rust bench-tools 기준 — SSE 50 clients/30s, throughput 60s, latency 1,000 samples, collector kill 복구 시간 측정

Metric Before After Improvement
SSE TPS (50 clients) 3,161.61 events/s 4,812.09 events/s +52.2%
Tick TPS 26.27 ticks/s 107.82 ticks/s +310.5%
Avg Latency 11,923.25ms 28.89ms -99.8%
P95 Latency 15,320ms 1,333ms -91.3%
Recovery Time 4,186ms

단순 성능 최적화보다 장애 격리와 순서 보장을 우선한 설계로 운영 안정성을 확보했습니다.


Pulse (개인, 2026.02 ~ 2026.05)

등록된 API 엔드포인트에 주기적으로 HTTP 요청을 보내고 결과를 기록하는 헬스체크 서비스

DynamoDB 접근 패턴과 스케줄링 구조를 재설계하여 읽기 비용을 API 개수와 무관한 구조로 전환하고, DB 쓰기 횟수를 최대 33% 감소시켰습니다.
Java Spring EC2 DynamoDB SNS S3 CloudFront CloudWatch Terraform
백엔드 (Spring Boot + AWS)
  • 타임아웃·재시도를 포함한 논블로킹 HTTP 헬스체크 스케줄러 설계
  • DynamoDB 기반 모니터링 API 및 체크 이력 도메인 설계
  • 연속 실패 임계값 초과 시 SNS 이메일 알림, 쿨다운으로 알림 중복 발송 방지
인프라 (Terraform + AWS)
  • Terraform으로 전체 인프라 프로비저닝 자동화 — 수동 설정 오류 제거 및 환경 재현성 확보
  • OAC(SigV4) 기반으로 S3 퍼블릭 접근 차단, CloudFront에서만 버킷 접근 허용
  • 단일 CloudFront 배포에서 /apis/* → EC2(캐싱 없음), 정적 파일 → S3(캐싱) 경로 분리
  • S3 sync + CloudFront invalidation 자동화 배포 스크립트 구성
🔍 문제 인식

1. 모니터링 주기마다 DynamoDB 전체 스캔

  • 매 주기마다 테이블 전체를 읽어 메모리에 올린 뒤 애플리케이션에서 필터링
  • 실제 체크 대상이 소수여도 읽기 비용은 테이블 전체 크기에 비례해 RCU를 불필요하게 소진

2. 체크 1회에 DB 쓰기가 2~3회로 분산 발생

  • 체크 결과 저장, 연속 실패 횟수 갱신, 다음 실행 시각 갱신이 각각 별도 네트워크 호출로 실행
  • API 50개 기준 한 사이클에 최대 150회 쓰기가 동일 파티션에 집중되어 핫 파티션 유발

3. 이력 전체를 읽어온 뒤 애플리케이션에서 정렬 후 제한 적용

  • 체크 이력 조회 시 전체 결과를 DynamoDB에서 읽어온 뒤 정렬·제한을 애플리케이션에서 처리
  • TTL 30일 기준 최대 43,200개를 전량 읽는 구조로, 이력이 쌓일수록 응답 지연 증가

4. 단일 필드 갱신을 위한 불필요한 Read-then-Write

  • 알림 발송 시각 갱신 시 전체 아이템을 먼저 읽고 수정 후 다시 쓰는 방식으로 네트워크 호출 2회 발생
  • 동시 수정 경합 시 오래된 스냅샷이 최신 값을 덮어쓰는 일관성 문제 발생

5. 모든 API HTTP 요청이 동시에 시작, 블로킹 작업이 I/O 스레드를 점유

  • 주기적 스케줄링 방식에서 전체 API 요청이 한 번에 시작되어 커넥션 풀 한도 초과 위험
  • HTTP 응답 후 실행되는 DB 저장·알림 등 블로킹 작업이 I/O 처리 스레드를 점유해 다른 요청 처리 지연
⚙️ 해결 과정

1. 주기적 전체 Scan 제거, API별 독립 스케줄링 도입

  • 앱 시작 시 1회만 전체 조회 후 각 API를 인메모리에서 독립적으로 스케줄링
  • 체크 완료 후 자신의 인터벌 기준으로만 다음 실행을 재등록, 이후 추가 DB 조회 없음
  • CRUD 발생 시 DB 저장과 동시에 인메모리 스케줄도 즉시 반영

2. 다중 DB 쓰기를 단일 호출로 병합

  • 체크 후 갱신 필드를 인메모리에서 먼저 병합 후 DB 저장 단일 호출 — 분산 쓰기를 1회로 통합
  • 다음 실행 스케줄 등록도 인메모리 상태를 참조하므로 추가 DB 조회 불필요

3. 정렬, 제한을 DB Query 단계로 이관

  • 소트키가 ISO 8601 형식이라 사전순 = 시간순이 보장되는 특성을 활용
  • 내림차순 정렬과 조회 건수 제한을 DynamoDB Query 단계에서 처리, 애플리케이션의 전체 로드 후 정렬 방식 제거

4. 알림 상태 갱신을 기존 저장 호출에 통합, Read-then-Write 제거

  • 알림 발송 시각을 인메모리 상태에 반영 후 기존 저장 호출에 통합
  • 전용 갱신 메서드 삭제로 별도 DB 호출 0회 추가, 스냅샷 덮어쓰기 문제 구조적으로 제거

5. 블로킹 작업을 I/O 스레드에서 분리

  • API별 독립 스케줄링으로 동시 요청 폭주 방지
  • 블로킹 작업을 전용 스레드 풀로 분리해 I/O 스레드 점유 제거 — 응답 처리와 후처리 독립 확장
🎯 개선 포인트

O(n) Scan → O(1) per check 구조 전환 — API 수가 늘어도 읽기 비용이 증가하지 않음

Metric Before After Improvement
DB 쓰기 / 체크 1회 2회 1회 -50%
한 사이클 최대 쓰기 (API 50개) 150회 100회 -33%
이력 조회 행 수 최대 43,200개 요청 수만큼 -99.9%
알림 경로 추가 DB 호출 2회 0회 기존 저장에 흡수

비용 절감보다 시스템 규모가 커져도 같은 방식으로 동작하는 구조를 만드는 데 집중했습니다.


GitRank (개인, 2025.06 ~ 2025.07)

GitHub Organization 내 멤버 랭킹을 조회하는 API

외부 API 의존성을 요청 경로에서 제거하여 평균 응답 시간을 40초 → 1.65ms로 단축하고, GitHub Rate Limit 영향을 받지 않는 구조를 구축했습니다.
Go Fiber Redis
백엔드 (Go + Fiber)
  • 외부 API 응답 지연을 요청 경로에서 격리하기 위해 API 서버와 Worker를 이중 프로세스로 분리 설계
  • GitHub GraphQL API로 조직 멤버 기여도 수집 — REST 대비 필요한 필드만 선택적으로 조회
  • gRPC 통신 도입 후 오버엔지니어링으로 판단하여 Redis Pub/Sub으로 대체 (구조 단순화)
  • Redis 기반 SWR 캐싱 전략 적용 — 캐시 히트 시 즉시 응답 후 백그라운드 갱신, 분산 락으로 중복 갱신 요청 방지
  • 별도 엔드포인트 없이 기존 Redis 연결로 Worker 생존 여부 확인
  • Worker Redis 재연결 버그 수정 — 점진적 백오프·타임아웃 컨텍스트 적용으로 무한 루프 및 리소스 누수 방지
🔍 문제 인식

1. 외부 API 호출이 요청 처리 경로에 직접 포함

  • 사용자 요청마다 GitHub GraphQL API를 직접 호출해 조직 멤버 기여도 데이터를 수집
  • 외부 API 응답 지연이 그대로 사용자 응답 시간에 반영, 평균 응답 시간 수십 초
  • GitHub API rate limit 초과 시 서비스 전체가 응답 불가 상태로 전환
⚙️ 해결 과정

1. 요청 경로에서 외부 API 호출 제거

  • 요청 경로에서 GitHub API 직접 호출 제거 — 사전 수집 캐시 데이터만 반환하도록 구조 전환
  • 응답 지연의 근본 원인인 외부 의존성을 요청 경로에서 분리

2. 데이터 수집과 요청 처리를 독립적인 워커 구조로 분리

  • 별도 워커 프로세스가 주기적으로 GitHub API를 호출해 데이터를 수집하고 Redis에 저장
  • 수집과 서빙의 책임을 분리해 장애 격리 구조 확보

3. SWR(Stale-While-Revalidate) 전략으로 응답 지연과 데이터 최신성 동시 해결

  • 캐시 만료 시에도 즉시 응답 후 백그라운드 갱신 — 응답 지연 없이 데이터 최신성 동시 확보
  • 동시 다중 갱신 요청이 발생하면 분산 락으로 결합해 중복 수집 방지
🎯 개선 포인트

캐시를 추가한 것이 아니라, 외부 의존성을 시스템 경계 밖으로 밀어낸 구조 개선 사례입니다.

k6 부하 테스트 기준 — Cached: 500 VU / 4m 30s, No Cache: 5 VU / GitHub rate limit 제약

Metric Cached No Cache Improvement
Avg Latency 1.65ms 40.09s seconds → milliseconds
P95 Latency 4.39ms 43.33s seconds → milliseconds
Throughput 5,740 req/s — ¹
Total Requests 1,549,907 8

캐시 적중 시 외부 API 호출 없이 응답 처리 — rate limit 장애 위험 구조적으로 제거
¹ No Cache 처리량은 GitHub API rate limit(5,000 points/h)에 의해 결정되며 서버 처리 성능과 무관


Qiri (팀 4인, 2025.04 ~ 2025.09)

손 제스처로 AI와 소통하는 애플워치용 경량 챗봇 서비스

한국어 질의 정규화를 통해 캐시 히트율을 6배 향상시키고, 평균 응답 시간을 55% 단축했습니다.
Rust Axum Docker EC2 Elasticache nginx MySQL Redis Tailscale
백엔드 전담 (Rust + Axum)
  • EC2 메인 서버와 온프레미스 LLM 서버를 분리 운영, Tailscale 사설 네트워크로 외부 노출 없는 내부 통신 환경 구축
  • LLM 응답 버퍼링으로 캐시 일관성 보장 — 스트리밍 대신 전체 응답 수신 후 저장 방식 채택
  • 한국어 조사 정규화로 동일 의미 쿼리를 단일 캐시 키로 수렴 — LLM 호출 비용 절감 및 히트율 6배 향상
  • Sign in with Apple OAuth 인증 구현 — Apple 공개 키 동적 조회·JWT 서명 검증으로 토큰 위조 차단
🔍 문제 인식

1. 사용자 표현 차이로 동일 의미 요청이 서로 다른 캐시 키로 분기

  • "이거 뭐야"와 "이거는 뭐야?"처럼 의미는 같지만 조사·어미 차이로 캐시 미스 발생
  • 캐시 히트율 저하로 LLM 호출 비율 증가, LLM 응답 지연이 그대로 사용자 응답 시간에 반영
  • 캐시 구조는 올바르나 키 수렴 전략 부재로 히트율 저하
⚙️ 해결 과정

1. 조사 정규화로 동일 의미 요청을 단일 캐시 키로 수렴

  • 의미에 영향을 주지 않는 한국어 조사(은/는/이/가/을/를/의/에 등) 47개를 토큰 단위로 제거
  • 긴 조사부터 순서대로 검사해 부분 치환 오류 방지 (예: "에서는"을 먼저 처리해야 "에서"가 남지 않음)
  • 정규화된 문자열을 해시화해 캐시 키로 사용 — 문자열 직접 저장 대비 메모리 효율 확보

2. LLM 응답 버퍼링으로 캐시 일관성 보장

  • LLM 서버의 스트리밍 응답을 캐시하려면 전체 응답이 완료된 뒤 저장해야 함
  • 응답 전체를 메모리에 버퍼링 후 반환하는 방식 채택 (스트리밍 대신 캐시 일관성 우선)
  • 초기 응답 지연 소폭 증가 감수 — 캐시 히트 시 LLM 호출 완전 생략으로 전체 응답 시간 대폭 단축

3. 락 프리 인메모리 캐시로 동시 요청 처리

  • 다중 요청 환경에서 캐시 읽기·쓰기 경합을 없애기 위해 락 프리 동시성 해시맵(DashMap) 사용
  • Arc로 핸들러 간 캐시 공유 — 별도 동기화 없이 스레드 안전성 확보

4. 정규화 효과를 2단계 부하 테스트로 검증

  • 캐시 웜업 후 조사 변형 쿼리로 히트율을 검증하는 k6 시나리오 작성
  • 정규화 전후 캐시 히트율·응답 지연을 수치로 측정해 적용 범위 확정
🎯 개선 포인트

모델을 바꾸지 않고도 시스템 설계만으로 사용자 체감 성능을 개선했습니다.

k6 부하 테스트 기준 — 정규화 전/후 각 10 VU / 100 iterations, 조사 변형 쿼리 20종

Metric Before After Improvement
Cache Hit Rate ~10% ~60% +500%
Avg Latency ~36s ~16s -55%
Cache Hit Latency (p95) ~162ms ~112ms -31%
Cache Miss Latency ~40s ~40s 거의 동일

캐시 미스 지연은 개선 전후 거의 동일 — 정규화가 기존 요청 품질에 영향 없이 히트율만 높임

📁 토이 프로젝트
  • astron — 로켓을 컨셉으로 만든 인터프리터 프로그래밍 언어 Rust
    • 토크나이저, 파서, AST, 실행기 구조를 직접 설계하며 언어 구현 원리를 학습
  • cavira — 프로세스 실행 분석 CLI 도구 Rust
    • 프로세스 실행 과정을 추적, 분석하는 CLI 도구로, 리눅스 프로세스 모델에 대한 이해를 목적으로 개발
🤝 오픈소스 기여
RabbiTTY — Rust 기반 크로스플랫폼 터미널 에뮬레이터  |  PR #1, #5, #8
  • Windows 셸(cmd, PowerShell) 탭 지원 기능 추가
  • 조건부 컴파일 적용으로 실행 환경에 따른 명령 셸 분리 — 크로스 플랫폼 빌드 호환성 확보
spectra-log — Node.js 터미널 로깅 라이브러리  |  PR #1, #2
  • CommonJS → ESM 전환 리팩토링 주도
  • README 코드 예제 및 사용법 문서 전면 업데이트
  • 실제 사용자의 마이그레이션 비용까지 고려하며 변경 작업 진행
👣 커리어
  • 2026.03정보처리산업기사 취득
  • 2026.01주식회사 스위치 인턴십
  • 2026.012학기 캡스톤 프로젝트 동상
  • 2025.12교내 전공 컨퍼런스 연사 — '초보 개발자를 망치는 잘못된 공부 방법'
  • 2025.11프로그래밍기능사 취득
  • 2025.09제 60회 전국기능경기대회 정보기술 종목 장려상
  • 2025.071학기 캡스톤 프로젝트 대상
  • 2025.03교내 웹 개발 동아리 WINE 부장
  • 2024.12제 4회 경상북도 SW-AI 인재양성 프로젝트 우수상
  • 2024.07교내 알고리즘 동아리 COMMENT 차장
구조가 바뀌면 문제가 사라진다고 믿습니다.
이 포트폴리오에 담긴 모든 개선은 그 믿음을 검증한 기록입니다.