메뉴

문서정보

목차

원문 : https://engineering.videoblocks.com/web-architecture-101-a3224e126947

소개

웹 서비스를 개발 할 때 참고 할 수 있는 기본 아키텍처다. 실 서비스에서는 구성요소들이 복잡해지고 다뤄야 하는 내용이 깊어질 수는 있겠지만 많은 서비스들은 여기에서 소개한 아키텍처를 확장 & 응용하는 것만으로 큰 틀을 잡을 수 있을 것이다.

 기본 아키텍처

위 그림은 운영중인 joinc 웹 서비스 아키텍처를 묘사하고(개인 사이트이기 때문에, 실제 위의 구성요소를 모두 가지고 있지는 않다. 설명을 쉽게 하기 위한 장치라고 보면 되겠다) 있다. 경험있는 웹 개발자가 아니라면 매우 복잡하게 보일 수도 있을 것이다. 각 구성요소들에 대해서 자세히 설명하려고 한다. 일단 서비스를 간단히 묘사해보자.

Joinc는 위키 서비스를 제공한다. 유저는 텍스트 뿐만 아니라 이미지도 함께 업로드 할 수 있다.

유저는 웹 브라우저를 이용해서 위키 서비스를 이용할 것이다. 유저가 "AWS 아키텍처 모델"와 같은 검색어를 입력하면, 검색결과로 https://www.joinc.co.kr 가 첫 페이지에 노출될 것이다. 유저가 URL 링크를 클릭하면 사이트에 접속하게 된다. 인터넷 상에서 서비스는 "IP 주소"로 찾을 수 있기 때문에 www.joinc.co.kr 도메인에 대한 IP 주소를 찾아야 한다. 웹 브라우저는 DNS 서버에 www.joinc.co.kr의 IP 주소를 질의 한다.

유저의 요청은 로드밸런서(Load balancer)로 향한다. 로드밸런서 밑에는 10개의 웹 애플리케이션 서버가 있는데, 이중 하나에 요청을 전달한다. 웹 서버는 캐싱서비스에서 유저 세션, 이미지 등 일부 정보를 가져온다. 캐싱 서비스에서 충분한 정보를 얻지 못했을 경우 데이터베이스에서 정보를 가져온다.

유저는 문서와 이미지 업로드를 요청하는데, 업로드 이미지에 대한 섬네일 이미지를 만드는 작업은 시간이 좀 걸린다. 나는 이 작업을 비동기로 진행하기 원했기 때문에 job queue에 이미지의 위치등을 포함한 작업 정보를 밀어 넣었다. Job worker는 총 5대가 작동하고 있는데, 이 중 하나가 job queue에서 작업을 꺼내서 이미지 작업을 수행한다. 이미지 작업이 끝나면 작업결과를 데이터베이스에 업데이트 한다.

Joinc는 유저가 올린 이미지의 검색을 위한 풀 텍스트 검색서비스도 제공한다. 이미지가 포함된 문서의 텍스트와 이미지의 타이틀과 설명문을 색인한다. 마지막으로 서비스에서 발생한 모든 로그는 data firehose를 통해서 클라우드 스토리지 시스템에 저장되며, 다른 한편으로 Date warehouse 에 저장된다. DW에 저장된 데이터는 비지니스 정보를 분석하는데 사용하며 클라우드 스토리지에 저장된 데이터는 Hadoop MapReduce 등을 이용한 데이터분석 혹은 기계학습 등에 사용하게 될 것이다.

이제 서버는 유저 브라우저에 응답으로 HTML 문서를 반환한다. 이 HTML 문서는 자바스크립트와 CSS, 이미지 파일 정보를 포함하고 있다. 이들 파일은 클라우드 스토리지에 저장돼 있으며, 이 앞단에는 CDN이 놓여있다. 유저 브라우저는 클라우드 스토리지에 요청하지 않고 CDN에 요청을 한다. HTML, CSS, 이미지등을 모두 로딩한 브라우저는 페이지를 렌더링해서 화면에 출력한다.

지금까지 Joinc의 서비스가 어떻게 이루어지는지 간단하게 살펴봤다. 이제 각 컴포넌트들을 자세히 살펴볼 것이다.

DNS

DNS는 Domain Name Server의 줄임말로 WWW(world wide web)을 위한 핵심 서비스 중 하나다. 기본적으로 DNS는 도메인이름(google.com 같은)과 도메인이름에 대한 IP(85.129.83.120 같은)로 이루어진 key/value 데이터베이스 시스템이다. 웹 브라우저와 같은 클라이언트 애플리케이션들은 이 데이터베이스를 조회해서 서비스를 받을 경로를 설정 할 수 있다. 도메인 이름과 IP 주소의 차이는 "yundream 에게 전화걸기" 와 "201-867-5309 로 전화걸기"의 차이다. yundream의 전화번호를 찾기 위해서 전화번호부가 필요 했던 것처럼, 도메인에 대한 IP 주소를 조회하기 위해서 DNS가 필요하다. 인터넷의 전화번호부라고 생각하면 된다.

DNS에 대한 자세한 내용은 DNS문서들을 참고하자.

로드밸런서

로드밸런싱을 다루기 전에 수평적 확장(horizontal scaling)과 수직적 확장(vertical scaling)를 살펴봐야 할 것 같다.

수평적 확장은 리소스 풀에 새로운 컴퓨터를 추가해서 처리량을 늘리는 방식이다. 수직적 확장은 컴퓨터의 CPU나 메모리 등을 업그레이드해서 처리량을 늘리는 방식이다.

 수직적 확장 vs 수평적 확장

구성이 단순하면서, 장애에 대한 강한 내성을 가지고 있기 때문에 나는 거의 언제나 수평적 확장을 사용한다. 서버의 장애는 예측 할 수 없다. 네트워크는 단절될 수도 있고 느려질 수도 있다. 때때로 데이터센터 전체가 다운될 수도 있다. 두 개 이상의 서버를 사용하면 애플리케이션이 계속 실행되도록 할 수 있다. 즉, 다른 장치 없이 서버를 하나 더 두는 것만으로 내결함성(Fault tolerant)를 확보할 수 있다. 수평적확장을 사용 할 경우 백앤드 애플리케이션을 여러 작은 단위(웹 서버, 데이터베이스, 기타 다른 서비스)로 쪼개서 다른 서버에서 실행하도록 구성할 수 있다. 결정적으로 수직적 확장으로는 현대 인터넷 서비스가 요구하는 계산을 수행 할 수가 없다. 구글과 같은 큰 회사 뿐만 아니라, 그다지 크지 않은 규모의 회사도 마찬가지다. 예를 들어 joinc 같은 경우에도 30-40대의 AWS EC2 인스턴스를 사용하며, 특정 시점에서는 150 ~ 300개가 넘는 인스턴스를 사용한다. 수직적 확장으로는 이러한 컴퓨터 성능을 제공할 수 없다.

로드밸런서 이야기를 해보자. 로드밸런서는 수평적 확장을 가능하게 해주는 도구다. 들어오는 클라이언트의 요청을 여러 개의 복제된(같은 일을 하는) 애플리케이션 서버에 라우팅하고, 처리된 결과를 그대로 클라이언트에게 전달한다. 요청에 대한 처리가 분산되기 때문에, 애플리케이션 서버를 늘이는 것으로 부하를 분산 할 수 있다.

 로드밸런서

이게 전부다. 개념적으로 로드밸런서는 아주 간단하다. 물론 좀 더 들어가면 L3, L4, L7 로드밸런서등 각 분야별로 다뤄야 할 것들이 많긴 하지만 여기까지만 다루겠다. 요즘에는 AWS 같은 클라우드 서비스를 이용하는 경우가 많은데, 버튼 클릭 몇번으로 로드밸런서를 구축 할 수 있다. 개념만 알고 나중에 메뉴얼 보면서 설정해도 될 정도로 쉬워졌다.

웹 애플리케이션 서버

웹 애플리케이션 서버는 사용자의 요청을 처리하고 그 결과를 HTML 문서 형태로 변환해서 유저의 브라우저로 보내는 핵심 비니지스 로직을 실행한다. 이런 작업들을 수행하기 위해서 데이터베이스, 캐싱 시스템, 작업 큐, 메시지큐, 검색 서비스, 로그 큐 등 다양한 백앤드 인프라와 통신을 한다.

애플리케이션 서버는 Node.js, Ruby, PHP, Scala, Java, C#, .Net, GoLang와 같은 언어 혹은 Ruby on Rails, Java Spring, Django 등의 프레임워크로 개발한다. 이들 언어와 프레임워크에 대한 것은 이 문서의 범위가 아니므로 다루지 않겠다.

데이터베이스 서버

모든 현대적인 웹 애플리케이션은 하나 혹은 두 개 이상의 데이터베이스를 활용해서 정보를 저장한다. 데이터베이스를 이용해서 서비스를 위한 데이터 구조를 정의하고, 데이터를 삽입하고, 찾고 수정하고 삭제하는 등의 작업을 수행한다. 대부분의 경우 웹 애플리케이션 서버는 데이터베이스와 직접 통신한다. 때때로 웹 애플리케이션 서버와 격리된 자체 데이터베이스와 데이터 조회 기능을 제공하는 애플리케이션이 있을 수 있다.

데이터베이스를 조작하기 위한 SQL이라는 언어가 있다. SQL은 구조화된 질의 언어(Structured Query Language)의 약자로 1970년대 관계형 데이터베이스에 질의 하는 표준적인 방법을 제공하기 위해서 만들어졌다. SQL 데이터베이스는 (일반적으로 숫자로 된) common ID를 통해서 연결된다. 아래는 유저의 주소 정보를 저장하는 간단한 예제다.

 셈플 테이블

 셈플 테이블

웹 애플리케이션을 개발하려고 한다면, 데이터베이스와 테이블의 관계를 이해하고 SQL을 사용 할 수 있어야 한다. 요즘에는 SQL을 질의어로 사용하지 않는 "NoSQL" 데이터베이스도 널리 사용하고 있기는 하지만, 그런 경우라고 하더라도 SQL 기반의 데이터베이스는 필수적으로 사용하고 있을테니 반드시 익히는게 좋을 것이다. SQL을 배우고 싶다면 khan academy에 있는 SQL basics 코스를 추천한다.

NoSQL은 "Non-SQL"의 줄임말이다. NoSQL은 대규모의 웹 애플리케이션에서 대량의 데이터의 처리를 도와주기 위해서 개발된 (비교적)새로운 데이터베이스 기술 세트다. 오랜기간 검증된 SQL 솔류션이 있지만 이들 SQL 솔류션은 수평적 확장에 한계가 있어서 웹 스케일 서비스에 사용하기에는 적당하지 않다.

유명한 NoSQL 솔류션으로는 MongoDB와 DynamoDB, CouchBase, Hadoop, Cassandra 등이 있다. 아래의 문서들을 참고하자. SQL은 명백한 사용상의 장점이 있기 때문에, NoSQL 솔류션들도 인터페이스로 SQL을 제공하는 경우가 많다. 어쨋든 SQL은 배워야 한다.

캐싱

보통 캐싱 서비스는 간단한 key/value 데이터 스토어로 개발한다. key/value 데이터베이스는 데이터를 찾는데 단지 O(1)시간만 쓰면된다는 장점이 있다. 응용 프로그램은 캐싱 서비스를 이용해서 값비싼 계산 결과를 저장한다. 그리고 다음번에 동일한 계산이 필요한 요청이 들어오면 새로 계산하는 대신에 이미 계산된 결과를 검색해서 제공 한다. 응용 프로그램은 데이터베이스 쿼리, 외부 서비스 호출, 특정 URL의 HTML 결과등을 캐시 할 수 있다.

현대 인터넷 서비스에서 성능을 높이기 위해서 우선 시도하는 것은 "캐싱"이다. 코드와 알고리즘의 최적화는 (버그 수준으로 잘못 만들어진 코드가 아닌 이상에는)그 이후에 시도 한다. 캐싱 서비스를 위해서 가장 멀리 사용하는 솔류션은 Redis와 Memcache 다.

Job Queue & Worker

모든 웹 애플리케이션은 비동기적인 작업을 필요로 한다. 비동기 작업은 요청과 응답이 분리되는 것을 의미한다. 클라이언트가 요청한 작업이 비동기 작업인 경우, 유저는 그 작업이 언제 끝날지 알 수 없다. 왜냐하면 서버는 요청을 받으면 즉시 리턴하고, 백앤드에서 작업을 수행하고 그 결과를 클라이언트에게 통지하는 방식을 사용하기 때문이다.

유튜브와 같은 동영상 서비스를 한다고 가정해보자. 유저가 동영상을 올리면, 동영상을 받은 서버는 모바일과 데스크탑 혹은 노트북 환경에 맡게 변환하고 검색 할 수 있도록 색인한다. 어떤 영상인지 알 수 있도록 영상클립을 만들기도 한다. 이러한 작업은 많은 시간이 걸릴 수 있고, 유저에게 무작정 기다리라고 할 수는 없다. 애플리케이션 서버는 "요청 잘 받았다. 처리가 끝나면 알려 줄테니 다른 일을 하라고" 응답한다.

이러한 비동기 작업을 수행하기 위한 다양한 아키텍처가 있지만 "작업 큐"를 이용한 아키텍처를 가장 널리 사용하고 있다. 이 아키텍처는 1. 실행해야 할 작업 명세서의 목록을 가지고 있는 작업큐 와 2. 작업 큐에서 작업을 읽어서 작업을 수행할 하나 이상의 서버(보통 Worker라고 부르는)로 구성된다.

작업 큐는 작업요청 명세 목록을 저장한다. 가장 간단한 구현은 FIFO(First-in-first-out 혹은 선입선출)이지만 많은 애플리케이션들이 일종의 (중요한 작업을 먼저 처리하는)우선 순위 대기열 처리 시스템을 가지고 있다.

워커(worker)는 작업 큐에서 명세서를 꺼내서 읽은 다음 작업을 수행한다. 작업 수행이 끝나면 작업 큐에 또 다른 작업이 있는지 폴링하면서 확인 한다.

 Job Queue

작업 큐에 저장되는 작업명세서는 결과적으로 워커에게 보내는 메시지이므로 "메시지 큐"라고 부르는 애플리케이션들을 작업큐로 사용한다. RabbitMQ, AWS SQS, Apache ActiveMQ, Celery등의 다양한 소프트웨어들이 있다. Redis 같은 큐를 제공하는 소프트웨어도 작업 큐 용도로 사용 할 수 있다.

Full-text Search Service

대부분의 웹 애플리케이션들이 사용자가 입력한 텍스트(검색어라고도 함)를 기반으로 가장 관련성이 높은 결과를 반환하는 검색기능을 지원하지 않는 경우가 많다. 이러한 검색 서비스를 위해서는 Invert Index를 활용한 색인 기술이 필요하다. 이 기술을 이용하면 검색어를 포함한 문서를 빠르게 검색 할 수 있다.

 Inverted Index

예제는 이미지의 title로 색인을 하고 있다. "In", "the", "with"와 같은 일반적인 단어(stop word)는 invert index에 포함하지 않는다. 유저가 "mountains"를 검색어로 입력하면 1, 2 문서가 검색된다. 만약 "man mountains"를 검색어로 입력하면 1 문서가 검색될 것이다. 굉장히 단순화 했지만 대략 어떻게 색인되고 검색되는지 감은 잡을 수 있을 것이다.

Full text Search 서비스에 대한 요구가 많아지면서 MySQL 같은 RDBMS도 full-text search를 지원하고 있다. 하지만 Elasticsearch 나 Apach Solr 같은 전문 검색엔진을 사용하는 추세다. AWS를 사용하고 있다면 AWS ElasticSearch나 AWS CloudSearch 같은 서비스를 이용해서 간단하게 검색서비스를 만들 수 있다.

Serices

서비스가 일정 규모에 도달하면, 별도의 애플리케이션으로 실행되는 서비스를 구성하기 시작한다. 이러한 애플리케이션은 인터넷에 직접 노출되는 않고 내부에 있는 다른 애플리케이션들과 상호작용 한다. Joinc는 아래와 같은 서비스들을 운용하고 있다.

Data

빅 데이터의 시대다. 데이터를 얼마나 잘 활용하는지에 따라서 기업이 성장할 수도 그렇지 않을 수도 있다. 요즘에는 모든 서비스들이 특정 규모에 도달하면 데이터를 수집, 저장 및 분석 할 수 있도록 데이터 파이프라인을 구축한다. 일반적으로 데이터 파이프 라인 구축에는 3가지의 단계가 있다.
  1. 애플리케이션 데이터(일반적으로 유저의 행동에 대한 이벤트)를 수집하고 처리 할 수 있는 데이터 Firehose를 만든다. 원시 데이터가 그대로 전달될 수도 있지만 원시 데이터를 가공하거나 보강해서 다른 Firehose로 전달하기도 한다. AWS Kinesis와 Kafka가 이런일을 하는 대표적인 기술이다.
  2. 원시데이터와 가공된 데이터들은 클라우드 스토리지에 저장된다. AWS라면 S3에 저장할 것이다.
  3. 가공된 데이터들은 분석을 위해서 DW(data warehose)에 로드된다. 오라클, AWS Redshift 등을 사용 할 수 있다. 데이터가 매우 크다면 분석을 위해서 Hadoop MapReduce 기술이 필요할 수도 있다.
아키텍처 다이어그램에는 나와있지 않지만 Joinc는 매일 새벽 주기적으로 S3에 저장된 데이터를 읽어서 분석하고 그 결과를 데이터베이스에 기록한다. 이 데이터를 이용해서 비지니스 정보를 얻거나 유저 추천 서비스를 제공한다.

Cloud storage

데이터 양이 크게 늘어나고 있다. 이 데이터는 애플리케이션 서버들 혹은 클라이언트에서 직접 접근 할 수 있어야 한다. 따라서 대용량의 공유 가능하며, 안전한 스토리지가 필요하다. 유저 애플리케이션 혹은 서버 애플리케이션은 HTTP(S)를 통해서 사용할 수 있어야 한다. AWS의 S3는 현재 가장 널리 사용하는 클라우드 스토리지다. Joinc는 여기에 이미지, 동영상, CSS, 자바스크립트, HTML 템플릿, 유저 이벤트 데이터등을 저장하고 있다.

CDN

CDN은 Content Delivery Network의 약자이며, 원본 서버에서 제공하는 것보다 훨씬 빠르게 CSS, Javascript, 이미지, 동영상 등을 제공하기 위해서 사용 한다. CDN은 전 세계에 컨텐츠를 배포할 수 있는 Edge 서버를 제공한다. 유저는 자신의 위치와 가까운 곳에 있는 CDN Edge에서 컨텐츠를 다운로드 할 수 있다.

 CDN

일반적인 인터넷 서비스들은 대부분 CDN을 이용해서 CSS, Javascript, 이미지, 동영상 등을 배포한다. 서비스를 국내로 한정 할 경우에는 CDN 없이도 서비스를 할 수 있겠으나 글로벌 서비스라면 반드시 사용해야 한다.