Persona의 Rails 아키텍처 진화: 유연성, 확장성, 다양성을 위한 여정

RailsConf 2025 Simplifying at Scale: 7 Years of Rails Architecture at Persona by Alex Coomans

작성자
Ruby Central
발행일
2025년 07월 24일

핵심 요약

  • 1 Persona는 Google App Engine에서 Kubernetes로 인프라를 전환하고, MySQL 데이터베이스를 샤딩하며 Rails 애플리케이션의 확장성과 유연성을 확보했습니다.
  • 2 Rails의 기본 기능(Asset Pipeline, 데이터베이스 연결)을 활용하되, Webpacker에서 Vite로, Active Storage에서 Shrine으로 전환하는 등 필요에 따라 외부 도구를 적극 도입하며 진화했습니다.
  • 3 복잡성 관리를 위해 '스택'이라는 단일 통합 배포 모델을 도입하여 각 고객 환경을 격리하고, 글로벌 분산 MongoDB 기반의 라우팅 테이블로 효율적인 요청 처리를 구현했습니다.

도입

본 발표는 Persona의 소프트웨어 엔지니어 Alex Cumins가 회사의 정체성 플랫폼을 지원하기 위해 Rails 아키텍처가 지난 7년간 어떻게 진화해왔는지 설명합니다. 초기 Rails 애플리케이션에서 시작하여 유연성, 확장성 및 다양한 사용 사례를 지원하기 위한 아키텍처의 변화 과정을 다룹니다. 특히 Google App Engine에서 Kubernetes로의 인프라 전환, 데이터베이스 샤딩, 그리고 복잡성 관리 전략에 중점을 둡니다. Persona는 온보딩, 규정 준수, 사기 방지 등 다양한 산업 분야의 고객 요구사항을 충족하기 위해 고도로 적응 가능한 플랫폼을 구축해야 했습니다.

Rails 아키텍처의 진화와 도전

1. 초기 Rails와 프론트엔드 변화

  • 시작: Persona는 Rails 5.2 기반으로 Google App Engine에서 시작하여 빠른 개발과 배포를 가능하게 했습니다.
  • Asset Pipeline: Rails 3.1에서 도입된 Asset Pipeline은 자산 관리의 혁신을 가져왔습니다. 초기에는 Sprockets, CoffeeScript, Sass, jQuery를 사용했습니다.
  • 프론트엔드 생태계 폭발: Ember, Angular.js, React와 같은 프레임워크의 등장으로 Rails는 API 제공자 역할로 전환되었고, 프론트엔드 빌드 시스템과의 통합이 중요해졌습니다.
  • Webpacker 도입과 한계: Rails 5.1에서 Webpacker가 옵션으로, Rails 6.0에서 기본으로 도입되어 React 및 TypeScript와 같은 현대 프론트엔드 툴링을 연결했습니다. 그러나 복잡성 증가, 느린 피드백 루프, 새로운 도구 지원 지연으로 인해 Webpacker는 한계에 부딪혔습니다.
  • Vite로의 전환: 결국 Webpacker에서 Shakaacker를 거쳐 빠르고 유연한 최신 JavaScript 빌드 도구인 Vite로 전환했습니다. 이는 Rails의 기본 설정을 넘어서는 유연성의 필요성을 보여줍니다.

2. 인프라 확장: App Engine에서 Kubernetes로

  • App Engine의 한계: 초기에는 속도와 단순성을 제공했지만, 서비스 구조 및 다중 서비스 배포의 유연성 부족, 수동 스케일링의 비효율성 등의 한계에 도달했습니다.
  • Kubernetes (GKE)로의 마이그레이션: Google Cloud의 관리형 Kubernetes 서비스인 GKE로 전환하여 클러스터 프로비저닝, 업그레이드, 노드 관리의 부담을 줄이면서도 상당한 운영 제어권을 확보했습니다.
  • App Engine vs. Kubernetes 비교:
    • 컴퓨팅: App Engine은 완전 관리형이었으나, Kubernetes는 컨테이너 및 노드 수명 주기를 직접 관리합니다. Kubernetes는 GPU와 같은 특수 컴퓨팅 리소스를 지원하여 Persona의 문서 분석, 생체 인식 매칭 등에 필수적이었습니다.
    • 스케일링: App Engine은 CPU 사용률 및 동시 요청 기반 스케일링만 제공했지만, Kubernetes는 시스템 및 사용자 정의 지표를 통한 세밀한 수평/수직 스케일링 및 사용자 정의 스케일링 로직을 지원합니다. 이는 Sidekiq과 같은 백그라운드 작업 처리에 특히 중요했습니다.
    • 네트워킹: App Engine은 완전 관리형이었으나, Kubernetes는 서비스, 인그레스, 게이트웨이 등을 통해 복잡한 로드 밸런싱 전략을 구축할 수 있지만, DNS, TLS, 헬스 체크, 방화벽 규칙 등을 직접 관리해야 합니다.
    • 관측성(Observability): App Engine은 Google Cloud 모니터링 도구와 자동 통합되지만, Kubernetes는 Prometheus, DataDog와 같은 도구를 자유롭게 선택하여 통합해야 합니다.
  • 복잡성 증가: Kubernetes로의 전환은 유연성과 제어권을 제공했지만, 네트워킹, 관측성, 배포 워크플로우 등 스택의 모든 부분에 대한 소유권을 요구하며 운영 복잡성을 증가시켰습니다.

3. 데이터베이스 확장: MySQL 샤딩

  • 데이터베이스 압박: 제품 및 고객 기반이 성장함에 따라 MySQL 클러스터에 대한 압박이 커졌습니다.
  • MySQL 샤딩 도입: 2022년 중반, MySQL 클러스터에 대한 압박을 해결하기 위해 애플리케이션 샤딩을 시작했습니다. 데이터 상주 요구사항에 따라 유럽에 세 번째 샤드를 추가하는 등 6개월 만에 1개 지역 1개 데이터베이스에서 2개 지역 3개 샤드로 확장했습니다.
  • 데이터 스토어 구성: MySQL, MongoDB (기본), Elastic Search (검색 및 인덱싱), Redis (캐싱, Sidekiq 큐)를 혼합하여 사용합니다.
  • Rails의 샤딩 지원:
    • Rails 6.0: 모델별 데이터베이스 연결 설정을 지원하는 수직 샤딩(vertical sharding)을 도입하여 특정 모델 또는 읽기/쓰기를 다른 데이터베이스 인스턴스로 라우팅할 수 있게 했습니다.
    • Rails 6.1: 동일한 테이블의 행을 여러 데이터베이스에 분할하는 수평 샤딩(horizontal sharding)을 기본으로 지원하기 시작했습니다.
  • 샤딩 구현의 도전: connected_to 헬퍼는 샤딩의 필수 구성 요소이지만, 애플리케이션 전체에 샤드 컨텍스트를 일관되게 전달하는 데 어려움이 있었습니다. 웹 요청의 경우 API 키와 같은 라우팅 키를 중앙 집중식 조회 테이블에 매핑하여 올바른 샤드로 요청을 보내야 했습니다. 이 조회 테이블은 높은 처리량, 낮은 지연 시간, 높은 가용성을 요구하며, Persona는 이를 MongoDB 기반으로 구축하여 전역적으로 분산된 읽기 복제본을 통해 사용자에게 가까운 곳에서 라우팅 조회를 처리합니다. 현재 이 테이블에는 10억 개 이상의 항목이 있습니다.
  • 대규모 MySQL 테이블 관리:
    • 느린 쿼리, 어려운 마이그레이션, 예측 불가능한 쿼리 계획, 스키마 변경 시 운영 위험 등의 문제가 발생했습니다.
    • Percona의 pt-online-schema-change나 Shopify의 LHM(Large Hadron Migrator)과 같은 도구를 사용했지만, 매우 큰 테이블의 경우 마이그레이션에 몇 주가 걸릴 수 있습니다.
    • MySQL 8.0의 인스턴트 DDL(Data Definition Language) 작업은 일부 개선을 제공하지만, 인덱스 생성 등은 여전히 전체 테이블 재구축을 요구합니다. 따라서 데이터 액세스 패턴을 중심으로 스키마를 설계하는 것이 중요합니다.
    • 백필(backfill) 작업은 MySQL 버퍼 풀의 핫 페이지를 제거하거나 인덱스 통계를 변경하여 예측 불가능한 성능 저하를 유발할 수 있습니다.
  • Active Storage의 문제: Persona에서 가장 큰 두 테이블은 Active Storage의 BlobsAttachments로, 각각 약 34억 개의 행을 가집니다. 이들은 크고, 자주 접근되며, 변경하기 어렵습니다. Persona는 이러한 문제를 해결하기 위해 더 가볍고 모듈화된 접근 방식을 사용하는 Shrine으로 마이그레이션 중입니다.
  • 데이터베이스의 보편적 교훈: MongoDB의 핫 샤드, Elastic Search의 인덱스 튜닝, Redis 샤딩 등 모든 데이터 스토어는 규모가 커지면 고유한 운영 과제를 안겨줍니다. 관리형 서비스라도 세심한 계획, 지속적인 튜닝, 겸손한 자세가 필요합니다.

4. 단순성으로의 회귀: Stacks 아키텍처

  • 복잡성 관리: Persona는 그동안의 경험을 바탕으로 복잡성을 통합하고 단순화하기 위해 ‘스택(Stacks)’이라는 새로운 아키텍처를 도입했습니다.
  • 단일 통합 배포 모델: 스택은 여러 클러스터, 데이터베이스에 분산된 복잡성을 단일 자립형 단위로 정의하고, 이 단위를 반복적으로 복제하여 전체 플랫폼을 실행하는 방식입니다.
  • 유사 싱글 테넌시: 각 스택은 여러 테넌트를 호스팅할 수 있지만, 스택 간에는 강력한 경계가 존재합니다. 이를 통해 격리, 영향 범위 축소, 운영 유연성과 같은 싱글 테넌시의 이점을 얻으면서도 고객별 환경을 구축하는 오버헤드를 줄일 수 있습니다.
  • 코어 서비스: 라우팅 테이블과 같은 일부 핵심 기능은 모든 스택에서 공유되는 ‘코어’ 시스템으로 중앙 집중화됩니다.
  • 엣지 라우팅: 사용자에게 가장 가까운 엣지 위치에서 코드를 실행하여 들어오는 요청을 검사하고, 로컬 캐시 또는 MongoDB 기반의 전역 조회 테이블을 통해 올바른 스택으로 즉시 라우팅합니다. 이는 고객 구현에 변경 없이 백엔드 인프라를 확장하는 원활한 진화를 가능하게 합니다.

결론

Persona의 여정은 복잡성이 불가피하지만, 이를 어디에 둘지 의도적으로 선택할 수 있음을 보여줍니다. Rails는 훌륭한 기본값을 제공하지만, 이러한 기본값을 제약으로 여기지 않고 필요할 때 외부 도구를 과감히 도입하는 유연성이 중요합니다. 단순화할 수 있는 부분은 단순화하고, 반드시 확장해야 하는 부분은 확장하며, 필요할 때는 고정관념을 깨고 새로운 접근 방식을 시도해야 합니다. 궁극적으로 Persona는 '스택' 아키텍처를 통해 복잡성 속에서 단순성을 되찾고, 확장 가능한 백엔드 인프라를 고객에게 투명하게 제공하는 데 성공했습니다. 이러한 교훈은 Rails를 확장하는 모든 개발자에게 유용한 통찰력을 제공합니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!