본문으로 건너뛰기

Appendix. 웹 서비스 구축에 필요한 실전 기술

1강. 작업큐 시스템

웹 서비스와 요청

웹 서비스에서는 기본적으로 요청이 동기적으로 실행됩니다. 즉, 요청에 기인하는 모든 처리가 끝난 다음에 응답이 반환됩니다.

양호했던 성능도 시간이 지남에 따라 악화되고 서비스 사용자 경험에 영향을 주는 경우가 발생합니다. 이런 경우에 작업큐 시스템을 사용함으로써 나중으로 미뤄도 되는 처리를 비동기로 실행할 수 있고 사용자 경험도 개선할 수 있습니다.

예를 들면 하테나 북마크에서는 사용자가 URL을 북마크했을 때의 처리를 작업큐 시스템에서 처리하고 있습니다. 이에 따라 URL의 개요를 얻거나 키워드 추출, 카테고리 판정 등 나중에 처리해도 되는 작업을 비동기로 실행하고 있습니다.

작업큐 시스템 입문

웹 애플리케이션의 일부 처리를 가장 간단하게 비동기화하는 방법은 비동기화하고자 하는 처리를 독립된 스크립트로 해서 해당 스크립트를 애플리케이션 내부에서 호출하는 방법입니다. 이렇게 함으로써 아주 간단하게 작업처리를 비동기화할 수가 있습니다.

단, 이 방법에서는 스크립트 시작과 초기화의 오버헤드가 커서 성능이 좋지는 않습니다. 또한 일시적으로 대량의 비동기 처리를 실행시키려 하면 그 수만큼의 프로세스를 실행시키려고 하기 때문에 이것도 성능상 단점이 됩니다. 따라서 이 방법은 프로토타입이나 극히 소규모 애플리케이션에만 적용하는 게 좋을 것입니다.

어느 정도 양이 있는 비동기 처리를 안정적으로 수행하려면 작업큐(Job-Queue)와 워커(Worker)를 세트로 한 작업큐 시스템을 사용하는 것이 일반적입니다. 작업큐 시스템에서는 작업큐에 실행하고자 하는 처리(작업)를 등록하고, 워커가 큐에서 작업을 추출해서 실제로 처리합니다.

작업큐를 통해서 일시적으로 대량의 처리가 등록되었을 때 부하의 변동을 흡수할 수가 있습니다. 워커는 항상 실행해둠으로써 작업을 처리할 때 초기화 오버헤드를 거의 없앨 수 있습니다.

그림 A.1 작업큐 시스템의 기본적인 처리 흐름

  • 클라이언트(웹 애플리케이션): 작업을 투입합니다. 작업을 투입한 다음 처리를 계속 진행할 수 있습니다.
  • 작업큐: 작업을 쌓습니다.
  • 워커: 작업큐를 참조하고 미실행된 작업을 추출해서 작업을 실행합니다.

2강. 스토리지 선택

증가하는 데이터의 저장

수십GB, 수백GB, TB를 넘는 데이터를 다루는 스토리지는 약간의 구성 변경이나 액세스 패턴 변화로 예상 밖으로 응답속도가 저하되는 경우가 있습니다. 따라서 데이터량이나 스키마, 액세스 패턴에 맞는 스토리지를 선택하는 것은 대단히 중요합니다.

웹 애플리케이션과 스토리지

여기서 스토리지란 애플리케이션 데이터를 영속적으로 혹은 일시적으로 저장하기 위한 기능이라는 의미로 사용하고 있습니다. 한마디로 애플리케이션에서 다루는 데이터라고 해도 업로드된 디지털 카메라 사진 데이터나 블로그 본문과 같이 본질적으로 없어질 수 없는 원본 데이터부터, 원본 데이터를 가공함으로써 생성된 액세스 랭킹이나 검색용 인덱스 데이터 등 재생성 가능한 가공 데이터, 캐시와 같이 사라져도 성능상의 문제 이외에는 다른 문제가 없는 데이터까지 다양한 특성이 있습니다.

스토리지 선택의 전제가 되는 조건

우선 스토리지를 선택할 때에는 애플리케이션에서의 액세스 패턴을 이해하는 것이 중요합니다. 액세스 패턴으로는 아래 여섯 가지 지표가 선택의 중요한 판단 포인트가 됩니다.

  • 평균크기
  • 최대크기
  • 신규추가빈도
  • 갱신빈도
  • 삭제빈도
  • 참조빈도

스토리지의 종류

현재 사용가능한 스토리지를 큰 카테고리로 분류하면 다음과 같이 됩니다.

  • RDBMS
  • 분산 key-value
  • 분산 파일시스템
  • 기타 스토리지

RDBMS

RDBMS(Relational Database Management System)란 표 형식으로 데이터를 저장하고 대부분은 SQL 언어로 데이터 조작을 수행하는 시스템입니다. 다양한 데이터를 저장한다거나 강력한 질의를 할 수 있어서 가장 범용성이 높은 스토리지입니다.

RDBMS의 오픈소스 구현은 MySQL이나 PostgreSQL 등이 있으며, 둘 다 실제 운용환경에서 널리 사용되고 있습니다.

MySQL

MySQL의 아키텍처는 그림 A.2와 같이 되어 있으며, SQL을 해석해서 실행하는 기능 블록과 실제로 데이터를 보관하는 기능 블록이 분리되어 있다는 게 특징입니다. 후자는 스토리지 엔진이라 불리며, 다양한 종류가 개발, 구현되고 있습니다.

따라서 표준으로 제공되고 있는 것뿐만 아니라 제3자에 의해 구현된 스토리지도 비교적 간단하게 이용할 수가 있습니다.

그림 A.2 MySQL의 아키텍처

분산 key-value 스토어

key-value 스토어는 key와 value 쌍을 저장하기 위한 심플한 스토리지이고, 분산 key-value 스토어는 key-value 스토어에 네트워크를 지원함으로써 다수의 서버로 확장시키는 기능을 지닌 것입니다. key-value 스토어는 RDBMS에 비해 기능적으로는 부족하지만, 성능이 10~100배 이상이라는 게 특징입니다.

memcached

memcached는 심플한 구현의 분산 key-value 스토어로 분산 알고리즘을 클라이언트 라이브러리로 구현하고 있다는 점이 특징적입니다.

memcached는 앞서 언급했듯이, 메모리 상에서 동작하고 있으므로 매우 빠르지만 프로세스를 재시작하면 데이터가 모두 사라져버립니다. 따라서 원본 데이터 저장으로는 당연히 부적합하며, 재생성 시에 시간이 걸리는 가공 데이터 저장에도 적합하지 않은 경우가 있습니다. memcached의 특성을 가장 잘 활용할 수 있는 데이터는 캐시 데이터입니다.

전형적인 예로는 RDBMS에서 읽어들인 데이터를 일시적으로 저장해두고 또다시 참조할 때는 먼저 memcached를 참조해서 찾지 못한 경우에만 RDBMS를 참조하는 방법이 있습니다.

캐시로 한정할 경우에는 서버에는 메모리만 충분히 탑재해두면 되며, CPU나 I/O 성능은 그다지 요구되지 않습니다. 따라서 저가 하드웨어를 나열해서 대량의 캐시풀을 구축하고 고가 하드웨어를 요구하는 RDBMS 대수를 줄이는 구성이 가능해집니다.

분산 파일시스템

분산 파일시스템도 물론 스토리지의 유력한 후보가 됩니다. 분산 파일시스템은 파일시스템의 특성상 보통은 어느 정도 이상인 크기의 데이터를 저장하는 데 적합합니다. NFS와 같이 그것이 고려되고 있는 구현을 제외하고, 작은 데이터가 대량으로 존재하는 용도에는 적합하지 않은 경우가 많습니다.

분산 파일시스템은 다양한 구현이 있는데, 하테나에서 실제로 사용하고 있는 분산 파일시스템을 설명하도록 하겠습니다.

MogileFS

앞에서 소개했었는데, 분산 파일시스템과 묶어서 복습해두겠습니다. MogileFS는 비교적 작은 대량의 파일을 다룰 목적으로 Perl로 구현된 분산 파일시스템입니다.

아키텍처는 그림 A.4와 같이 메타데이터를 수용하는 RDBMS, 스토리지 서버, 사이를 연결하는 전송 서버로 구성됩니다. MogileFS는 대량의 수KB~수십MB 정도의 이미지 파일을 효율적으로 저장하기 위한 시스템입니다. 기본적으로 대부분의 데이터는 추가된 다음 갱신되지 않고 참조하기만 하는 용도에 적합합니다. 즉, 업로드 이미지 파일을 접수받는 웹 애플리케이션에 적합합니다.

스토리지 서버 상에서 개개의 파일은 실제 파일시스템 상에서도 하나의 파일로 저장됩니다. 통상 하나의 파일은 3중으로 다중화되어 일부 스토리지 서버가 고장 나서 데이터가 손실되더라도 시스템 전체로서는 계속 정상적으로 동작할 수 있도록 설계되어 있습니다.

파일 저장장소와 파일을 특정 짓기 위한 키와의 대응관계는 메타데이터로 RDBMS에 저장됩니다. 파일을 참조할 때는 통상의 파일시스템처럼 마운트하는 것이 아니라 WebDAV 프로토콜로 얻게 됩니다. 따라서 MogileFS를 사용할 경우에는 애플리케이션 측에 구현이 필요합니다.

그림 A.4 MogileFS

기타 스토리지

지금까지 소개한 것 외에도 다양한 스토리지가 존재합니다. 여기서는 하테나에서 사용한 적이 있는 NFS, WebDAV, HDFS에 대해 소개합니다.

NFS 계열 분산 파일시스템

오래 전부터 존재하던 분산 파일시스템으로 NFS가 있습니다. NFS는 특정 서버의 파일시스템을 다른 서버에서 마운트해서 해당 서버의 로컬 파일시스템과 마찬가지로 조작할 수 있도록 하는 기술입니다. 대부분의 UNIX 시스템에 구현되어 있으며, 간단하게 사용할 수 있다는 점이 특징입니다.

반면, 커널 레벨에서 구현되어 있는 경우가 많아서 서버 측에 장애가 발생하면 클라이언트의 동작도 덩달아서 정지해버리는 일도 발생합니다.

WebDAV 서버

앞서 언급했듯이, NFS의 프로토콜은 커널 계층에서 구현되어 있기 때문에 약간의 불안정함이 바로 장애로 이어지는 경우가 있습니다. 이런 경우에는 WebDAV 프로토콜을 지원하는 스토리지를 사용할 수도 있습니다. WebDAV는 HTTP를 기반으로 한 프로토콜로, 애플리케이션 계층에서 구현되는 경우가 많아서 보다 안정된 시스템을 구축할 수 있습니다.

다중화에 관해서는 NFS와 비슷한 어려움이 따라붙으므로 프로세스 간 데이터를 건네주는 것처럼 일시적으로 필요한 데이터를 저장하는 장소로는 적합합니다. WebDAV에 의한 스토리지도 일반적으로는 마운트할 수 없으므로 파일 조작을 위해서는 애플리케이션을 약간 손볼 필요가 있습니다.

HDFS

HDFS(Hadoop Distributed File System)는 그 이름에서 나타나듯이, 뒤에서 설명할 Hadoop용으로 설계된 분산 파일시스템입니다. HDFS에서는 파일을 64MB씩 분할해서 저장하고, 수백MB~수십GB의 거대한 데이터를 저장하는 것을 그 목적으로 하고 있습니다.

MapReduce처럼 거대한 파일을 저장하고 있으면서 한 번에 처리하려는 용도에 적합합니다.

3강. 캐시 시스템

웹 애플리케이션의 부하와 프록시/캐시 시스템

웹 애플리케이션의 부하가 서서히 증가해서 시스템 용량이 부족해졌을 때에는 AP 서버나 DB 서버를 증설함으로써 대응할 수도 있지만, HTTP 레벨의 캐싱을 수행하는 HTTP 가속기를 사용함으로써 낮은 비용으로 효과가 높은 대책을 세울 수 있습니다.

HTTP 액세스를 고속화하는 HTTP 가속기는 크게 포워드 프록시와 리버스 프록시, 2종류가 있습니다(그림 A.6). 포워드 프록시(Forward Proxy)는 클라이언트가 외부 서버에 액세스할 때 사이에 두는 프록시입니다.

반면, 리버스 프록시는 역으로 외부의 클라이언트가 내부 서버에 액세스할 때 사이에 두는 프록시입니다.

그림 A.6 HTTP 가속기

프록시에서는 요청에 대한 응답을 캐싱해둠으로써 다음에 같은 요청이 전달됐을 때 캐싱해둔 응답을 반환할 수가 있습니다. 이에 따라 대역이나 서버 리소스를 소비하지 않고 빠르게 요청을 처리할 수가 있습니다.

어느 정도 규모에 달한 웹 애플리케이션에서는 리버스 프록시를 이용한 캐시 서버를 효과적으로 이용함으로써 리소스 소비를 억제하면서 대량의 요청을 처리할 수 있게 됩니다.

4강. 계산 클러스터

대량 로그 데이터의 병렬처리

대규모 웹 서비스를 운영하다 보면 로그 데이터도 대량으로 쌓여갑니다. 대량으로 쌓인 로그 데이터의 처리는 이를 한 번에 읽어들이는 것만도 어려우며, 더욱이 통계처리나 분석을 하려고 하면 엄청나게 큰 계산 리소스를 필요로 합니다.

예를 들면 하테나 다이어리의 액세스 로그는 하루에 4GB 정도의 크기가 되고, 로그 1개월분을 처리하려고 하면 120GB의 로그를 처리해야 합니다. 만일 월간 고유 사용자를 계산하려고 하면 이 로그를 한 번에 처리하게 되는데, HDD에서 읽어들이는 성능을 평균 50Mbps라고 하면 읽어들이는 데만 5시간 이상 걸리게 됩니다.

이와 같은 처리를 빠르게 수행하기 위해서는 병렬처리가 가능한 계산클러스터가 필요합니다.

MapReduce의 계산모델

하테나에서는 계산 클러스터로 Hadoop이라는 MapReduce의 오픈소스 구현을 사용하고 있습니다. MapReduce란 Google이 2004년에 발표한 계산모델입니다.

MapReduce는 거대한 데이터를 빠르게 병렬로 처리하는 것을 목적으로 하며, 계산 시스템은 다수의 계산 노드로 구성된 클러스터와 대량 데이터를 분산해서 저장하기 위한 분산 파일시스템으로 구성됩니다.

MapReduce 계산모델은 key와 value 쌍의 리스트를 입력 데이터로 해서 최종적으로 value의 리스트를 출력합니다. 계산은 기본적으로 Map 단계와 Reduce 단계로 구성됩니다.

그림 A.10 MapReduce 계산모델

Map 단계는 먼저 마스터 노드에서 입력 데이터를 잘게 분할해서 각 노드로 분산합니다. 각 노드에서는 분할된 입력 데이터를 계산하고, 계산결과를 key와 value 쌍으로 구성된 중간 데이터로 출력합니다. Map 단계의 처리는 다음과 같이 나타낼 수 있습니다.

(k1, v1) -> list(k2, v2)

Reduce 단계에서는 먼저 Map 단계에서의 출력 데이터를 key(k2)별로 정리해서 key(k2)와 key에 대응하는 값의 리스트(list(v2))로 재구성합니다. 다음으로 각각의 key를 각 노드로 분산합니다. 이 과정을 Shuffle Phase라고도 합니다.

그 다음, 노드에 있는 key(k2)와 key에 대응하는 값의 리스트(list(v2))를 입력 데이터로 해서 각 리스트(list(v3))를 최종적인 출력 데이터로 하는 처리를 수행합니다. 최종적으로 각 노드에서 값의 리스트(list(v3))를 집약하면 계산이 완료됩니다.

이 MapReduce 계산모델에서는 Map과 Reduce라는 두 가지 처리를 수행하는 함수를 준비하는 것만으로 대량의 데이터를 빠르게 처리할 수 있게 됩니다. 얼핏 보면 단순한 처리밖에 할 수 없는 것처럼 생각할 수 있지만, 로그 분석, 검색엔진의 인덱스 생성 등 응용범위는 광범위합니다.

또한 MapReduce 계산모델의 실행에서는 대량의 입력 데이터를 읽어들이는 부분이 성능의 병목이 되는 경우가 많습니다. 따라서 MapReduce는 분산 파일시스템과 병용하는 것이 중요합니다.

Wrap Up

웹 서비스 구축에 필요한 실전 기술은 요청을 비동기화하는 작업큐, 데이터 특성에 맞춘 스토리지 선택, HTTP 레벨 캐싱, 그리고 대량 로그 분석용 계산 클러스터까지 폭넓게 걸쳐 있습니다. 각각은 성격이 다르지만, 결국 공통된 핵심은 부하를 분산하고 저장과 계산의 성격에 맞는 도구를 선택해 시스템 전체 효율과 응답성을 높이는 데 있습니다.

Summary

작업큐 시스템은 동기 요청에서 나중으로 미뤄도 되는 작업을 분리해 워커가 비동기로 처리하도록 함으로써 사용자 경험을 개선하고 부하 변동을 흡수합니다. 스토리지를 고를 때는 데이터의 평균·최대 크기와 추가·갱신·삭제·참조 빈도 같은 액세스 패턴을 먼저 봐야 하며, 그에 따라 RDBMS, 분산 key-value, 분산 파일시스템, 기타 스토리지 중 적합한 것을 선택해야 합니다. memcached는 매우 빠르지만 휘발성이므로 캐시에 적합하고, MogileFS나 HDFS는 파일 크기와 용도에 따라 다른 장점을 가집니다. 또한 리버스 프록시 기반 캐시 시스템은 HTTP 응답을 재사용해 낮은 비용으로 대량 요청을 처리할 수 있게 해줍니다. 마지막으로 대량 로그 분석처럼 단일 서버로 감당하기 어려운 계산은 Hadoop과 MapReduce 같은 계산 클러스터와 분산 파일시스템을 함께 사용해야 효율적으로 처리할 수 있습니다.

Reference