김상현
BACKEND · INFRASTRUCTURE

KIMSANGHYEON

백엔드와 인프라에 관심이 있는 개발자 김상현입니다.
서비스를 직접 만들고 운영하며, 측정한 결과를 바탕으로 개선합니다.

성균관대학교 소프트웨어학과
github.com/snghyn28 ↗

KivoStory

2026.10–현재 · 개인 프로젝트

블루 아카이브 팬들이 이야기를 만들고 공유하는 웹 서비스.
스토리 편집기부터 API 개발, 배포와 운영까지 맡았습니다.

서비스 ↗
Next.jsSpring BootMySQLKubernetesCloudflare · R2
서비스 구조
KivoStory 서비스 구조브라우저는 Cloudflare를 거쳐 R2 정적 파일과 Kubernetes의 Next.js 및 Spring Boot API에 접근합니다. 좋아요 워커도 홈서버 Kubernetes 안에서 실행되며, API와 함께 MySQL을 사용합니다.홈서버 · Kubernetes브라우저CloudflareCDN · TunnelR2JS · CSS · 미디어Next.jsSSG · ISRSpring BootAPI · 인증MySQL좋아요 워커이벤트 처리 · 순위 집계HTMLAPI정적 파일
CI/CD
  1. GitHub Actions테스트 · 이미지 빌드
  2. GHCR이미지 발행
  3. GitOps 저장소이미지 digest 갱신
  4. Argo CD배포 상태 동기화
  5. Kubernetes롤링 업데이트
01 · 백엔드

SQL 개선에서 자원 설정까지

백엔드 · 500 RPS 테스트

337→61ms
p95 응답시간
  1. 01

    연결 대기에서 병목 발견

    500 RPS 목표 · 455 RPS / p95 337 ms

    200 RPS에서는 안정적이었지만 부하를 높이자 DB 연결 대기가 늘었습니다. 풀을 키우기 전에 목록 조회와 COUNT 비용부터 나눠 측정했습니다.

  2. 02

    COUNT 제거 + ngram, 오히려 회귀

    p95 337 → 453 ms

    전체 COUNT를 없애고, 현재 페이지보다 더 조회해 남은 페이지 수를 API 응답에 담았습니다. 프론트엔드는 이 값(0~4페이지 또는 5페이지 이상)으로 페이지네이션 UI를 표시했습니다. ngram은 희소 검색에 빨랐지만, 흔한 검색어의 후보 수집·정렬 비용이 컸습니다.

  3. 03

    필요한 결과를 찾으면 멈추도록

    p95 61 ms · 499.5 RPS

    ngram 인덱스를 철회하고, 최신순·인기순 인덱스 순서로 읽다가 필요한 결과를 찾으면 종료하도록 바꿨습니다. ID·상세 분리도 비교했지만 이점이 작아 단일 SQL로 단순화했습니다.

  4. 04

    풀·CPU 증설, 처리량과 지연을 함께 비교

    최종 선택 · 풀 8 / CPU 2코어

    최대 814 RPS를 관측했지만 노드 CPU가 97%에 도달하고 p95도 악화됐습니다. 처리량보다 공유 CPU 여유와 지연을 함께 고려했습니다.

공개 API 요청 · 120초

응답시간 p95
문자열 + COUNT337 ms
455.3 RPS
COUNT 제거 + ngram453 ms
416.5 RPS
ID·상세 분리61 ms
499.3 RPS
단일 SQL61 ms
499.5 RPS

ID 분리와 단일 SQL의 p95는 같았습니다. 최종 구현은 단일 SQL을 선택했습니다.

검색 결과가 많을 때와 적을 때
ngram26.99ms
인덱스 순서로 검색1.91ms
흔한 검색어에서는 필요한 결과를 찾고 일찍 멈추는 편이 빨랐습니다.

한국어 · 최신순 · 첫 페이지 · 일반 SQL 30회 평균(ms)

02 · 프론트엔드

서버 증설에서 렌더링·캐싱 전환으로

프론트엔드 · 100 RPS 테스트

1,204→177ms
p95 응답시간
  1. 01

    SSR Pod·CPU 증설

    50 RPS · p95 1,776 → 502 ms

    하나의 큰 Pod보다 여러 Node.js 프로세스로 분산한 구성이 유리했습니다. 다만 같은 HTML을 요청마다 생성하는 비용은 남았습니다.

  2. 02

    SSR 작업 경량화

    50 RPS · p95 502 → 356 ms

    번역 데이터를 페이지별로 나누고 댓글 모듈을 지연 로딩했습니다. HTML도 줄었지만, 부하가 커지면 반복 렌더링이 다시 부담이 됐습니다.

  3. 03

    공통 화면을 SSG·CDN으로

    100 RPS · CPU 누적 105.51 → 1.13초

    공통 HTML은 미리 생성하고 JS·CSS는 R2로 전달했습니다. 원본 계산을 거의 제거해도 응답 지연은 남아 네트워크 경로를 구분해 봤습니다.

  4. 04

    동적 뷰어도 ISR·CDN으로

    100 RPS · p95 1,204 → 177 ms

    스토리별 제목이 담긴 HTML을 300초간 재사용했습니다. 제목·공유 상태 변경에는 재검증과 캐시 무효화를 연결했습니다.

동적 SSR · Pod·CPU 구성과 경량화 비교

응답시간 p95
1 Pod · 2 CPU / 2 GiB1,776 ms
2 Pods · 각 1 CPU / 1 GiB1,019 ms
2 Pods · 각 2 CPU / 2 GiB502 ms
SSR 경량화 · 2 Pods356 ms

프로세스 분산과 SSR 경량화는 효과가 있었지만, 요청마다 HTML을 만드는 작업은 남았습니다.

03 · 좋아요와 인기순

기간별 좋아요를
매번 다시 세지 않도록.

최근 24시간·7일·30일 인기순을 조회할 때마다 좋아요 관계 테이블을 조인하고 기간별 COUNT를 계산하면 비용이 커집니다.

이벤트 루프는 유휴 시 약 1초 간격으로 조회해 누적 좋아요와 5분 단위 버킷을 갱신합니다. 순위 루프는 별도로 5분 경계마다 슬라이딩 윈도우를 이동해 신규·변경분을 더하고 만료분을 뺍니다.

조회는 저장된 점수로 정렬합니다. 집계를 요청에서 분리해 같은 집계 행의 락 경합도 줄였고, 순위 반영에는 지연이 생깁니다.

같은 워커 안의 두 집계 루프
요청좋아요 상태 + 이벤트 저장같은 트랜잭션에 저장
이벤트누적 좋아요 + 5분 단위 버킷 갱신유휴 시 약 1초 간격 조회 · 밀린 배치는 연속 처리
순위24시간 · 7일 · 30일 점수 갱신5분 경계마다 · 이벤트 루프와 별도로 실행
00:00:00좋아요 1개의 흐름
좋아요를 누르기 전

좋아요가 저장되고 집계되는 과정을 단계별로 확인합니다.

1 / 7
원본 좋아요없음
대기 이벤트0
누적 좋아요0
최근 24시간0
최근 7일0
최근 30일0
슬라이딩 윈도우

00:00–00:05 좋아요 버킷

최근 24시간점수 0
t − 24시간t

집계 전

최근 7일점수 0
t − 7일t

집계 전

최근 30일점수 0
t − 30일t

집계 전

좋아요는 유지돼도 버킷이 창 밖으로 빠지면 해당 기간의 점수에서 차감됩니다.

교내 공간예약 서비스

2026.03–04 · SCG

학과별 공간 예약과 관리자 승인 업무를 위한 서비스.
요구사항 조율부터 프론트엔드·백엔드 개발, 배포까지 단독으로 맡았습니다.

서비스 ↗
Spring Boot · JPAMySQLNext.js

학과가 달라도 같은 서비스로

관리 단위를 기준으로 데이터와 관리자 권한을 분리했습니다. 운영 시간·이용 인원·예약 권한은 설정으로 표현해 학과마다 다른 정책을 수용했습니다.

A 관리 단위소속 · 공간 · 관리자
B 관리 단위소속 · 공간 · 관리자

같은 시간의 중복 예약 방지

예약 트랜잭션에서 공간 행에 비관적 락을 획득한 뒤 겹치는 예약을 검사합니다. 같은 공간에 동시에 들어온 요청을 순서대로 처리합니다.

비관적 락→시간 중복 검사→예약 저장

공간·예약을 나눠 읽고 메모리에서 결합

공간을 먼저 페이징하고, 해당 공간들의 지정 날짜 예약만 IN 쿼리로 일괄 조회했습니다. 예약자 정보는 Fetch Join으로 가져온 뒤, 예약을 공간 ID별 Map으로 묶어 공간 목록과 메모리에서 결합했습니다.

공간별 예약 조회에서 발생하는 N+1을 피하면서 DB 페이징을 유지했습니다.

공간 페이징→예약 IN 조회 + 예약자 Fetch Join→공간 ID별 Map → 메모리 결합

DesignCat

2023.06–12 · 미리디 산학협력

자연어로 문안을 만들고, 어울리는 템플릿과 배치를 제안하는 AI 디자인 도우미.

5인 팀 · 담당: 데이터 전처리, SBERT 파인튜닝·검색, 결과 시각화와 웹 데모

Python · PillowSentence TransformersNext.js · TypeScript
01 · 데이터 전처리

디자인 XML을 학습 가능한 데이터로

XML에서 텍스트·Bounding Box·스타일을 추출하고, SVG와 래스터 이미지를 합성했습니다. 회전·반전·투명도와 좌표 크기를 맞춰 텍스트 없는 배경 이미지와 텍스트·좌표 데이터를 생성했습니다.

02 · 유사도 검색

입력 문장에 어울리는 템플릿 추천

템플릿의 본문·키워드 데이터로 SBERT를 파인튜닝했습니다. 사용자 입력과 템플릿 임베딩의 코사인 유사도를 계산해 Top-K 후보를 추천하고, 웹 데모의 배경 선택 화면에 연결했습니다.

03 · 결과 시각화

모델이 예측한 좌표를 화면으로

UDOP의 레이아웃 예측 좌표를 슬라이드 좌표로 변환하고, 선택한 배경 위에 텍스트를 렌더링했습니다. 좌표에 맞춰 위치·크기·글꼴 크기를 조정해 결과를 미리 볼 수 있게 했습니다.

04 · 웹 데모

대화에서 PPTX 다운로드까지

Next.js와 TypeScript로 GPT 대화와 Python 모델 API를 연결했습니다. 문안 생성·수정, 추천 배경 선택, 레이아웃 생성과 슬라이드 미리보기, PPTX 다운로드까지 하나의 흐름으로 구현했습니다.

활동

System Consultant Group성균관대학교 개발 단체 · 웹 개발 · 인프라

GDSC SKKUCore Member

수상

NYPC Master Track 금상

NYPC Code Battle Top 100 특별상

SW인재페스티벌 카카오 후원기업상

Google Solution Challenge Top 100