ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이코에코(Eco²) ext-authz: AuthN/AuthZ 검증 엔진 Stress Test
    이코에코(Eco²)/Auth Offloading (ext-authz) 2025. 12. 14. 15:29

    Go-!

    ext-authz 서버 개발기에서 이코에코 서비스 API(GET)로 ext-authz의 대략적인 성능을 테스트했다.
    당시 유저수 200명->1000명으로 증가하도록 테스트해도 서비스 API의 RPS가 250-280선에서 증가하지 않아, ext-authz 서버가 부하를 온전히 받지 못했다. 서비스 API(GET)엔 도메인별 DB 조회 로직이 포함된만큼 변수가 많았기에, ext-authz 서버의 임계 성능을 알아보고자 더미 엔드포인트(/api/v1/character/ping)를 만들고 AuthN/AuthZ 검증 필터를 통과하도록 구성했다.

    주요 리소스 스펙

    이코에코 클러스터 내 ext-authz 부하 테스트에 영향을 받는 리소스들의 스펙은 아래와 같다.

    EC2 인스턴스

    k8s-api-character t3.small 2 2GB character-api
    k8s-redis t3.medium 2 4GB Redis (cache/blacklist)
    k8s-ingress-gateway t3.medium 2 4GB Istio Ingress Gateway

    Pod 리소스 할당

    character-api k8s-api-character 100m / 300m 128MB / 1GB
    Redis k8s-redis 100m / 500m 128MB / 1GB
    istio-ingressgateway k8s-ingress-gateway 100m / 2000m 128MB / 1GB

    Istio 리소스

    EnvoyFilter cookie-to-header istio-system s_access 쿠키 → Authorization 헤더
    AuthorizationPolicy ext-authz-policy istio-system /api/v1/* 경로 ext-authz 호출
    VirtualService {domain}-vs 각 ns 경로 기반 라우팅

    ext-authz 기본 설정

    Port 50051 (gRPC)
    Timeout 0.25s
    failOpen false
    cpu / memory (limits) 0.2 core / 128Mi
    검증 대상 헤더 authorization, x-refresh-token, x-request-id

    패킷 플로우

    테스트용 더미 엔드포인트(/character/ping)는 별도의 로직없이 AuthN/AuthZ 검증 필터만 통과한다.
    Istio GW Route로 ext-authz 서버를 거쳐 검증을 진행한 뒤, 더미 엔드포인트로 라우팅된다.

    Observability: Grafana, Prometheus

    항목 쿼리
    p99 Latency histogram_quantile(0.99, sum(rate(ext_authz_request_duration_seconds_bucket[5m])) by (le))
    Avg Latency sum(rate(ext_authz_request_duration_seconds_sum[5m])) / sum(rate(ext_authz_request_duration_seconds_count[5m]))
    p99 JWT 검증 histogram_quantile(0.99, sum(rate(ext_authz_jwt_verify_duration_seconds_bucket[5m])) by (le))
    p99 Redis 조회 histogram_quantile(0.99, sum(rate(ext_authz_redis_lookup_duration_seconds_bucket[5m])) by (le))
    Success Rate (%) sum(rate(ext_authz_requests_total{result="allow"}[5m])) / sum(rate(ext_authz_requests_total[5m])) * 100
    CPU (cores) sum(rate(container_cpu_usage_seconds_total{namespace="auth", pod=~"ext-authz.*"}[5m]))
    Memory (GB) sum(container_memory_working_set_bytes{namespace="auth", pod=~"ext-authz.*"}) / 1024 / 1024 / 1024
    RPS sum(rate(ext_authz_requests_total[5m]))
    동시 요청 수 ext_authz_requests_in_flight
    에러 원인 sum by (reason) (rate(ext_authz_requests_total{result="deny"}[5m]))

    ext-authz의 HTTP 1.1 서버에 부착된 메트릭을 기반으로 성능 지표를 수집한다.
    HTTP 1.1 서버와 gRPC 서버는 별개 goroutine이지만 동일한 스레드(OS 레벨)를 공유해서 노이즈 없이 메트릭이 수집된다.
    Locust의 RPS는 전체 플로우를 기준으로 잡히기에 보다 정밀한 측정을 위해 ext-authz의 RPS를 측정하는 쿼리를 추가했다.
    5분 단위로 측정되며 동시 접속자 2000명->2500명, 200명/s -> 250명/s 으로 테스트를 진행했다. 지표와 함께 살펴보자

    Stress Test, users: 2000, ramp-ups: 200, 5m

    users: 2000, ramp-ups: 200, 5m, p99 latency, 431 - 484ms
    users: 2000, ramp-ups: 200, 5m, avg latency, 57.4-80.1 ms
    users: 2000, ramp-ups: 200, 5m, p99 latency(JWT): 0.7-0.95ms
    users: 2000, ramp-ups: 200, 5m, p99(Redis): 100+ms
    users: 2000, ramp-ups: 200, 5m, success rate: 90.4-94.0%
    users: 2000, ramp-ups: 200, 5m, CPU usage, 0.0247 - 0.194 cores
    users: 2000, ramp-ups: 200, 5m, memory usage, 17.5 - 25.1 MB
    users: 2000, ramp-ups: 200, 5m, RPS: 200-884
    users: 2000, ramp-ups: 200, 5m, 최대 동시 처리: 230
    users: 2000, ramp-ups: 200, 5m, reason_error, redis_error: 11.5 - 54.4 req/s

     
    동시 접속자 1000명, 100명/s에서 도메인 API(GET)와 ext-authz가 다운되지 않았기에 2000명, 200명/s부터 측정했다.
    p99 431 - 484ms, avg latency 57.4 - 80.1ms로 sparse한 환경에서의 속도(14.3ms)를 감안하면 부하가 반영된 상황이다.
    JWT 검증 연산은 p99 1ms 미만으로 매우 빠른 속도로 처리되고 있으며 유의미한 부하가 관측되진 않았다.
    주된 병목 요인은 Redis 조회로 p99 100ms, p99 기준 총 처리시간의 25%를 소비한다.

    ubuntu@k8s-master:~$ kubectl exec -it -n auth deployment/auth-api -- python3 -c "
    import redis
    import time
    r = redis.Redis(host='dev-redis.redis.svc.cluster.local', port=6379, db=0)
    
    # Warm-up
    r.ping()
    
    # 측정 (100회)
    ...
    "
    Min: 1.02ms
    Avg: 1.22ms
    p99: 1.78ms
    Max: 3.02ms

    성공률 90.4-94%다. 최대 초당 55.4개의 요청이 Redis 조회에서 실패했다(redis_error).
    RPS가 200-884인 걸 감안하면 초당 11.5 - 54.4 요청 실패는 에러율인 6-10%의 수치와 일치한다.
    현재 Redis의 poolsize는 0-10 이다.
    동시 처리수는 230이니, 초당 210개 요청이 레디스 풀 대기로 진입하며 이중 PoolTimeout(4s)을 초과한 요청이 드랍됐다.
    동일한 네임스페이스인 auth-api(FastAPI)의 redis 조회시간은 2ms 남짓이다. p99 100ms 중 98%가 풀 대기시간이다..
    이론상으론 Redis에서 20 connections × (1000ms / 2ms) = 10,000 req/s의 요청을 커버할 수 있지만, 실제 측정값에 따르면 RPS 200-884임에도 버스트 트래픽(p99)에서 풀 대기가 발생했다.

    Stress Test (breaking point), users: 2500, ramp-ups: 250, 5m

    users: 2500, ramp-ups: 250, 5m, p99 latency: ~470ms
    users: 2500, ramp-ups: 250, 5m, avg latency: ~125ms
    users: 2500, ramp-ups: 250, 5m, p99 JWT verify: ~0.7ms
    users: 2500, ramp-ups: 250, 5m, p99 Redis latency: 100+ms
    users: 2500, ramp-ups: 250, 5m, success rate: ~86%
    users: 2500, ramp-ups: 250, 5m, CPU usage: 0.008-0.012 cores
    users: 2500, ramp-ups: 250, 5m, memory usage: ~22MB
    users: 2500, ramp-ups: 250, 5m, RPS: ~42
    users: 2500, ramp-ups: 250, 5m, 최대 동시 처리: 180
    users: 2500, ramp-ups: 250, 5m, redis_error: ~6 req/s

    2000명 테스트에서 시스템이 다운되지 않았기에 부하를 25% 증가시켜 2500명, 250명/s로 측정했다. p99 전체 응답시간 470ms, 평균 125ms로 이전 테스트(p99 431-484ms, avg 57-80ms) 대비 평균 응답시간이 56% 증가했다. JWT 검증은 p99 0.7ms로 여전히 빠르며 병목이 아니다. p99 Redis 조회시간은 100ms 초과로 관측된다. Go 서버의 redis latency metrics 최대치가 100ms로 걸려있던 상황이라.. 보강이 필요한 상황이다. auth-api에서 측정한 Redis 조회시간(p99 1.78ms)과 비교하면 98ms가 풀 대기 시간이다.

    에러율 = redis_error / RPS = 6 / 42 ≈ 14%

    성공률은 86%로 이전(90-94%)보다 4-8%p 하락했다. redis_error rate가 6 req/s로 이전(11-54)보다 낮아 보이지만, 전체 RPS가 42로 급감했기 때문에 실 에러율은 14%로 증가했다.
    RPS가 이전(200-884)에서 42로 10-20배 감소한 것은 시스템이 포화 상태에 도달했음을 의미한다.
    동시 처리 요청은 약 180개로 이전(230)보다 감소했으며, 메모리 22MB로 리소스는 충분히 여유가 있다.
    CPU 사용량의 경우 최대 0.196 cores로 미미해보이나, 현재 ext-authz의 cpu limits가 0.2 cores로 부하 지점까지 도달한 상황이다.
    현재로선 ext-authz의 CPU limits와 Redis 커넥션 풀(PoolSize=20)이 주요 병목 지점이다.

    주요 지표 요약

    p99 Latency 431-484ms ~470ms 유사
    Avg Latency 57-80ms ~125ms 56% 증가
    p99 JWT 0.7-0.95ms ~0.7ms 유사
    p99 Redis 100ms 초과 100ms 초과 메트릭 보강 필요
    Success Rate 90-94% ~86% 4-8% 하락
    redis_error 11-54 req/s ~6 req/s 감소
    RPS 200-884 ~42 대폭 감소
    동시 처리 230 ~180 감소
    CPU 0.01 cores 0.01 cores 유사
    Memory 17-25MB ~22MB 유사

    테스트 환경: Locust (Local)

    부하 테스트는 로컬 Mac에서 Locust Python 스크립트를 실행하는 방식으로 진행했다.

    로컬 환경 스펙

    OS macOS (Darwin 24.3.0)
    Chip Apple Silicon
    가용 스레드 2,560
    Python 3.11+
    Locust 2.x

    실행 방법

    환경변수 설정 후 실행

    ACCESS_TOKEN="" AUTH_METHOD=cookie locust
    -f tests/performance/test_ext_authz.py
    --host=https://api.dev.growbin.app
    --users=2000
    --spawn-rate=200
    --run-time=5m
    --headless

    테스트 구성

    항목 설정값
    Target https://api.dev.growbin.app
    Endpoint GET /api/v1/character/ping
    인증 방식 Cookie (s_access)
    wait_time 1-3초 (between)

    로컬에서 클러스터로 요청을 보내는 구조로, 네트워크 레이턴시가 포함된다.
    Locust Web UI(http://localhost:8089)에서 실시간 RPS, 응답시간, 실패율을 모니터링하며 Grafana 대시보드에서 ext-authz 내부 메트릭(JWT 검증, Redis 조회)을 병행 확인했다.

    스케일 한계

    로컬 단일 프로세스로 동시 접속자 10,000+ 테스트 시 제약이 있다.

    • File descriptor 한계: ulimit -n 65536 조정 필요
    • CPU 포화: distributed mode 권장

    Distributed mode (Master + Workers)

    locust -f test_ext_authz.py --master --host=https://api.dev.growbin.app


    별도 터미널에서 Worker 실행


    locust -f test_ext_authz.py --worker --master-host=127.0.0.1
    현재 테스트 규모의 요청 부하(2,000-2,500 users, wait_time: 3s)는 로컬 Mac으로 커버 가능했다.
    이어지는 포스팅으론 Redis 풀 튜닝을 기록해두겠다. 해야할 피처들이 쌓이는 상황이라 우선순위를 정해야한다.
    LLM과 함께한다지만 아이디어에서 적용, 구현까지의 주기가 짧아진만큼 기록, 처리해야할 정보량과 태스크(PR, 테스트)가 함께 폭증하기도 해서 살짝 지치기도 한다.. 주말엔 쉬는 게 맞다😴

    댓글

ABOUT ME

🎓 부산대학교 정보컴퓨터공학과 학사: 2017.03 - 2023.08
☁️ Rakuten Symphony Jr. Cloud Engineer: 2024.12.09 - 2025.08.31
🏆 2025 AI 새싹톤 우수상 수상: 2025.10.30 - 2025.12.02
🌏 이코에코(Eco²) 백엔드/인프라 고도화 중: 2025.12 - Present

Designed by Mango