포스트

2024-08-13-TIL

2024-08-13-TIL

Today I Learned

P95

P95는 95번째 백분위수(percentile)다. 응답 시간 P95가 200ms라면 전체 요청의 95%가 200ms 이내에 끝났고, 나머지 5%는 그보다 느렸다는 뜻이다. 같은 방식으로 P50은 중앙값, P99는 상위 1%를 제외한 경계값이다.

평균 대신 백분위수를 보는 이유는 평균이 분포를 가리기 때문이다. 대부분의 요청이 빠르고 일부가 몇 초씩 걸려도 평균은 그럴듯하게 나온다. 그 일부 느린 요청을 겪는 사용자에게는 서비스가 느린 것이므로, SLO나 성능 목표는 보통 “P95 응답 시간 300ms 이하”처럼 백분위수로 정한다. 한 화면이 여러 API를 동시에 호출하면 그중 하나라도 느리면 화면 전체가 느려지므로, 꼬리 지연(P99 이상)의 영향은 생각보다 크다.

  • https://kscodebase.tistory.com/544#:~:text=%EC%9D%B4%20%EB%95%8C%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%20%EB%B0%B1%EB%B6%84%EC%9C%84,%EC%B4%88%20%EC%9D%B4%EB%82%B4%EB%9D%BC%EB%8A%94%20%EB%9C%BB%EC%9D%B4%EB%8B%A4.

Key Strategy

데이터베이스 기본 키를 정하는 전략이다.

  • 자연 키(natural key): 주민번호, 이메일처럼 업무상 의미가 있는 값을 키로 쓴다. 별도 컬럼이 필요 없지만, 업무 규칙이 바뀌면 키를 바꿔야 하고 외래 키로 참조한 모든 테이블이 영향을 받는다.
  • 대리 키(surrogate key): 업무와 무관한 값을 키로 쓴다. 키가 바뀔 일이 없어 대부분의 경우 기본 선택이 된다. 자연 키는 unique 제약으로 따로 보장한다.

대리 키를 만드는 방법도 나뉜다.

방식장점단점
AUTO_INCREMENT작고 순차적이라 인덱스에 유리DB가 발급하므로 저장 전에는 ID를 모름, 여러 DB에 나누면 충돌
UUID(v4)애플리케이션에서 미리 생성, 분산 환경에서 충돌 걱정 없음크기가 크고 무작위라 인덱스 삽입 위치가 흩어짐
시간 순서 ID(UUIDv7, Snowflake 류)분산 생성이 가능하면서 대체로 순차적생성기 구현이나 라이브러리 필요

MySQL InnoDB는 기본 키 순서로 데이터를 저장하는 클러스터드 인덱스를 쓰기 때문에, 무작위 값인 UUIDv4를 기본 키로 쓰면 삽입마다 B+Tree의 임의 위치에 끼워 넣게 되어 페이지 분할과 캐시 효율 저하가 생긴다. 보조 인덱스도 기본 키 값을 함께 저장하므로 키가 클수록 모든 인덱스가 커진다. 2024년 5월 발행된 RFC 9562는 앞부분에 타임스탬프를 넣어 시간순 정렬이 되는 UUIDv7을 정의했다.

  • https://f-lab.kr/insight/database-key-design
  • https://ssdragon.tistory.com/162
  • https://kghworks.tistory.com/109

System Design Interview

시스템 디자인 면접 준비 자료를 모았다. devpill 뉴스레터 글은 요구 사항 정리(기능, 비기능), 용량 추정, 고수준 아키텍처, 데이터베이스 설계, API 설계, 핵심 컴포넌트 심화, 확장성과 안정성 문제 대응이라는 단계로 접근하라고 정리한다. 완벽한 답보다 문제를 분석하고 생각의 흐름을 설명하는 능력을 보여 주는 것이 목적이라는 점을 강조한다. 이 TIL의 다른 항목(P95, 키 전략, 재시도, 직렬화)도 이런 면접에서 심화 질문으로 자주 이어지는 주제다.

  • https://maily.so/devpill/posts/d64fde2f
  • https://f-lab.kr/insight/naver-interview-preparation
  • https://blog.naver.com/okestro/223494285066
  • https://career.guru99.com/ko/top-21-cad-interview-questions/
  • https://brunch.co.kr/@jihyun-um/43
  • https://deveric.tistory.com/105

Best practices for retry pattern: Retry Backoff

재시도 패턴을 쓸 때의 일반적인 원칙은 다음과 같다.

  • 일시적인 오류(타임아웃, 429, 503)만 재시도하고, 400이나 인증 실패처럼 다시 보내도 결과가 같은 오류는 재시도하지 않는다.
  • 재시도 간격은 지수적으로 늘리고 jitter를 섞어 여러 클라이언트의 재시도가 한꺼번에 몰리지 않게 한다.
  • 최대 재시도 횟수와 전체 타임아웃을 정해 무한히 기다리지 않게 한다.
  • 재시도하는 연산은 멱등해야 한다. 결제처럼 멱등하지 않은 요청은 멱등 키를 함께 보낸다.
  • 여러 계층(클라이언트, 게이트웨이, 서비스)이 각자 재시도하면 시도 횟수가 곱으로 늘어나므로, 재시도는 한 계층에서만 한다.
  • 장애가 길어지면 Circuit Breaker로 재시도를 멈춘다.

  • https://harish-bhattbhatt.medium.com/best-practices-for-retry-pattern-f29d47cd5117

Serialization and Redis

Spring Data JPA의 엔티티를 직렬화하면 JdkSerializer를 사용하면 기본적으로 패키지를 포함한 풀네임이 포함되는 형태로 직렬화된다. 이 값을 바로 Redis같은 저장소에 넣게되면 버전업이 되었을때 패키지 경로가 달라져서 역직렬화에 실패하는 경우가 발생한다. 따라서 레디스 같이 자바 시스템 외부에 있는 저장소에 값을 저장할때는 자바에 의존적인 형태가 아닌 단순한 key-value 형태나 JSON 등 표준화된 포맷으로 저장해두고 어떤 시스템에서도 언어나 프레임워크에 상관없이 읽어들일 수 있어야한다.

Spring Data Redis 문서 기준으로 RedisCacheManager의 기본 값 직렬화기는 JdkSerializationRedisSerializer다. 그래서 별도 설정 없이 @Cacheable을 쓰면 위의 문제가 그대로 생긴다. 값 직렬화기를 JSON으로 바꾸는 것이 첫 번째 대응이다.

다만 JSON 직렬화기도 설정에 따라 클래스 정보를 함께 저장한다는 점에 주의해야 한다. GenericJackson2JsonRedisSerializer는 역직렬화할 타입을 알기 위해 JSON 안에 @class 속성으로 FQCN을 넣기 때문에, 클래스를 옮기면 결국 같은 문제가 생긴다. 패키지 변경에서 자유로우려면 캐시 이름마다 대상 타입을 지정하는 Jackson2JsonRedisSerializer를 쓰거나, 엔티티 대신 캐시 전용 DTO를 저장해 저장 형식을 도메인 모델의 변경과 분리하는 것이 안전하다. 엔티티를 그대로 캐시하면 지연 로딩 프록시가 직렬화되는 문제도 함께 생긴다.

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

변경이력

2번 수정

  1. docs(posts): separate the sections of every post with a thematic break
  2. docs(til): write up the link-only 2024 tils and the shell script note

댓글

아직 댓글이 없습니다