스케일링의 중요성 및 유형
-
스케일링이란? 애플리케이션이 분당 사용자 요청(RPM) 증가를 처리할 수 있는 잠재력입니다. 초기부터 스케일링을 고려하는 것은 제품의 유연성을 확보하고 미래 성장에 대비하며, 향후 기술적 문제를 예방하는 데 중요합니다.
-
스케일링의 필요성:
- 성능 문제: 데이터 증가에 따라 성능 저하 발생 (예: 1,000개 이상의 게시물 쿼리 시 속도 저하).
- 수정의 어려움: 초기 설계 단계에서 스케일링을 고려하지 않으면 나중에 앱의 많은 부분을 재작업해야 합니다.
- 예측 불가능한 사용자 증가: 갑작스러운 트래픽 급증에 대비하지 않으면 앱이 다운될 수 있습니다.
- 데이터 복잡성 증가: 기능 추가 시 데이터 관계 및 쿼리 복잡성 증가로 성능 저하 발생.
- 신뢰도 하락: 앱 속도 저하 또는 충돌은 사용자 신뢰를 떨어뜨립니다.
수직 스케일링 vs 수평 스케일링
- 수직 스케일링 (Vertical Scaling):
- 기존 서버에 RAM, CPU, 디스크 저장 공간 등 더 많은 자원을 추가하여 성능을 향상시키는 방법입니다.
- 장점: 초기 단계에서 트래픽 증가에 대응하기 쉽고, 코드 변경이 필요 없는 경우가 많습니다.
- 단점: 물리적 한계가 있으며, 비용이 빠르게 증가하고, 단일 서버 장애 시 전체 앱이 다운됩니다.
- 주의사항: N+1 쿼리, 메모리 누수 등 실제 병목 현상을 먼저 파악해야 합니다.
- 수평 스케일링 (Horizontal Scaling):
- 여러 대의 서버에 Rails 앱을 동시에 실행하여 워크로드를 분산하는 방법입니다. 로드 밸런서를 통해 트래픽을 효율적으로 분배합니다.
- 장점: 시스템의 복원력과 처리량을 크게 향상시키며, 한 서버에 문제가 발생해도 다른 서버가 서비스를 유지합니다.
- 단점: 설정이 더 복잡하고, 앱을 무상태(stateless)로 만들어야 하며, 로드 밸런서 등 인프라 지식이 필요합니다.
- 가장 효과적인 Rails 앱 스케일링 방법으로 간주됩니다.
스케일링 모범 사례
-
프로파일링: Skylight, rack-mini-profiler, New Relic 등으로 병목 현상 식별.
-
무상태(Stateless) 앱 및 서버 추가: 세션 등을 Redis나 DB에 저장하여 무상태로 만들고, 로드 밸런서 뒤에 앱 서버를 추가합니다.
-
백그라운드 작업 활용: Sidekiq 등으로 이메일 발송, PDF 생성 등 무거운 작업을 오프로드하여 앱 응답성 유지.
-
캐싱: Rails 내장 캐싱 또는 Redis를 사용하여 렌더링된 뷰, 부분 뷰, 비싼 쿼리 결과 등을 캐싱합니다. SQL 캐싱도 활용.
-
데이터베이스 최적화: 적절한 인덱스 추가, N+1 쿼리 방지(eager loading),
EXPLAIN ANALYZE활용. 읽기 복제본(read replicas) 사용 고려. -
CDN 활용: 이미지, JavaScript, CSS 등 정적 자산을 Cloudflare, Amazon CloudFront 같은 CDN으로 오프로드하여 지연 시간 감소 및 서버 부하 경감.
-
코드 핫스팟 수정: New Relic 등으로 느린 엔드포인트나 메모리 소모가 큰 기능 식별 및 수정.
-
자동 스케일링: AWS, Heroku, Kubernetes 등에서 자동 스케일링을 활성화하여 실시간 수요에 따라 인프라를 확장/축소.
-
모든 것 모니터링: Datadog, Prometheus 등으로 앱 성능, 오류, 인프라 사용량 실시간 추적.
-
부하 테스트: JMeter, k6 등으로 실제 트래픽 시뮬레이션하여 문제점 사전 식별.
-
종속성 및 Ruby/Rails 버전 최신 유지: 최신 버전은 성능 개선을 포함하므로 정기적인 업그레이드 중요.
일반적인 스케일링 실수
-
수직 스케일링에만 의존: 단기적인 해결책이며, 결국 성능 한계에 도달합니다.
-
모니터링 부재: 서버 부하 및 잠재적 문제 추적을 위한 모니터링 도구 없이는 문제 파악이 어렵습니다.
-
데이터베이스 건강 무시: 인덱스 누락, 읽기 복제본 미설정, N+1 쿼리, 연결 풀 관리 미흡 등은 성능을 급격히 저하시킵니다.
-
부하 테스트 미실시: 실제 트래픽이 발생하기 전에 문제점을 발견하지 못합니다.
초기 아키텍처 고려사항
-
모듈화되고 유지보수 가능한 코드 작성: 논리를 작은 재사용 가능한 부분으로 분리합니다.
-
도메인 주도 설계(DDD) 유지: 실제 비즈니스 로직을 중심으로 앱을 구축하여 유연성 확보.
-
서비스 객체, 작업, 직렬 변환기 현명하게 사용: 컨트롤러/모델에서 무거운 논리를 분리하고, 시간 소모적인 작업을 백그라운드 작업으로 이동, API 응답은 깔끔하게 유지합니다.
-
필요시 서비스 기반 아키텍처 또는 마이크로서비스 사용: 모놀리스로 시작하되, 병목 현상이 발생하는 부분을 독립적인 서비스로 분리하여 개별적으로 확장/배포합니다.