모든 주제와 태그

성능·용량

32개의 글

[쿠버네티스 아래의 리눅스 3] cgroup v2와 QoS: requests와 limits가 커널 파일이 되기까지

파드의 requests와 limits가 cgroup v2의 cpu.max, cpu.weight, memory.max로 바뀌고 커널이 그 값을 집행하는 과정을 따라갑니다. OOMKilled와 CPU 스로틀링이 각각 어디서 생기는지, QoS 클래스와 oom_score_adj가 누구를 먼저 죽이는지 커널 문서와 kubelet 소스로 확인하고 JVM 서비스의 OOMKilled 포스트모템을 함께 읽습니다.

[쿠버네티스 아래의 리눅스 4] OverlayFS: 이미지 레이어가 하나의 루트가 되기까지와 Netflix의 마운트 병목

컨테이너 이미지 레이어가 OverlayFS의 lowerdir, upperdir, workdir, merged로 쌓여 컨테이너 루트가 되는 과정을 커널 문서와 containerd 소스로 따라갑니다. copy-up, whiteout, 마운트 옵션 한 페이지 제한을 정리하고 레이어마다 만든 마운트가 노드 전체를 멈춘 Netflix 사례를 원문 기준으로 읽습니다.

[DE Ch-08] 지연은 백분위와 최댓값으로 본다: 평균이 숨기는 꼬리와 합칠 수 있는 히스토그램

평균이 같은 두 서버도 p99는 열 배 차이 날 수 있습니다. 지연은 정규분포를 따르지 않아서 평균과 표준편차가 분포를 설명하지 못합니다. 백분위 여러 개와 최댓값을 함께 보고 백분위는 평균 내지 말고 히스토그램을 합쳐서 구합니다. 팬아웃이 커지면 드문 꼬리가 대부분의 요청에 닿는 이유를 The Tail at Scale의 수치와 시뮬레이션으로 확인합니다.

[DE Ch-09] 측정의 함정: 부하 도구가 느린 순간을 지우는 coordinated omission

서버가 1초 멈췄는데 부하 도구가 p99를 1ms로 기록할 수 있습니다. 응답을 받아야 다음 요청을 보내는 도구는 서버가 멈춘 동안 요청을 보내지 않아서 가장 나쁜 순간의 표본이 빠집니다. 이 coordinated omission을 시뮬레이션으로 재현하고 open loop 부하와 보정 방법을 비교합니다. 워밍업 구간을 다루는 방법과, 두 기계의 시계로 지연을 잴 때 생기는 오차도 정리합니다.

[DE Ch-07] 태스크 하나가 Pod 하나가 될 때: 기동 시간을 구간으로 나누기

KubernetesExecutor와 Argo Workflows는 태스크마다 Pod를 띄웁니다. 쿠버네티스의 공식 Pod 기동 SLO는 p99 5초인데, 이 숫자는 이미지 pull과 init 컨테이너를 뺀 값입니다. Pod가 생성되어 컨테이너가 시작하기까지를 condition, 이벤트, kubelet 메트릭으로 나누는 방법과, 이미지 pull, Pod 생성 호출, init 컨테이너가 각각 어디서 시간을 쓰는지, 짧은 태스크가 많을 때의 대안을 정리합니다.

수요를 가정으로 세우자 쌓아 둔 실측이 용량 모델로 읽혔다

커머스 백엔드의 공급 실측(모델 서버 처리량, DB 읽기·쓰기 무릎, 앱 홈 경로)은 문서마다 쌓여 있었지만 수요 모델이 없어 몇 배 모자란지 말할 수 없었다. MAU 10만 · 30만 · 100만 가정에서 peak RPS를 유도해 대조하자 첫 병목이 모델 서버로 드러났다. 1대 9.34/s는 MAU 약 7.5만에서 홈 수요가 넘어선다. 잠금 직렬 상한과 Little's Law 가 실측과 2% 안에서 맞는지 확인한 뒤 수평 확장 예측을 측정 전에 커밋했다. 2대 실측 15.15/s는 예측 범위 밖이었다. 법칙은 맞았고 서비스 시간을 상수로 둔 가정이 틀렸다. AWS 서울 온디맨드 환산 월 비용은 $903에서 $1,792이고 성장할수록 모델 서버 대수가 비용을 정한다.

짧은 PG 타임아웃은 우리 쪽 대기만 줄였다

PG 읽기 타임아웃 5초는 토스 공식 권장 60초보다 12배 짧았다. 가짜 PG가 끊긴 호출도 끝까지 처리하게 고친 뒤 두 값을 쟀다. 평소 느린 꼬리에서 5초는 결제의 10.2%를 결과 모름으로 만들었고 확정까지 p95 92.7초가 걸렸다. PG가 전부 느린 브라운아웃에서 5초는 1.5배 더 팔았지만 모두 결과 모름을 거쳤고 PG 승인 호출은 16배였다. 측정 전에 적은 기준을 넘어 60초로 바꾸고 결제 화면이 그 시간까지 결과를 기다리게 했다.

재전송을 켰더니 느린 PG에서 결제 실패가 늘었다

PG 응답이 끊긴 승인을 같은 멱등키로 한 번 더 보내게 했다. 응답 일부를 잃는 PG에서는 확정 실패가 14.29%에서 1.90%로 줄었지만 동시 호출 상한이 찬 느린 PG에서는 31.56%에서 55.23%로 늘었다. 상한에 빈 자리가 4개 이상일 때만 다시 보내게 하자 느린 PG에서는 31.40%로 돌아왔고 손실에서는 2.82%로 이득이 남았다. 그 결정이 운영 기본값에 빠져 있던 것은 뒤늦게 찾아 고쳤다.

떠난 고객의 결제를 빼도 PG 호출은 8.4%만 줄었다

PG가 느리면 고객이 이미 화면을 떠난 결제도 PG 동시 호출 상한의 자리를 끝까지 차지한다. 요청에 남은 시간을 싣고 다 된 결제는 PG를 부르지 않게 했다. 요청의 30%를 떠난 결제로 보내면 PG 호출도 30% 줄 거라 예측했지만 8.4%만 줄었다. 비운 자리를 남은 결제가 바로 채웠고 상한 거절이 609건에서 169건으로 줄었다. 처음에는 클라이언트 시계의 절대 시각을 받았다가 남은 시간으로 바꿨다. 결제 화면과 똑같이 움직이는 고객으로 다시 재자 뺀 결제는 0건이었다. 기다림을 멈춘 고객도 조회로 결과를 기다리고 있어 화면이 보내는 남은 시간을 27초로 고쳤다.

쌓인 미확정 결제는 언제 풀리는가: 복구 시간의 하한과 상한, 그리고 알림을 나이에 거는 이유

PG 타임아웃으로 쌓인 미확정 결제 887건이 전부 확정되기까지의 시간은 복구 배치의 속도가 아니라 코드 상수 1분이 정했다. 청크를 줄이자 그때부터는 청크 ÷ 주기가 정했다. 두 식으로 기본 설정에서 1,351건은 약 3.7분, 1만 건은 약 20분이 걸린다고 미리 계산된다. 같은 이유로 적체 알림은 건수가 아니라 가장 오래된 건의 나이에 건다.

CDC가 139배 느리게 나왔다: 느렸던 것은 적지 않은 기본값이었다

사용자 활동 로그를 Debezium CDC로 나르게 바꾸고 재 보니 lag p95가 아웃박스의 139배(1,944ms 대 14ms)였다. 그대로 적었으면 CDC를 기각했을 것이다. 커넥터의 지연 설정 셋을 명시하자 13ms로 같아졌다. 그 전에 반영률 0%·오류 로그 0건인 측정이 있었고 원인은 컨슈머 빈이 아예 없던 것이었다.

캐시 값을 몇 바이트부터 압축할까: 교차점은 쟀고, 임계값은 거기에 두지 않았다

Redis 캐시 값을 압축하면 187B에서는 오히려 7% 커지고 1KB에서는 66% 줄었다. 손익이 갈리는 교차점은 187~279B였지만 임계값은 1KB로 정했다. 코덱은 통념과 달리 SNAPPY가 LZ4보다 CPU를 덜 썼고 이유는 확인하지 못했다. 그리고 값은 정했지만 켜지 않았다. 적용할 캐시가 없었다.

과부하에서 무엇을 포기하나: 정책은 지연을 정하고 개인화 비율은 모델 용량이 정한다

추천 모델이 감당하는 양보다 요청이 많을 때 넘친 요청을 어떻게 포기할지 세 정책을 비교했다. 개인화를 받은 요청 비율(coverage)은 세 정책이 ±0.3%p 안에서 같았고 갈린 것은 p95(160ms·320ms·454ms)였다. 아무것도 거절하지 않는 정책은 4배 부하에서 coverage가 31.4%로 더 좋아 보였는데 요청을 받지도 못해 분모에서 빠진 생존자 편향이었다.

Redis 샤딩: Consistent Hashing으로 워크로드 격리

단일 Redis 인스턴스에서 KEYS 블로킹(34.6ms), 배치↔실시간 워크로드 간섭(GET 최악 15.5ms), volatile-lru 보안 위험을 실측하고, KEYS→SCAN 전환 + 3노드 Consistent Hashing + 블랙리스트 전용 인스턴스 격리로 해결한다. 가상 노드 150개 ConcurrentSkipListMap 라우팅, 노드 장애 시 Lucene fallback, 100 VU 부하 테스트로 검증한 과정을 정리한다.