송정기

송정기백엔드 개발자

thdwjdrl401@gmail.com  ·  github.com/thdwjdrl401  ·  경력기술서

Java · Spring Boot · MySQL · PostgreSQL · Redis · RabbitMQ · Docker


약 20만 명
이 사용하는 경상북도 모바일 도민증의 API 서버와 관리자 웹, 앱을 운영·고도화하며 보안 취약점 대응, 성능 개선, 장애 대응을 경험했습니다.

최근에는 대량 데이터 처리와 분산 시스템에 관심이 생겨 관련 프로젝트를 진행하고 있습니다.
실시간 텔레메트리 수집·관제 플랫폼(Locus)에서는 4코어 서버에서 텔레메트리 10,000건/s를 처리했습니다.
100만 명 웹 푸시 발송 시스템(집현전 확장)에서는 처리 시간을 61시간 21분에서 5분 48초로 줄였습니다.


💻 프로젝트

구분소속이름기간
개인개인100만 명 웹 푸시 발송 시스템(집현전 확장)2026.08 ~ 2026.08
팀SSAFY집현전 실시간 라이브 강의 플랫폼2026.07 ~ 2026.08
개인개인Locus 실시간 텔레메트리 수집·관제 플랫폼2026.06 ~ 2026.06
팀(주)에즈넷카썹 중고 자동차 부품 거래 플랫폼2025.08 ~ 2025.09
팀(주)에즈넷덕성여자대학교 산학협력단 홈페이지2024.03 ~ 2025.09
팀(주)에즈넷모이소 경상북도 모바일 도민증 (사용자 20만)2024.04 ~ 2025.08
팀(주)에즈넷tomas 입시 상담 예약 시스템2024.05 ~ 2024.09
개인(주)에즈넷사내 운영 대시보드2024.07 ~ 2024.07

📋 이력

구분소속이름기간
교육SSAFY15기 Java 트랙 (삼성청년SW·AI아카데미)2026.01 ~
경력(주)에즈넷개발팀 사원2024.03 ~ 2025.09
병역육군ROTC 중위 만기전역 · 제32보병사단 해안경계부대 통신소대장2021.03 ~ 2023.06
학력명지대학교정보통신공학과2017.03 ~ 2021.02

🏆 수상

  • SSAFY 2학기 공통 프로젝트 우수상
  • 제32보병사단장 해안감시유공 표창장
    (감시장비의 네트워크 기능을 활용해 다중 PC 영상감시 환경 구축)
  • 육군정보통신학교 신임장교 과정 성적 우수 횃불상
    (네트워크 운용, 운영체제, 정보보호 등 과목 이수)

📜 자격

  • 정보처리기사
  • SQLD
  • ADsP
  • TOEIC Speaking IM3

포트폴리오

집현전 · 100만 명 웹 푸시 발송 시스템 · Locus · 모이소

안녕하세요, 백엔드 개발자 송정기입니다.

저는 기술을 이해하고, 함께 판단할 수 있는 상태를 만드는 것을 중요하게 생각합니다.
아래는 제가 일하고, 배우고, 협업하는 방식입니다.

운영 환경의 제약을 고려해 서비스를 개선합니다

이미 사용 중인 데이터와 기능이 있는 만큼, 변경이 기존 사용자와 시스템에 미치는 영향을 함께 고려합니다.

기술의 내부 동작을 소스 코드까지 따라가며 확인하는 것을 좋아합니다

사용하면서 내부 동작이 궁금해진 부분을 소스 코드까지 찾아보는 것을 좋아합니다.
그중 기억에 남은 내용을 기술 노트로 정리했습니다.

함께 판단할 수 있는 상태를 만드는 것을 중요하게 생각합니다

협업에서는 상대가 자신의 영역에서 판단할 수 있도록 필요한 정보와 근거를 공유하는 것을 중요하게 생각합니다. 기술적인 결론만 전달하면 이후에 전제가 바뀌었을 때 다시 판단하기 어렵기 때문에,
어떤 문제를 해결하려 했는지, 어떤 대안을 검토했는지, 왜 이 방법을 선택했는지까지 함께 남기려고 합니다.

실무에서 특정 이미지 파일만 업로드되지 않은 적이 있습니다. 보안 장비 담당자에게 장비에서 차단된다는 결론뿐만 아니라 업로드가 성공하는 파일과 실패하는 파일, 요청의 서버 도달 여부, 파일 확장자와 실제 형식의 일치 여부를 정리해 공유했고, 담당자는 그 정보를 기준으로 차단 정책을 확인해 해당 업로드 경로에 예외 정책을 적용했습니다.
이 경험처럼 각자가 자신의 역할에서 판단할 수 있는 정보를 정리해 공유하며 협업합니다.

프로젝트

집현전 실시간 라이브 강의 플랫폼 (2026.07 ~ 2026.08)

유튜브 라이브와 유사한 구조로, 강의를 실시간으로 시청하면서 지나간 구간을 다시 재생할 수 있고, 실시간 자막과 강의 자료 기반 AI 질의응답을 함께 제공하는 온라인 강의 플랫폼입니다.
강의 자료에서 핵심 용어를 미리 추출하고, 강의 중 해당 용어가 언급되면 관련 설명이나 자료를 바로 확인할 수 있도록 연결해 학습 흐름을 보조했습니다.

6인 팀에서 백엔드와 인프라를 맡아 SFU 자체 호스팅, STT 자막 파이프라인, 웹 푸시 알림 팬아웃을 담당했고,
SSAFY 2학기 공통 프로젝트 우수상을 받았습니다.

실시간 자막과 핵심 용어 · 라이브 강의 화면

집현전 라이브 강의 화면집현전 라이브 강의 화면 · 핵심 용어
실시간 자막과 함께 강의 자료에서 추출한 핵심 용어를 강조합니다. 자막의 시간을 누르면 영상이 해당 시점으로 이동하며, 오른쪽 패널에서 실시간 노트와 AI 질의응답, 핵심 용어를 확인할 수 있습니다.
실시간 자막 파이프라인 구축
문제 실시간 자막을 저장하고 학생에게 전달하는 과정에서 중복 저장과 메시지 분배 문제가 발생할 수 있는 구조전달 보장이 at-least-once라 동일 자막 조각의 재전달 가능sequence 값은 음성 인식 재시도마다 1부터 다시 시작해 멱등키로 사용 불가 AI 질의응답 서버가 동일 스트림을 소비 중이어서 같은 Consumer Group 사용 시 한 메시지가 두 서버 중 한 곳에만 전달되는 구조
해결 백엔드용 별도 Consumer Group을 구성해 두 서버가 동일 스트림의 전체 메시지를 각각 소비 멱등키를 sequence 대신 eventId로 변경하고, DB에 실제 저장된 자막만 트랜잭션 커밋 이후(AFTER_COMMIT) WebSocket으로 전달 수동 ACK 방식으로 변경해 DB 영속화 완료 후 ACK 처리. 파싱 불가 poison 메시지는 로그 기록 후 ACK, 일시적 저장 실패는 ACK하지 않고 Pending 상태로 유지 Spring Data Redis의 오류 발생 시 구독 종료 동작을 확인하고 cancelOnError를 비활성화해 일시적인 연결 오류 이후에도 소비 유지
결과 eventId 기준 멱등 처리로 중복 저장 방지, 일시적 오류 발생 시 Pending 메시지 재처리 가능

100만 명 웹 푸시 발송 시스템집현전 확장 · 개인 (2026.08)

집현전 프로젝트 종료 후, 개인 확장 과제로 웹 푸시 알림 시스템을 100만 명 규모까지 확장해, 처리량과 자원 사용량을 측정하며 병목을 찾고 발송 구조를 개선했습니다.

단계 흐름도. 자료 게시가 PostgreSQL 아웃박스에 기록되고, 아웃박스가 RabbitMQ 팬아웃 큐로 이어진다. 팬아웃 큐는 Spring 팬아웃 워커 여러 개가 소비하며, 워커는 PostgreSQL 의 notification 표와 아웃박스에 쓴다. 그 아웃박스가 RabbitMQ 웹 푸시 큐로 이어지고, 큐를 Spring 푸시 워커 여러 개가 노드마다 소비한다. 컨테이너별 자원은 PostgreSQL cpu 2.0 mem 1.5GB, RabbitMQ cpu 1.0 mem 512MB, 팬아웃 워커 cpu 2.0 mem 1.6GB, 푸시 워커 노드당 cpu 2.0 mem 1.6GB
자료 게시 이벤트를 Outbox, Fanout Queue, Web Push Queue로 분리해 처리하고, Push Worker만 독립적으로 수평 확장할 수 있도록 구성했습니다. 상단에는 각 컨테이너에 할당한 CPU와 메모리를 표시했습니다.
대규모 알림 발송 시스템의 병목 분석과 성능 개선100만 건 · 61시간 21분 → 5분 48초개선 과정 문서 열기
문제 강의 자료 게시 시 수강생에게 알림을 보내는 기능을 확장해 100만 명 규모에서 1분 내 발송 완료를 목표로 설정 초기 구조는 강의 자료 게시 트랜잭션 안에서 알림 생성부터 발송까지 수행하는 동기 처리 방식 수신자 1,000명 기준 220.9초, 100만 명 환산 시 약 61시간 21분 소요 알림 발송 실패 시 자료 게시까지 롤백되고, 이미 발송된 푸시는 회수할 수 없어 게시 상태와 발송 상태 간 정합성 문제 발생
해결 Transactional Outbox를 적용해 게시 트랜잭션과 알림 처리를 분리. 게시 트랜잭션에서는 자료와 outbox 한 행만 저장하고, 알림은 커밋 이후 별도 처리 알림 발송 실패 시 게시 데이터는 유지하고, 미발행 outbox 행만 폴링해 재처리하도록 구성 이후 DB, 브로커, 발송 노드의 병목을 단계별로 측정하고 8단계에 걸쳐 개선
결과 노트북 한 대(물리 4코어)에서 61시간 21분 → 5분 48초로 단축. 웹 푸시 1,097,488건 전부 성공(유실 · 429 · 5xx 0건) 수신자 적재 26.00초 → 14.11초, 기기 1건 암호 처리 5.95ms → 0.36ms, 발송 4.2건/s → 3,283건/s 코어 수 2배 증가 시 처리량 2.03배 증가. 이를 기준으로 1분 내 처리를 위해 필요한 물리 코어 수를 12코어로 산정
발송 라이브러리의 암호 처리 오버헤드 : 암호 객체 재사용
문제 발송 구간에서 애플리케이션 CPU 사용량이 376%로 할당 한도 400%에 근접한 반면 PostgreSQL은 18%에 그쳐, CPU 병목이 애플리케이션에 있음을 확인 Web Push는 표준(RFC 8291·8292)에 따라 기기별 키로 암호화·서명해야 하며, 발송 라이브러리의 건당 암호 처리 시간이 5.95ms로 측정 단계별로 분해하니 암호 객체 생성과 서버 키쌍 검증이 대부분을 차지. 라이브러리가 Cipher·KeyAgreement·Mac·Signature·KeyPairGenerator를 호출마다 만들고, 바뀌지 않는 서버 키쌍을 매번 검증
해결 RFC 8291·8292의 암호화·서명 절차를 직접 구현해 호출마다 만들던 암호 객체를 스레드별로 재사용. 객체가 연산 상태를 가지므로 공유하지 않고 ThreadLocal로 스레드마다 독립 인스턴스를 유지 기기 1건마다 수행하던 서버 키쌍 일치 검증(verifyKeyPair, 1.83ms)을 기동 시 한 번만 수행하도록 변경 키 생성기를 secp256r1 곡선으로 명시적으로 지정해 초기화하도록 바꿔 임시 키쌍 생성과 ECDH를 각각 5.3배 · 6.7배 단축 임시 키쌍과 salt는 규격이 요구하는 대로 메시지마다 새로 생성
결과 기기 1건의 암호 처리 시간을 5.95ms → 0.36ms로 단축 전체 발송 처리량 470 → 1,609건/s

Locus 실시간 텔레메트리 수집·관제 플랫폼 (2026.06)github.com/thdwjdrl401/locus

여러 종류의 디바이스 텔레메트리를 MQTT·HTTP로 수집해 TimescaleDB에 적재하고, 위치와 상태를 실시간으로 조회하며 지오펜스 진입·이탈을 판정하는 플랫폼입니다.

초당 10,000건 처리를 목표로, 실시간 처리 구조의 병목 지점을 확인하기 위해 만든 개인 프로젝트로, 
수집 API부터 시계열 적재, Redis Streams 기반 처리 경로, k6 부하 측정까지 직접 구현했습니다.

모든 성능 측정은 4코어 8GB 5400rpm HDD 단일 서버에서 같은 조건으로 진행했습니다.

로봇 작업 구역 · AMR 10대

AMR 지오펜스 관제 화면

관제 구역 · 모바일 디바이스 40대

폰 위치 관제 화면
같은 관제 화면에서 로봇 작업 구역과 모바일 디바이스 관제 구역에 각각 지오펜스를 구성했습니다.
디바이스가 경계를 통과하면 ENTER/EXIT 이벤트를 실시간으로 판정해 좌측 하단 목록에 기록합니다.
수집 처리량 : 배치 적재와 저장소 변경
문제 요청 1건마다 INSERT와 COMMIT을 수행해 커밋마다 fsync 발생, 처리량 33 rows/s에서 정체 지표 확인 결과 CPU 사용률 2~8%, 디스크 %util 97%, f_await 25ms로 측정돼 디스크 I/O 병목 확인
해결 수집 요청을 인메모리 큐에 적재하고 일정 건수씩 묶어 INSERT와 COMMIT을 수행하는 배치 처리로 변경해 fsync 횟수 감소synchronous_commit 완화도 검토했지만, 장애 시 최근 커밋 데이터 유실 가능성이 있어 기존 설정 유지 배치 적재 이후 MySQL InnoDB의 클러스터드 인덱스에서 발생하는 랜덤 페이지 쓰기를 줄이기 위해 TimescaleDB 하이퍼테이블로 전환
결과 요청당 fsync 1.2 → 0.059, 처리량 33 → 1,437 rows/s (44배) TimescaleDB 전환 후 1,437 → 5,459 rows/s (3.8배)디스크 쓰기는 평균 200KB 순차 쓰기 형태, 최대 사용률 59%
지속 적재 : 워커 병렬화와 청크 주기 조정
문제 디스크 사용률 최대 59% 수준에서 단일 배치 워커가 처리량을 제한해 남는 I/O 대역폭 활용 불가 워커 수 증가 후 동일 device 행 동시 갱신으로 deadlock detected 발생. 데이터 증가에 따라 현재 청크의 인덱스가 shared_buffers보다 커지며 디스크 읽기 증가
해결 배치 워커를 16개로 늘리고 공유 큐를 분리해 병렬 처리워커당 커넥션 1개를 사용해 여러 커밋이 group commit으로 처리되도록 구성 device_id를 TreeMap으로 정렬해 모든 워커가 동일한 순서로 락을 획득하도록 변경, 데드락 제거 체크포인트 구간의 backlog를 수용하도록 큐 크기를 200k로 설정하고, TimescaleDB의 기본 청크 주기인 7일에서 5분으로 줄여 현재 청크 인덱스가 메모리 범위 안에 유지되도록 조정
결과 초당 10,000건을 63분간 연속 적재하면서 데이터 유실 0건, 처리량 편차 약 ±1% 청크 크기를 조정한 뒤 데이터가 계속 쌓이는 상황에서도 목표 처리량 유지

모이소 경상북도 모바일 도민증 (2024.04 ~ 2025.08)Play 스토어

약 20만 명의 사용자가 본인 확인 후 주민등록번호로 공공 마이데이터를 조회하고, 받은 행정 정보를 기준으로 복지 서비스 자격을 판정해 신청까지 처리하는 공공 플랫폼입니다. 

(주)에즈넷에서 초기 구축 이후 합류하여, API 서버·관리자 웹·Flutter 앱 3개 저장소를 주 담당으로 맡아 기존 서비스 고도화, 조회 성능 개선, 첨부파일 암호화, 신규 기능의 요구사항 정리와 구현을 수행했고, 보안 진단 지적 항목을 조치해 이행진단을 통과했습니다.

근무상황표 조회 N+1 제거와 일괄 조회 전환
문제 여러 교사의 한 달 근무 기록을 날짜별로 조회하는 화면의 응답 시간 10초 이상 소요 교사 수와 날짜 수가 늘수록 동일한 조회 쿼리가 반복되는 N+1 문제 확인
해결 조회 범위에 필요한 근무 로그를 한 번에 조회해 DB 왕복 횟수 감소 교사 / 날짜 / 근무 유형 기준 3단계 Map으로 인덱싱해 이후 계산은 메모리에서 처리 날짜와 교사 기준 2단계 페이징을 적용해 한 요청에서 처리하는 데이터 범위 제한
결과 응답 시간 10초 이상 → 0.5초 이내 같은 달 반복 조회 빈도가 낮아 외부 캐시는 적용하지 않고 요청 단위 인덱싱 사용. 데이터 증가 시 CPU와 힙 사용량 증가 가능성 존재
주민등록번호 평문 메모리 잔존 제거
문제 외부 보안 진단에서 회원가입 과정에 입력한 주민등록번호의 앱 메모리 평문 잔존 지적. Frida · fridump3 메모리 덤프로 동일 현상 재현 Dart의 String은 불변 객체라 내용 덮어쓰기 불가, 참조 해제 후에도 GC 전까지 메모리에 잔존 가능
해결 입력값을 String 대신 가변 리스트(List<int>)로 받아 보안 키보드의 문자 코드를 그대로 처리하고 중간 String 생성 제거 사용 직후 fillRange(0, len, 0)으로 버퍼를 덮어쓰고 입력 직후 RSA 암호화, 이후 처리에서는 암호문만 사용 클라이언트에서 수행하던 주민등록번호 validation을 서버로 이전해 클라이언트에서 평문을 사용하는 구간 제거
결과 동일한 방식의 메모리 재덤프에서 주민등록번호 평문 미검출, 보안 진단 지적 항목 조치 완료
첨부파일 암호화 방식을 운영 중 점진 전환
문제 보안 진단에서 평문 첨부파일 저장이 지적돼 Base64 방식으로 변경했으나, 추가 조치 과정에서 실제 암호화 적용이 요구되어 AES/CBC로 전환 필요운영 환경에는 기존 평문 파일과 Base64 파일이 이미 함께 존재해, 전체 파일을 일괄 변환하기보다 여러 포맷을 동시에 처리할 수 있는 방식이 필요
해결 파일 앞부분에 4바이트 매직 넘버를 추가해 값이 있으면 AES/CBC, 없으면 기존 Base64 방식으로 처리 DB 컬럼 추가 없이 파일 자체에서 포맷을 판별하고 기존 파일은 그대로 유지, IV는 파일마다 개별 생성 CipherOutputStream과 8KB 버퍼를 사용한 스트리밍 암호화 적용
결과 서비스 중단과 기존 파일 일괄 변환 없이 Base64 · AES/CBC 두 포맷 동시 지원