← Work
Backend · 프로젝트 · 2025.11 –

아이리딩Ireading

독서 기록으로 독서 습관과 진도율을 확인하는 B2B 서비스의 백엔드입니다. 스토어에 출시해 운영 중입니다.

역할
백엔드
운영
스토어 출시, 운영 중
NestJSTypeORMMySQLClaude CodeCodex
  1. 01동시 요청 경합 방어
  2. 02외부 도서 API 통합과 안정화
  3. 03본인인증 기반 가입과 소셜 로그인 4종
Case 01

동시 요청 경합 방어

리뷰 작성, 챕터 기록, 소셜 연동 해제, 토큰 재발급 등 6곳의 경합을 찾아, 상황에 맞는 도구로 하나씩 막았습니다.

문제

  • 6개 경로에서 같은 요청이 동시에 들어오면 데이터가 중복되거나 상태가 유실됐습니다.

판단 근거

  • 경로마다 맞는 도구가 다릅니다. 이미 있는 행은 비관적 락, 단일 행 갱신은 조건부 UPDATE, 아직 없는 행의 삽입은 UNIQUE 제약으로 막습니다.

해결

  • 소셜 연동 해제는 비관적 락으로, 두 계정을 동시에 해제해도 마지막 하나는 남도록 했습니다.
  • 토큰 재발급은 현재 토큰이 맞을 때만 갱신하는 조건부 UPDATE로 바꾸고, 갱신된 행이 없으면 실패로 처리합니다.
  • UNIQUE 인덱스를 추가하기 전에 기존 중복을 정리했습니다. 버릴 챕터의 페이지 기록은 남길 행으로 옮겨 데이터 손실 없이 마이그레이션했습니다.

결과

  • 이 중 4곳은 실제 MySQL에 동시 요청 2건을 보내 '1건 성공, 1건 409, DB 1행'이 되는지 통합 테스트로 검증
Case 02

외부 도서 API 통합과 안정화

두 API에 나뉘어 있던 도서 검색을 알라딘 API 하나로 통합하고, 외부 호출 수를 제한하는 워커 풀로 레이트리밋 초과를 막았습니다.

문제

  • 도서 검색이 네이버와 국립중앙도서관 두 API에 나뉘어 있었습니다.
  • 여러 권을 한꺼번에 등록하면 책마다 DB 조회와 외부 호출이 반복되고, 외부 API의 호출 한도를 넘을 위험이 있었습니다.

판단 근거

  • 외부 호출은 병렬로 해도 되지만 동시 실행 수에는 상한이 있어야 합니다.
  • DB 쓰기는 하나의 트랜잭션 연결에서 동시에 할 수 없으므로 순서대로 해야 합니다.

해결

  • 알라딘 API 하나로 통합했습니다. 알라딘은 오류를 정상 응답 본문으로 내려주므로 따로 처리하고, 타임아웃은 5초로 두었습니다.
  • 동시 실행 개수를 제한하는 워커 풀을 직접 구현해, 외부 호출만 동시에 하고 DB 쓰기는 순서대로 합니다.
  • 같은 ISBN을 동시에 저장하다 UNIQUE에 걸리면, 방금 커밋된 행이 보이도록 잠금 조회로 다시 읽습니다.
  • 메타데이터가 빠진 책은 7일 간격으로 다시 채우고, 출간일이 하루 밀리던 타임존 버그를 고쳤습니다.

결과

  • 100건 일괄 등록 시 ISBN별 SELECT 200회 → 2회, 외부 호출 순차 → 동시 4건
Case 03

본인인증 기반 가입과 소셜 로그인 4종

본인인증 결과를 서명된 단기 토큰으로 발급해 이후 가입 단계가 외부 API를 다시 부르지 않게 하고, 미성년자 가입의 예외 상황을 다뤘습니다.

문제

  • 가입은 본인인증, 중복 계정 확인, 가입 또는 기존 계정 연동으로 이어집니다. 단계마다 외부 본인인증 API에 기대면 외부 의존이 여러 곳에 퍼집니다.
  • 만 14세 미만은 법정대리인 인증이 필요한데, 법정대리인 번호는 형제자매가 함께 쓸 수 있습니다.

판단 근거

  • 외부 API 호출은 한 곳에 가두고, 이후 단계는 서버가 서명한 결과를 믿는 편이 단순하고 안전합니다.
  • 미성년자는 전화번호만으로 같은 사람인지 판단할 수 없습니다.

해결

  • PortOne 본인인증은 확인 단계에서 한 번만 호출하고, 그 결과를 서명된 단기 JWT로 발급해 이후 단계는 이 토큰을 믿습니다.
  • 미성년자는 전화번호, 이름, 생년월일이 모두 일치해야 같은 계정으로 판단합니다.
  • Google·Apple·Kakao·Naver의 ID Token을 JWKS로 검증하고, provider마다 검증기를 따로 두는 전략 패턴으로 만들어 새 provider는 구현체만 추가하면 되게 했습니다.
  • 탈퇴한 사용자에 묶인 소셜 계정은 정리한 뒤 다시 가입할 수 있게 했습니다.

결과

  • 외부 본인인증 호출을 한 곳에 모아, 이후 가입 단계는 외부 API 없이 진행