들어가며
일을 할 때 정성을 다한다는 것은 어떤 의미일까.
우연히 인적 없는 거리에서 풀숲 깊숙이 박힌 쓰레기를 줍기 위해 온몸에 먼지와 풀잎을 묻혀가며 일하는 당신의 모습을 본 적이 있습니다. '왜 이렇게까지 열심히 할까?' 생각하며 당신이 참 미련하다고 여겼습니다.
퇴직 후 무릎 보호대를 차고 절뚝이며 걷는 당신을 보며, '미련하다'는 말 하나만으로는 당신의 삶을 설명하기 어렵다는 것을 깨닫습니다. 상반기의 시간을 돌아보며, 당신의 삶을 어떻게 설명할 수 있을지 답을 남기고 싶어 이 글을 씁니다. 부디 이 글이 당신에 대한 참회록이 되기를 바라며.
정성을 다하는 일
가끔 사람인지라, 일하기 힘들 때가 있습니다. 지친 마음에 집에 가던 어느 날, 유튜브 알고리즘에 뜬 영상을 보다가 일하기 싫었던 이유에 대해 조금은 이해할 수 있었습니다.
스님의 말씀처럼 나의 일이 아니라고 생각한 일이 나에게 주어졌을 때 일하기 싫었던 것 같습니다. 하지만 일을 할 때 어떤 가치를 만들고 있다고 생각하면 일을 더 열심히 할 수 있었고, 그 과정에서 자연스레 정성이 들어갔습니다.
어떤 일이 주어졌을 때, 이 일을 통해 어떤 가치를 만들 수 있을지 고민하는 마음, 새로운 가치를 창출하기 위해 새로운 일을 만들어가는 마음을 잃지 않는 '수행'이 계속해서 필요하구나 깨닫습니다.
스님의 말씀을 듣고 지난날을 돌아봅니다. 당신을 매일 반복해서 단순하게 청소를 하는 사람으로 생각했던 것 같습니다. 하지만 이제는 당신이 어떤 가치를 만들고 있다는 마음으로 긴 시간 동안 수행했구나 하는 생각에 이제야 당신을 조금은 알아가는 듯합니다.
이번 상반기는 당신처럼 나름의 수행을 했는지 돌아보고 싶어 글을 정리하고자 합니다.
어떤 가치를 만들었는가
먼저 육아크루의 팀원으로서 상반기에는 어떤 수행을 했는지 구체적인 성과로 정리합니다.
게시판 조회 API 성능 1차 개선
정렬 방식 및 NOT IN 쿼리를 최적화하고, 다중 커넥션 활용 환경을 적용하여 TPS를 6.11에서 10.39으로 약 70% 개선했습니다.
문제 정의
커넥션 풀 점유 대기 시간 10초 초과
게시글 목록 조회 API에서 커넥션 풀 점유 대기 시간이 10초를 초과하며 타임아웃이 발생했습니다.
트래픽 증가에 대비해 DB 커넥션 수가 60 이상으로 3분간 유지되면 자동으로 DB Replica가 증설되도록 설정했으나, Replica 증설 완료까지 시간이 소요되어 짧은 시간 동안 커넥션 요청이 급증할 경우 DB가 증설되기 전까지 API 요청이 실패할 가능성이 있었습니다. 문제의 원인을 API 성능 저하로 판단하고, 현재 API 성능이 어느 정도 나오는지 테스트를 진행했습니다.
TPS 6.11의 느린 API 성능
운영 환경과 동일한 환경에서 성능 테스트를 진행하기 위해 staging 서버 환경을 구축했습니다. EC2 t3.micro 서버 4대와 Aurora PostgreSQL db.t3.medium master 1대, Replica 3대를 준비한 후 CloudFront에 ALB를 연결하여 테스트를 준비했습니다.
k6를 활용하여 1분 동안 vUser를 30까지 증가시키고, 이후 2분 동안 50 vUser까지 증가시키며 API 응답 성능을 측정했습니다. 테스트 결과, P95 응답 시간은 7.87초였고 일부 요청에서는 최대 10.32초까지 지연이 발생했습니다. TPS는 6.11인 느린 API 응답을 개선해야 할 필요가 있었습니다.
문제 해결
정렬 방식 및 NOT IN 쿼리 개선
게시글 목록 조회 API는 posts 테이블에서 데이터를 조회한 후, 필요한 추가 데이터를 함께 불러오는 구조입니다. 유저가 다시 피드를 조회할 때 이전에 조회했던 커리어톡 정보를 응답값에 포함하지 않도록 NOT IN 쿼리를 사용하고 있었고, API를 호출할 때마다 5개의 커리어톡 정보를 랜덤 하게 조회하기 위해 랜덤 정렬을 진행하고 있어서 과정에서 1200ms의 속도가 걸리고 있었습니다.
문제 해결을 위해 서버에서 랜덤 하게 정렬해서 5개 정보를 주는 방식을 최신순으로 10개만 전달해서, 클라이언트에서 랜덤 정렬하는 방식으로 동작 방식을 수정했습니다. 그리고 NOT IN 쿼리를 Left outer join으로 수정하여 특정 조건에 매칭되지 않는 경우엔 우측 테이블의 값이 null인 값을 조회하도록 하여 1200ms에서 300ms로 조회 성능을 개선했습니다.
API 분리 및 다중 커넥션 처리
하나의 API에서 하나의 커넥션으로 모든 데이터를 조회하고 있었습니다. API를 한 번 요청하면 데이터가 모두 조회될 때까지 오래 동안 DB 커넥션을 맺고 있어 커넥션 풀에 반환이 빠르게 이루어지지 않았습니다. 이로 인하여 응답을 빠르게 전달하지 못하는 문제가 있었습니다.
문제 해결을 위해 API를 분리했고, 조회 성능을 더욱 향상시키기 위해 단일 커넥션이 아닌 다중 커넥션을 사용해 데이터를 조회하도록 설정했습니다. 그리고 다중 커넥션 로직을 병렬로 실행하기 위해 Promise.all을 활용하여 병렬적으로 쿼리를 실행하고, 쿼리 실행이 완료되면 커넥션을 빠르게 반환하도록 구성해 성능을 최적화했습니다.
결과
TPS 69.8% 개선
테스트 검증을 위해 하나의 API를 호출하면 분리한 API의 로직이 실행되도록 구성했고, 테스트 케이스는 동일하게 유지하여 API 응답 성능을 측정한 결과 P95 응답 시간은 7.87초에서 4.56초로 42% 단축됐고, TPS는 6.11에서 10.39로 약 69.8% 증가했습니다.
게시판 조회 API 성능 2차 개선
커스텀 데코레이터를 활용하여 Redis와 로컬 메모리에서 데이터를 캐싱할 수 있도록 구성하여 TPS를 10.39에서 45.35로 약 336.48% 개선했습니다.
문제 정의
TPS 10.39의 느린 API 성능
비효율적인 쿼리를 개선하고 다중 커넥션을 처리하는 등 성능 개선을 진행했지만, 여전히 TPS는 10.39로 낮은 수치였고, 응답 시간은 p95 기준 4.56초였으며 최대 5.99초가 걸리는 요청도 있었습니다. DB 커넥션을 적게 사용하는 방식으로 개선해야 성능을 올릴 수 있다고 판단했습니다. 문제 해결을 위해 캐싱 적용을 검토했습니다.
이른 최적화 방지
게시글 목록 조회 API의 로직을 모든 유저에게 동일하게 제공되는 정적 데이터를 전달할 때 캐싱된 데이터를 전달하고, 유저별로 다르게 조회해야 하는 동적 데이터만 실시간으로 조회하는 방식으로 처리한다면 빠르게 API 응답 속도를 개선할 수 있다고 판단했습니다. 캐싱 적용을 위해 크게 4가지를 검토했고, 최종적으로는 캐싱을 활용하기 위한 NestJS 커스텀 데코레이터를 활용하여 캐싱을 처리하는 로직을 구성했습니다.
TypeORM cache 한계점
캐싱 적용을 위해 먼저 TypeORM에 내장된 cache 기능을 활용해 쿼리 결과를 캐싱하는 방법을 우선 검토했습니다. 이 방법은 캐싱이 필요한 쿼리에 .cache() 메서드만 추가하면 되기 때문에, 다른 비즈니스 로직을 수정할 필요 없이 간편하게 캐시를 적용할 수 있다는 장점이 있었습니다. 하지만 게시글 목록 조회 후 다른 데이터를 불러오는 로직에서도 캐싱을 일관되게 처리해야 하는데 캐싱 시점이 일치하지 않으면 잘못된 데이터를 반환할 가능성이 생겼습니다.
cache-manager 한계점
캐싱 적용을 위해 로컬 캐시에서 데이터를 먼저 조회하고 없을 경우 다음 계층에서 조회하도록 여러 캐시 스토어를 통합 관리할 수 있는 cache-manager를 활용하고자 했습니다. 검토 과정에서 cache-manager의 get 함수를 활용할 경우 로컬 캐시 Miss 되더라도 Redis 캐시가 Hit 상태라면 로컬 캐시 Miss 여부를 감지할 수 없다는 점을 알았습니다. 문제 해결을 위해 비즈니스 로직에서 직접 Look Aside 방식으로 데이터를 조회하고, 로컬 캐시가 Miss 되면 캐시를 직접 저장하는 로직을 구현하려 했지만 캐시 관리 로직이 서비스 코드에 관여하면서 코드의 가독성과 유지보수성이 저하되는 문제가 발생할 수 있었습니다.
NestJS CacheInterceptor 한계점
AOP 기반의 데코레이터 패턴을 적용하여 캐시 로직을 공통화하고, 서비스 로직과 분리함으로써 유지보수성과 가독성을 향상시키고자 했습니다. 이를 위해 NestJS에서 기본으로 제공하는 CacheInterceptor를 활용하여 캐시 데코레이터 활용 로직을 구성하려 했습니다. 하지만 CacheInterceptor를 활용한 데코레이터 로직은 컨트롤러에만 적용 가능하며, 서비스나 프로바이더 계층에서는 사용할 수 없는 한계가 있었습니다.
문제 해결
NestJS 커스텀 데코레이터 활용 환경 구성
문제 해결을 위해 NestJS에서 커스텀 데코레이터를 활용할 수 있도록 구성하려 했으나 nestjs-aop 패키지를 활용하면 쉽게 커스텀 데코레이터를 구현할 수 있다는 것을 알았습니다. 해당 패키지와 NestJS의 applyDecorators를 활용해 메모리 캐시와 Redis 캐시를 순차적으로 적용하는 데코레이터를 구성했습니다. 이를 통해 서비스나 프로바이더 계층에서도 효과적으로 캐시를 적용할 수 있었으며, 데코레이터로 캐싱을 활용할 수 있는 환경을 구축하여 코드의 가독성과 유지보수성을 개선했습니다.
ElastiCache 모니터링 환경 추가
현재 서버에서는 ElastiCache의 cache.t4g.micro 노드 유형을 사용하고 있습니다. 해당 유형은 0.5 GiB의 메모리를 제공하므로 메모리 공간이 충분하지 않지만 당장은 서비스에서 활용하는 캐시 데이터가 많지 않고, 로컬 캐시도 함께 활용하기 때문에 사용하는 데는 문제가 없다고 판단했습니다. 그러나 부족한 메모리와 사양으로 인해 추후 문제가 발생할 수 있을 것이라 판단하여 CloudWatch 지표를 기반으로 이상 징후가 감지되면 Slack을 통해 실시간으로 알림을 받아 즉각 대응할 수 있도록 설정했습니다. 알림에는 크게 세 가지 알림을 추가했습니다.
첫 번째로 메모리 사용률이 60% 이상이 되면 향후 메모리 부족 문제가 발생할 가능성이 높아지므로, 이를 대비해 불필요한 캐시를 삭제하거나 더 큰 메모리 용량의 DB로 확장할 수 있도록 알림을 추가했습니다.
두 번째로 CPU 사용률 증가로 인해 서비스에 영향을 줄 수 있기 때문에 CPU 관련 알림도 함께 설정했습니다. 일반적으로 사용 가능한 CPU의 90%를 임계값으로 설정하는 것이 권장되지만, Redis는 단일 스레드로 동작하기 때문에 실제 임계값은 노드의 총 코어 수를 고려해 조정해야 합니다. 예를 들어, 2 코어 노드 유형을 사용할 경우 CPU 사용률의 임계값은 90%를 2로 나눈 45%가 되며, 이에 따라 CPU 사용률이 30% 이상일 때 상태를 점검할 수 있도록 알림을 설정했습니다.
세 번째로 Redis 프로세스의 부하를 보다 정확하게 파악하기 위해 EngineCPUUtilization 지표가 60% 이상일 경우 부하가 발생한 것으로 판단하여 이를 모니터링하도록 구성했습니다.
결과
TPS 336.48% 개선
테스트 케이스를 동일하게 유지하여 API 응답 성능을 측정한 결과 P95 응답 시간은 4.56초에서 0.361초로 92.08% 단축됐고, TPS는 10.39에서 45.35로 약 336.48% 증가했습니다.
게시판 조회 API 성능 3차 개선
EC2 스케일 업 및 TypeORM의 커넥션 풀 size를 조정하여 TPS를 45.35에서 242.92로 약 435.66% 개선했습니다.
문제 정의
최대 1분의 지연 시간
캐싱을 적용했을 때 어느 정도의 트래픽까지 처리할 수 있는지 테스트하기 위해, 1분 동안 vUser를 200까지 증가시키고, 이후 2분 동안 300 vUser까지 증가시키며 API 응답 성능을 측정했습니다. 테스트 결과, 평균 응답 시간은 3.52초, P95 응답 시간은 5.02초, TPS는 46.35건으로 측정되었습니다. 그러나 일부 요청에서 실패 응답이 발생했으며, 사용자가 최대 1분을 기다려도 API 응답을 받지 못하는 문제가 확인되었습니다. 이러한 문제로 인해 보다 안정적인 API 응답 성능을 확보하기 위한 최적화 작업이 필요했습니다.
CPU 과부하 문제
부하 테스트 과정에서 CPU 사용률이 급격히 상승하며 서버 동작이 멈추는 문제가 발생했습니다. 정적 데이터는 캐싱을 활용해 빠르게 조회할 수 있었지만, 사용자마다 응답값이 달라지는 동적 데이터는 DB에서 직접 조회해야 했습니다. API 요청이 쌓이면서 커넥션 풀을 점유하기 위한 대기 시간이 길어졌고, 이는 결국 서버 성능 저하로 이어졌다고 판단했습니다. 팀 작업의 우선순위를 고려했을 때, 코드 수정에 많은 리소스를 투입하기 어려운 상황이었기 때문에, 동적 데이터에 대한 캐싱 로직을 추가하는 것 외에도 성능을 개선할 수 있는 방법을 고민했습니다.
문제 해결
EC2 스케일 업
이번 성능 개선을 진행하면서, DB Replica를 추가하지 않고 서버 인스턴스를 스케일 업 또는 스케일 아웃했을 때 성능이 얼마나 개선될 수 있는지 확인하기 위해 테스트를 진행했습니다.
먼저, 스케일 아웃을 진행하여 t3.micro 인스턴스를 3대 추가해 총 7대를 운영했을 때, TPS는 45에서 70으로 증가했지만, 기대했던 만큼의 성능 향상은 이루어지지 않았습니다. 이후, 총 4대의 서버 중 2대는 기존과 동일하게 t3.micro 인스턴스를 유지하고, 나머지 2대는 CPU 작업에 최적화된 c5.large로 변경하여 스케일 업을 진행했습니다. 그 결과, TPS는 45에서 133으로 증가하며 성능이 크게 향상되었습니다. 이를 통해, CPU 크레딧 기반의 인스턴스를 단순히 확장하는 것보다, CPU 성능이 보장되는 인스턴스로 스케일 업하는 것이 더욱 효과적인 성능 개선 방법이라는 점을 확인할 수 있었습니다. 테스트 결과, 스케일 아웃보다 스케일 업을 적용하는 것이 성능 개선에 더 효과적이라는 결론을 도출할 수 있었습니다.
스케일 업 검토
스케일 업을 진행할 경우, CPU 작업에 최적화된 인스턴스를 사용하는 방안을 검토했습니다. 현재 서비스는 AWS Elastic Beanstalk 환경에서 운영되고 있으며, Node.js 런타임 환경의 서버를 사용하고 있기 때문에 vCPU 개수가 많은 서버를 사용할 필요가 없다고 판단했습니다. 이에 따라, vCPU가 최소인 인스턴스를 찾았지만, 현재 사용 중인 EB 플랫폼 버전에서는 vCPU가 1개인 인스턴스를 선택하기 어려웠습니다. 따라서, 최소 vCPU가 2개인 인스턴스 중 CPU 사용에 적합한 c5.large 타입을 적용하는 방향으로 설정을 검토했습니다.
비용 측면에서 보면 t3.micro의 시간당 요금: $0.0104, c5.large의 시간당 요금: $0.096로 c5.large는 t3.micro보다 약 9.23배 높은 비용이 발생합니다. 그러나 성능을 비교했을 때, TPS는 45에서 133으로 약 2.95배 증가했습니다. 비용이 9배 증가했음에도 TPS가 3배 가까이 증가한 점을 고려하면, 단순한 비용 증가가 아니라 트래픽 증가에 대비한 안정적인 서비스 운영을 위한 적절한 투자라고 판단했습니다.
특히, T3 인스턴스를 무제한 모드로 사용하면 크레딧을 반환하지 못할 경우 추가 비용이 발생할 가능성이 높고, 성능 변동성이 클 수 있기 때문에 보다 일정한 성능을 제공하는 c5.large로 변경하는 것이 더 합리적인 선택이었습니다. 또한, 장기적으로 트래픽 증가를 고려할 때, 성능이 보장되는 C5 인스턴스를 활용하는 것이 유지보수 비용을 줄이는 데 더 유리할 것으로 판단했습니다.
DB poolSize 설정
TypeORM의 poolSize 옵션도 재설정했습니다. 기존에는 기본값인 10으로 설정되어 있었는데, 이는 max_connections 값이 401인 데이터베이스 환경에 비해 지나치게 작은 값이었습니다. 따라서 최대 연결 수가 제한되어 성능 저하 문제가 발생할 가능성이 있다고 판단했습니다.
현재 4대의 WAS를 운영 중이며, DB Replica는 3대로 구성되어 있습니다. 또한, PostgreSQL 또는 RDS에서 시스템 및 관리자 작업을 위해 예약하는 연결 수를 10%로 고려하여, 전체 max_connections 값인 401에서 약 20개의 연결을 제외한 381개를 사용할 수 있다고 가정했습니다. 이를 기반으로 WAS 1대당 적절한 poolSize를 계산한 결과, 95개 정도가 적당하다고 판단했습니다. 하지만, CPU 사용량 증가에 따라 서버가 자동으로 확장될 가능성을 고려해야 했기 때문에, WAS가 최대 5대까지 확장되는 상황을 가정하고 poolSize를 75로 설정하여 테스트를 진행했습니다.
Nginx 타임아웃 설정
성능 테스트 과정에서 기존 타임아웃 시간이 길어 서버 장애 발생 시 사용자가 API 응답을 오랫동안 기다려야 하는 문제가 있었습니다. 이를 개선하기 위해 Nginx 타임아웃 설정을 적용하여 5초 이상 대기하는 요청은 즉시 실패하도록 구성하였습니다.
백엔드 서버와의 연결 및 데이터 전송 과정에서 다음과 같은 타임아웃 값을 설정하였습니다. proxy_connect_timeout을 5초로 설정하여 백엔드 서버 연결이 5초 이상 지연되면 타임아웃이 발생하도록 했습니다. proxy_send_timeout과 proxy_read_timeout을 5초로 설정하여 요청 데이터 전송 및 응답 대기 시간이 초과되면 요청을 종료하도록 하였습니다. 마지막으로 send_timeout을 5초로 설정하여 Nginx가 클라이언트로 응답을 전송할 때 5초 이상 걸리면 연결을 해제하도록 하였습니다.
Nginx 타임아웃 설정을 적용함으로써, API 요청 지연 문제를 해결하고, 서버 장애 발생 시 불필요한 대기 시간을 줄일 수 있었습니다. 또한, 빠른 타임아웃 처리를 통해 서버 리소스를 최적화하고 안정적인 서비스 운영이 가능하도록 개선하였습니다.
결과
총 TPS 3,875% 개선
처음에는 K6를 활용하여 1분 동안 vUser를 30까지 증가시키고, 이후 2분 동안 50 vUser까지 증가시키며 API 응답 성능을 측정했습니다. 테스트 결과, P95 응답 시간은 7.87초였으며, 일부 요청에서는 최대 10.32초까지 지연이 발생했습니다. 또한, TPS는 6.11로 매우 낮아, API 응답 속도를 개선해야 할 필요성이 있었습니다.
총 3번에 걸친 개선 과정을 통해 1분 동안 vUser를 500까지 증가, 이후 2분 동안 700 vUser까지 증가시키며 API 응답 성능을 측정한 결과 TPS는 6.11에서 242.92로 약 3,875% 증가했으며, EC2 스케일 업뿐만 아니라 DB 커넥션 및 타임아웃 최적화를 함께 진행함으로써 높은 부하 환경에서도 안정적인 API 응답 성능을 유지할 수 있도록 개선되었습니다.
게시판 조회 API 캐시 무효화
Redis 캐시 제거 데코레이터 구성 및 Redis Pub/Sub 활용 로컬 캐시 무효화 커스텀 데코레이터를 추가했습니다.
문제 정의
캐시 무효화 정책 부재
게시글 목록 조회 API는 Look-Aside 패턴과 Write-Around 패턴을 활용하여 캐시 된 데이터를 반환하지만, 사용자가 게시글을 추가, 수정, 삭제했을 경우 변경 사항이 즉시 반영되지 않는 문제가 발생했습니다. 특히, 악성 유저가 문제 되는 게시글을 올린 경우 Redis에서는 데이터를 수동으로 삭제할 수 있었지만, 로컬 캐시는 TTL이 만료될 때까지 유지되어 즉각적인 대응이 어려웠습니다. 사용자 입장에서는 이러한 동작이 버그로 인식될 수 있는 문제였습니다.
이 문제를 해결하기 위해 먼저 Redis 캐시와 로컬 캐시의 TTL을 다르게 설정하여, 로컬 캐시의 TTL을 짧게 유지함으로써 주기적으로 최신 데이터를 반영하도록 구성했습니다. 이후, 캐시 데이터를 자동으로 제거하는 커스텀 데코레이터를 생성하고, Redis Pub/Sub 기반의 캐시 동기화 방식을 도입하여 서버 간 데이터 정합성을 유지할 수 있도록 개선하였습니다.
문제 해결
CacheEvict 데코레이터 설계
캐시 무효화가 필요한 로직을 분석한 결과, 우선적으로 적용해야 할 세 가지 주요 기능을 선정했습니다. 먼저, 게시글 삭제 및 수정 기능의 경우 로컬 캐싱이 이루어지므로, 로컬 캐시까지 무효화할 수 있는 기능이 필요했습니다. 반면, 좋아요 추가 및 취소 기능은 Redis에서만 캐싱을 적용했기 때문에 이 기능에서는 Redis 캐시만 무효화할 수 있는 별도의 처리 방식이 필요했습니다.
캐시 무효화 로직을 구현할 때, 전체 네임스페이스의 캐시를 삭제할지 또는 특정 키만 삭제할지를 결정할 수 있도록 설계했습니다. allEntries 값이 true인 경우, 해당 네임스페이스(value:*)에 속하는 모든 캐시를 삭제하고, 특정 key와 pattern이 설정된 경우에는 특정 패턴에 속하는 캐시만 선택적으로 삭제하도록 구성했습니다.
이 과정에서 Redis의 SCAN 명령어를 활용하여 특정 패턴과 일치하는 캐시 키를 검색하고, 동적으로 생성된 정규식을 이용해 필요한 데이터를 필요하는 방식을 적용했습니다. 검색된 키는 Redis의 DEL 명령어를 사용하여 삭제되며, 삭제된 캐시 키 정보를 JSON 형식으로 변환하여 Redis의 cache-eviction 채널에 publish 하도록 설정했습니다.
결과
Redis Pub/Sub 활용 Local 캐시 제거
OnModuleInit 인터페이스를 구현하여 애플리케이션이 시작될 때 자동으로 subscribe 메서드를 호출하도록 설정했습니다. 만약 cache-eviction 채널에서 메시지를 수신하면 해당 키를 로컬 캐시에서 삭제하여 데이터 정합성을 유지할 수 있도록 구성했습니다.
이 작업을 통해 여러 서버에서 특정 캐시 키가 삭제되었음을 실시간으로 감지하고, 로컬 캐시를 정리하여 서버 간 동기화가 이루어지도록 개선했습니다. 결과적으로, 캐시 불일치 문제를 해결하고, 게시글 수정 및 삭제 시 Redis와 로컬 캐시가 동시에 갱신되어 최신 데이터를 반환할 수 있도록 보장할 수 있었습니다.
로깅 적재 시스템 비용 최적화
fluentbit, Kinesis Firehose, S3, Athena, Glue를 활용한 로그 저장 환경을 구성하여 기존 로깅 시스템에서 발생되었던 비용을 월 $2~300에서 $2로 절감할 수 있도록 설계했습니다.
문제 정의
기존 설계한 중앙화된 로깅 시스템에서 사용하는 CloudWatch Logs 로그 적재 환경에서 월 $2~300 정도 비용이 발생했습니다.
1GB 당 $0.76의 비용이 발생하는 환경에서 API 호출 수가 늘어날수록 로그 적재 비용은 비례하여 증가할 수 있어서 비용 최적화가 필요했습니다.
문제 해결
기존 CloudWatch Logs에 로그를 적재하는 환경을 제거하고, 로그 처리 로직을 애플리케이션으로부터 디커플링 할 수 있도록 사이드카 방식의 fluentbit을 활용하여 Firehose로 로그를 전달하는 형태로 로그 적재 환경을 변경하도록 설계했습니다.
fluentbit에 버퍼 형태로 적재된 로그를 S3로 직접 적재할 수 있지만, parquet 형태로 데이터를 가공하여 S3에 적재할 수 없어 Athena에서 데이터를 조회할 때 조회 비용이 증가할 수 있다고 판단하여 채택하지 않았습니다. NestJS 서버에서 Firehose에 AWS SDK를 활용하여 네트워크 환경에서 로그를 전달할 수 있지만, 로그 처리 로직이 애플리케이션으로부터 결합되어 코드를 관리하는 리소스가 추가로 들어갈 수 있다고 판단하여 선택하지 않았습니다.
결과
월 $2~300 정도의 CloudWatch Logs 비용을 제거하고 약 $2 정도에 로그를 수집할 수 있는 환경을 설계할 수 있었습니다.
필요한 일을 한 사람이었는가
정들었던 다이노즈 팀을 떠나며 이 글을 씁니다.
육아크루에서 엄마들의 산후 우울증 해결이라는 목표를 가지고 팀의 첫 개발자로 합류했습니다. 입사 2주 전부터 미리 합류하여 팀의 문제점을 파악하는 것으로 업무를 시작했습니다. 합류한 지 한 달 만에 서비스를 출시해야만 했습니다. 뿐만 아니라, 짧은 스프린트 기간 동안 여러 기능을 개발하고, QA 및 CS를 처리하며 동시에 장애까지 해결하면서 서비스를 발전시켜야 했습니다. 이 과정에서 서비스 성장과 조직 방향성을 고려하여 백엔드 개발, AWS 인프라 구축, 아키텍처 설계 등의 업무를 주도적으로 수행할 수 있었습니다. 특히 2024년 8월부터 진행한 SEO 고도화 작업을 통해 MAU 8,000명에서 2025년 2월 기준 MAU 13만 명으로 성장시킬 수 있었습니다.
작은 조직의 개발자로서, 개발 역량은 기본이었고 팀에 더 기여할 수 있는 일이 무엇인지 항상 고민했습니다. 돌이켜보면 팀을 성장시키고 지키기 위해 할 수 있는 일이라면 마다하지 않았습니다. 팀에서 어떤 문제를 해결했고 어떤 마음가짐으로 일했는지 회고 글을 꾸준히 작성했습니다. 작은 기록들이 모인다면 팀의 개발 문화와 업무 진행 방식을 설명하는데 도움이 될 거라 생각했습니다. 더 나아가 팀 브랜딩에도 신경 쓰며 우수한 인재를 모시기 위해 여러 글을 쓰고 발표를 했습니다. 여러 노력을 하면서 다이노즈 팀을 알릴 수 있었고, 동시에 '박상수'로서의 '나다움'을 찾을 수 있었습니다. '나다움을 찾는다는 행동'을 조수용 님은 아래와 같이 정의합니다.
풀숲 깊은 곳에 있는 쓰레기를 줍는 것이 월급을 받는 행위를 넘어 세상에 도움이 된다고 생각한 당신의 모습처럼, 어떤 일이든 일 속에서 가치를 찾아내고, 가치를 만들기 위해 정성을 다해 일해야 한다는 것. 그리고 혹 그런 마음이 들지 않을 때라도, 일을 할 때 정성을 다할 수 있도록 끊임없이 마음을 단련해야 한다는 것. 이런 마음을 유지하며 살아가는 것이야 말로 '나다움'일 수 있다고 생각했습니다.
이 '나다움'에 대해 깊이 성찰하면서, 언젠가 더 큰 문제를 해결하고 더 많은 사람들에게 도움이 되는 삶을 살기 위해서는 지금보다 더 큰 역량을 쌓아야겠다는 확신이 생겼습니다. 지난 시간을 돌아보고, 앞으로 팀이 나아가고자 하는 방향성을 바라보며 이제 육아크루에서 할 수 있는 모든 역할을 다 했다는 생각이 들었습니다. 개발자로서 더 큰 성장을 이루고 새로운 도전을 하기 위해 이직을 결심했습니다.
사람은 자신의 세계를 넓혀준 사람과 경험을 잊지 못한다고 합니다. 저에게 육아크루는 그런 의미에서 잊지 못할 추억이 될 것 같습니다. 약 3년이라는 시간 동안 개발자로서 '팀에 어떤 가치를 만들어낼 수 있는가'를 고민하며 주도적으로 업무를 수행하는 법을 배웠습니다. 팀과 동료들에게 진심으로 감사합니다. 덕분에 넓은 세상을 볼 수 있었고, 크고 작은 문제를 해결하는 과정에서 크게 성장할 수 있었습니다. 좋은 분들과 함께 할 수 있어서 영광이었습니다.
마치며
일을 정성스럽게 하는 이들의 마음가짐이 궁금할 때면 디즈니플러스 드라마 '더 베어' 작품 속 '리치'라는 캐릭터를 관찰하곤 합니다. 리치는 주인공과 함께 식당을 운영하면서 때로는 무례하고 화를 잘 내는 인물로 그려집니다. 그러다 고급 레스토랑에서 훈련을 받으며 서비스의 중요성과 직업에 대한 새로운 시각을 배우게 되는데, 그 과정에서 정성스럽게 일하는 사람의 마음가짐을 알아가는 모습이 나옵니다.
처음에는 무시당하고 화장실 청소나 포크 광내기 같은 허드렛일을 하며 불만을 터뜨리지만, 설거지를 하더라도 제대로 해야 레스토랑에 오는 손님에게 최고의 가치를 줄 수 있다는 것을 배웁니다. 그렇게 점점 성장하면서 레스토랑의 헤드 셰프와 대화할 기회가 생기고, 그 과정에서 일에 대한 진정한 의미를 깨닫습니다.
어른이 되면, 특별한 일을 하는 사람이 되어있겠지 생각했습니다. 하지만 누구보다 묵묵히, 반복되는 일을 정성스레 해내던 당신을 보면 특별하지 않은 일을 하더라도 주어진 삶을 잘 살아내는 것이 더 중요하다는 생각을 합니다. 누가 보든 안 보든 한결같은 태도로 임하는 그 꾸준함이야말로 그토록 찾아 헤매던 '정성'이자, '나다움'을 완성시켜 준 원동력이었다는 것을 이제야 깨닫습니다.
이 글을 통해 당신의 삶을 설명하려 했던 것은, 결국 나 자신이 '가치 있는 일을 일을 해낸 사람'이었는지를 돌아보는 과정이었음을 고백합니다. 이제 저는 육아크루에서 제가 할 수 있는 모든 '필요했던 일'을 해냈음을 확신하며, 당신에게 배운 '정성'의 태도와 '나다움'을 잃지 않고 다음 도전을 향해 나아가겠습니다. 이 글이 당신에 대한 저의 '참회록'을 넘어, 당신의 '정성'이 제 삶에 깊이 스며들어 저의 '나다움'으로 피어났음을 증명하는 글이 되기를 바랍니다.
참고 링크
'일상 정리 > 회고' 카테고리의 다른 글
[회고] 2024년 하반기 - 무용(無用)을 사랑하는 마음 (6) | 2025.01.01 |
---|---|
[회고] 2024년 상반기 - 등대가 되었던 사람 (2) | 2024.06.30 |
[회고] 2023년 하반기 - 미련한 사람이 우물을 판다 (11) | 2024.01.01 |
[회고] 2023년 상반기 - 친절한 자세를 흩뜨리지 않도록 (3) | 2023.07.02 |
[회고] 2022년 하반기 - 다정함을 닮을 수 있다면 (5) | 2023.01.01 |