-
웹 아키텍처 101DEV/Back-end 2018. 10. 24. 22:28
웹 개발자 일을 시작할 때 미리 알고 있었다면 좋았을
기본적인 웹 아키텍처 개념들위 도표에 저희 Storyblocks 서비스에서 사용중인 아키텍처가 상당히 잘 표현되어 있습니다. 숙련된 웹 개발자가 아닌 이상 도표가 복잡하다고 느낄 수 있습니다. 아키텍처를 이루고 있는 컴포넌트를 하나하나 자세히 들여다 보기 전에, 아래의 내용을 먼저 읽어본다면 접근하기가 좀 더 쉬워질 것입니다.
한 사용자가 구글에 "숲에 내리쬐는 강렬하고도 아름다운 햇살과 안개"를 검색합니다. 맨 처음 나온 검색 결과는 우리의 선구적인 스톡 사진 및 벡터 그래픽 사이트인, Storyblocks 사이트에서 나온 결과입니다. 사용자가 검색 결과 링크를 클릭하면 브라우저가 이미지 상세 페이지로 리다이렉트 합니다. 그 내부를 들여다보면, 사용자의 브라우저는 DNS 서버에 Storyblocks와 접촉할 방법을 알아내기 위해 요청을 보내게 됩니다.
브라우저가 보낸 요청은 로드 밸런서(load balancer)가 받게 됩니다. 사이트를 운영하는 데 사용하는 웹 서버가 10개 있다면, 이 중 하나 정도를 로드 밸런서가 임의로 선택하여 요청을 처리하도록 일을 맡깁니다. 웹 서버는 캐싱 서비스에서 이미지에 관한 정보를 찾아보고 여기서 못찾은 나머지 정보는 데이터베이스에서 가져오는(fetch) 작업을 합니다. 이미지 컬러 프로파일 계산 작업이 아직 끝나지 않았기 때문에, "컬러 프로파일" job을 job 큐에 보냅니다. 그러면 job 서버가 비동기로 큐에 들어있는 작업을 실행하여 받아온 결과를 가지고 데이터베이스를 알맞게 업데이트 합니다.
그 다음에 사용자는 이와 비슷한 사진을 더 찾아보려고, 검색 결과로 받아 본 사진의 제목을 입력합니다. 이 요청은 전문 검색 서비스(full text search service)에 전달 됩니다. 검색을 하기 위해 사용자는 Storyblocks 사이트에 회원 로그인을 하려고 하기 때문에, 계정 서비스에서 이 사용자의 계정 정보를 찾아 보는 작업이 진행됩니다. 마지막으로, 페이지 뷰 이벤트가 클라우드 스토리지 시스템에 기록될 목적으로 데이터 firehose로 전송됩니다. 그리하여 최종적으로 데이터 warehouse에 적재됩니다. 이렇게 적재된 데이터는 분석가들이 비즈니스 관련 질문에 답할 때 도움이 됩니다.
이제 서버에서는 HTML로 뷰를 렌더링하여 사용자 브라우저로 되돌려 보내는데, 이 때 뷰는 로드 밸런서를 먼저 거쳐 지나갑니다. 뷰 페이지에는 자바스크립트와 CSS 에셋이 포함되어 있는데, 이 파일들은 클라우드 스토리지 시스템에 들어갈 것들입니다. 그런데 클라우드 스토리지 시스템은 CDN에 연결되어 있으므로, 사용자 브라우저는 콘텐츠를 받아오기 위해 CDN과 접촉해 봅니다. 마지막으로 브라우저는 사용자가 페이지를 볼 수 있도록 화면에 렌더링 해줍니다.
다음으로는 컴포넌트를 하나씩 살펴보며 각각의 컴포넌트에 대한 "101" 소개를 해드리겠습니다. 앞으로도 발전할 웹 아키텍처에 대한 이해를 도와줄 좋은 멘탈 모델을 마련할 기회가 될 겁니다. 차후에는 Storyblocks에서 재직하며 배운것들을 바탕으로 추천하는 상세 구현 방법에 대해 설명하는 글을 후에 연재해 보도록 하겠습니다.
1. DNS
DNS는 "Domain Name Server(도메인 네임 서버)"의 약자이며, 월드 와이드 웹이 가능하게끔 만들어주는 핵심 기술입니다. 가장 기본적인 내용을 말하자면, DNS는 도메인 이름(예: google.com)을 가지고 IP 주소(예: 85.129.83.120)를 찾아낼 수 있는 키/값 조회(lookup) 기능을 제공합니다. 여러분 컴퓨터가 요청을 특정 서버로 올바르게 라우팅 하려면 이 기능이 필요합니다. 전화번호에 비유해 보자면, 도메인 이름과 IP 주소의 차이는 "홍길동에게 전화하기"와 "201-867-5309로 전화하기"의 차이라고 할 수 있습니다. 지난날에는 홍길동에게 전화를 걸려면 전화번호 책에서 찾아 볼 필요가 있었듯이, 해당 도메인을 찾아 가려면 DNS에서 IP 주소를 조회해야 합니다. 이러한 특성 때문에 DNS를 인터넷 상의 전화번호부라고 생각하셔도 됩니다.
이것 말고도 DNS에 대해서 알아볼 사항이 아주 많지만 101 단계에서는 모르더라도 큰일 나지 않기 때문에 넘어가도록 하겠습니다.
2. 로드 밸런서
로드 밸런싱에 대해 자세히 알아보기 전에, 한발 물러나서 수평적(horizontal) 애플리케이션 확장과 수직적(vertical) 애플리케이션 확장에 대해 먼저 논의해 볼 필요가 있습니다. 이 둘의 개념은 뭐고, 차이는 뭘까요? 여기 이 스택오버플로우 포스트에 매우 간단하게 설명이 되어 있습니다. 이에 따르면, 수평적 확장은 리소스 풀에 기계(machine)를 더 추가함으로써 확장시키는 것임에 비해, "수직적" 확장은 현재 사용중인 기계에 힘(예: CPU, RAM)을 더 추가함으로써 확장시키는 것을 뜻합니다.
웹 개발에서는 무언가 망가지는 상황이 발생하더라도 이를 최대한 단순하게 유지하기 위해, (거의) 언제나 수평적 확장을 선택하기 마련입니다. 서버에서는 무작위로 충돌이 납니다. 네트워크 품질은 종종 저하됩니다. 때때로 데이터 센터 연결이 전부 끊기기도 합니다. 서버를 두 대 이상 마련해 둔다면 서버 정지 상황에 대한 대비책을 마련할 수 있기 때문에 애플리케이션이 계속 돌아가도록 만들 수 있습니다. 다르게 말하자면, 여러분의 앱은 "오류에 대해 관용적(fault tolerant)"입니다. 수평적 확장을 선택하는 두번째 이유는 애플리케이션 백엔드(웹 서버, 데이터베이스, 어쩌구 서비스 등등)를 이루는 각각의 부분들이 서로 최소한으로 엮여져 있도록 하기 위해서 입니다. 이를 위해 각 부분을 서로 다른 서버에서 운영하게 됩니다. 세상의 어떤 컴퓨터도 애플리케이션에서 발생하는 연산을 혼자 다 수행할 정도로 충분하게 크지 않습니다. 이에 대한 전형적인 예로는 구글의 검색 플랫폼을 들 수 있습니다. 비록 구글보다 더 작은 규모의 회사에 해당되는 이야기이기는 하지만요. Storyblocks를 예시로 들자면 언제나 150-400개의 AWS EC2 인스턴스가 돌아가고 있습니다. 여기서 사용하는 컴퓨팅 파워를 수직적인 확장을 통해 전부 제공하려면 상당한 도전이 될 것 같습니다.
좋아요, 로드 밸런서로 돌아가 봅시다. 로드 밸런서는 수평적인 확장이 가능하게끔 만들어주는 마법의 소스입니다. 들어오는 요청을 서로의 이미지를 복제, 혹은 반영(mirror)하고 있는 수 많은 애플리케이션 서버 중 하나로 라우팅 처리해 주며, 앱 서버의 응답을 클라이언트로 다시 전송해 줍니다. 웹 서버는 모두 요청을 동일한 방식으로 처리하기 때문에, 오직 신경써야 할 일은 서버 하나라도 과부하 되지 않도록 요청을 분산시키는 일입니다.
이게 전부입니다. 개념적으로 보면 로드 밸런서는 상당히 직관적입니다. 내부를 보면 복잡한 점은 반드시 있지만, 101 단계에서 살펴볼 볼 필요까지는 없습니다.
3. 웹 애플리케이션 서버
웹 애플리케이션 서버는 고수준에서 볼 때 상대적으로 쉽게 설명할 수 있습니다. 사용자의 요청을 처리하여 사용자 브라우저로 HTML을 되돌려 보내주는 핵심 비즈니스 로직이 웹 애플리케이션 서버에서 실행됩니다. 이를 위해 서버는 보통 다양한 백엔드 인프라(데이터베이스, 캐싱 레이어, job 큐, 검색 서비스, 기타 마이크로서비스, 데이터/로깅 큐 등등)와 소통을 합니다. 위에서 언급한 것처럼, 웹 애플리케이션 서버는 최소 두 대 이상, 그리고 많은 경우 두 대보다 많이 사용하게 됩니다. 또한 사용자 요청을 처리하기 위해 로드 밸런서에 연결해 둡니다.
웹 서버를 구현하려면 특정 언어(Node.js - 역: Node.js는 JS 런타임 플랫폼인데 원문에서 언어에 들어가 있네요, Ruby, PHP, Scala, Java, C# .NET 등등)와 해당 언어를 위한 웹 MVC 프레임워크(Express, Ruby On Rails, Play, Laravel 등등)를 선택해야 합니다. 그러나 이들 언어와 프레임워크에 대한 세부사항은 이 글의 범위 밖에 있으므로 다루지 않겠습니다.
4. 데이터베이스 서버
최신 웹 애플리케이션은 모두 데이터베이스를 하나 이상 사용하여 정보를 저장해 둡니다. 데이터베이스를 사용하면 여러분만의 데이터 구조를 정의해 둘 수 있고, 새로운 데이터를 넣을 수도 있으며, 기존 데이터를 찾아내거나 삭제, 혹은 업데이트 할 수 있습니다. 이 외에도 데이터를 가지고 연산을 수행하는 등의 일을 할 수 있습니다. 웹 앱 서버는 거의 대부분 데이터베이스와 직접적으로 교류하며, job 서버도 마찬가지 일 것입니다. 웹 앱 서버 말고 다른 백엔드 서비스 역시 다른 애플리케이션과 분리된 자신만의 데이터베이스를 가지고 있을 수 있습니다.
이 글에서는 아키텍처 컴포넌트를 하나씩 깊게 살펴보는 일은 피하고 있으므로, SQL과 NoSQL 데이터베이스 대한 자세한 설명 역시 생략하는 실례를 범하도록 하겠습니다.
SQL은 "Structured Query Lanauge(구조화 질의어)"를 뜻하며, 대중적으로 접근할 수 있는 관계형 데이터 세트 대한 표준 질의 방식을 제공하기 위해 1970년대에 고안되었습니다. SQL 데이터베이스에는 테이블로 데이터를 저장하여 공통 ID(보통 정수형)로 테이블을 서로 연결시켜 둡니다. 사용자의 주소 정보를 저장하는 간단한 예시를 같이 살펴보도록 하겠습니다. 두 개의 테이블이 있는데, 하나는 users이고 다른 하나는 users_addresses라고 부릅니다. 두 테이블은 사용자의 id로 연결되어 있습니다. 더 간단한 설명을 원하신다면 아래 그림을 참고해 주세요. user_addresses의 user_id 열이 users 테이블의 id 열에 대한 "외부 키"이기 때문에 두 테이블은 서로 연결되어 있습니다.
SQL에 대해 잘 모르신다면, 여기 칸 아카데미의 수업과 같은 튜토리얼을 찾아 보는 것을 강력히 추천드립니다. 웹 개발할 때 아주 흔하게 사용되기 때문에 제대로 애플리케이션을 설계하려면 최소한 기본이라도 알아 두고 싶어질 것입니다.
NoSQL은 "Non-SQL(비-구조화 질의어)"를 뜻하며, 대규모 웹 애플리케이션에서 발생할 수 있는 대용량의 데이터를 처리하기 위해 나온 새로운 데이터 기술 셋을 가르킬 때 사용합니다. (대부분의 SQL류 언어는 수평적 확장이 매우 어렵고 오직 특정 시점에서 수직적으로 확장할 수만 있습니다.) NoSQL에 대해 아는 것이 없다면 아래와 같이 고수준에서 바라보는 소개글부터 읽어 보는 것을 권합니다.
- https://www.w3resource.com/mongodb/nosql.php
- http://www.kdnuggets.com/2016/07/seven-steps-understanding-nosql-databases.html
- https://resources.mongodb.com/getting-started-with-mongodb/back-to-basics-1-introduction-to-nosql
또 한가지 당부해 드리고 싶은 것은, 이 산업에서는 대체로 NoSQL 데이터베이스 조차 SQL 인터페이스에 기대고 있기 때문에 아직 SQL에 대해 모르신다면 정말 배워둘 필요가 있습니다. 요즘에는 배우지 않고 버티는 것이 거의 불가능합니다.
5. 캐싱 서비스
캐싱 서비스는 간단한 키/값 데이터 저장 공간을 제공해 주며, 이를 통해 거의 O(1) 시간만 들이고도 정보를 저장하거나 찾아볼 수 있습니다. 캐싱 서비스에는 보통 수행 비용이 많이 드는 계산 결과를 저장해 두기 때문에, 다음에 그 결과값이 필요할 때 다시 계산을 수행하는 대신 캐시에서 결과값을 가져올 수 있습니다. 캐시에는 데이터베이스 쿼리 결과값, 외부 서비스 호출값, 특정 URL에 대한 HTML을 비롯해 다양한 종류의 값을 저장해 둘 수 있습니다. 실생활에서 찾아볼 수 있는 예제를 몇가지 들자면 다음과 같습니다.
구글에서는 "개"와 "테일러 스위프트"와 같이 흔한 검색 쿼리 대한 결과값을 매번 다시 계산하는 대신 캐시해 둡니다.
- 페이스북에서는 여러분이 로그인 했을 때 보이는 포스트 데이터, 친구 데이터 등등 많은 데이터를 캐시해 둡니다. 페이스북의 캐싱 기술을 더 알아보고 싶다면 이 글을 읽어보세요.
- StoryBlocks에서는 서버 사이드 리액트 렌더링, 검색 결과, 증분 검색 결과 등에 대한 HTML 산출물을 캐시해 둡니다.
널리 사용되는 캐싱 서버 기술 두가지를 들자면 Redis와 Memcache가 있습니다. 이에 대해서는 다른 글에서 좀 더 알아보도록 하겠습니다.
6. Job 큐 & 서버
웹 애플리케이션을 만들다 보면, 사용자 요청 응답과 직접적으로 관련이 없는 몇가지 특정 업무는 대부분 뒤에서 비동기로 처리해야 합니다. 예를 들어, 구글에서는 검색 결과 반환을 하기 위해 인터넷 전체에 대한 크롤링 및 인덱싱 작업을 해야 합니다. 그러나 이 작업은 여러분이 구글에서 검색을 할 때마다 수행되는 일이 아닙니다. 대신, 웹 크롤링 작업을 비동기로 진행하고 동시에 검색 인덱스를 업데이트 합니다.
비동기 작업을 가능하게 만들어 주는 아키텍처가 몇가지 있는데, 이 중에 제가 "job 큐" 아키텍처라고 부르는 것이 가장 널리 쓰입니다. 이 아키텍처는 앞으로 실행될 예정인 "job"으로 이루어진 큐, 그리고 하나 이상의 job 서버(보통 "워커"라고 부릅니다), 이렇게 두가지 컴포넌트로 구성되어 있습니다.
Job 큐에는 비동기로 실행되어야 하는 job 목록을 저장해 둡니다. Job 큐의 가장 간단한 구조는 선입선출(FIFO, first-in-first-out) 큐이긴 한데, 결국 나중에 가서는 대부분의 애플리케이션에서 우선 순위 큐 시스템(priority queing system)과 비슷한 종류의 것을 사용할 필요가 생깁니다. 정규 스케쥴, 또는 사용자 액션에 의해 앱에서 job이 실행되어야 할 필요가 발생하게 되면, 해당 job이 큐에 추가됩니다.
예를 들어 Storyblocks 서비스에서는 job 큐의 힘을 빌려 마켓플레이스 지원에 필요한 많은 양의 뒷단 작업을 처리 하였습니다. job을 통해 비디오와 사진을 인코딩하고, 메타 태그 CSV를 처리하고, 사용자 통계 자료를 모았으며, 패스워드 재설정 이메일도 보내는 등의 일을 했습니다. 처음에는 간단한 FIFO 큐로 시작했으나, 패스워드 재설정 메일 전송과 같이 촌각을 다투는 작업을 최대한 빠르고 확실하게 완료하기 위해 나중엔 결국 우선순위 큐로 업그레이드 했습니다.
Job 서버는 job을 처리합니다. 할 일이 있는지 보기 위해 job 큐의 상태를 폴링해 보고, 일이 있다면 큐에서 job을 pop 해와서 실행합니다. Job 서버를 만드는 데 선택할 수 있는 언어와 프레임워크는 웹 서버를 만들 때 만큼 많이 있지만, 이 글에서 다루지는 않겠습니다.
7. 전문 검색 서비스
거의는 아니지만, 그래도 많은 수의 웹 앱이 사용자가 입력한 텍스트(종종 "쿼리"라고 부릅니다)를 가지고 가장 "관련성 높은" 결과를 검색해 돌려주는 기능이 있습니다. 이런 기능을 제공해 주는 기술을 보통 "전문 검색(full-text search)"이라고 부르는데, 역색인(inverted text)의 힘을 빌려 쿼리 키워드가 포함된 문서를 빠르게 찾아내는 기술입니다.
몇몇 데이터베이스에서는 자체적인 전문 검색 기능을 지원하지만(예: MySQL), 보통은 역색인에 대한 계산 및 저장 작업을 수행하고 쿼리 인터페이스를 제공해 주는 "검색 서비스"를 따로 구동 시킵니다. Sphinx나 Apache Solr와 같은 다른 옵션이 있기는 하나, 오늘날 가장 유명한 전문 검색 플랫폼은 Elasticsearch입니다.
8. 서비스
엡이 특정 규모에 도달하면, 특정 "서비스"들은 따로 떼어 내어 별도의 애플리케이션으로 돌려야 할 필요가 생길 수 있습니다. 이들 서비스는 외부로는 노출이 되지 않지만, 해당 앱과 그 앱의 다른 서비스와는 교류를 합니다. 예를 들어 Storyblocks에서는 계획적으로 운용하는 몇가지 서비스가 있습니다.
계정 서비스는 사이트 전체에 걸쳐 있는 사용자 데이터를 저장하는 일을 합니다. 덕분에 손쉽게 교체 판매(cross-sell) 기회를 포착할 수 있고, 좀 더 통합적인 사용자 경험을 만들어 낼 수 있습니다.
콘텐츠 서비스는 모든 비디오, 오디오, 이미지 콘텐츠에 대한 메타데이터를 저장하는 일을 합니다. 또한 콘텐츠 다운로드 및 다운로드 히스토리 열람용 인터페이스를 제공하기도 합니다.
결제 서비스는 고객용 신용카드 청구 인터페이스를 제공해 줍니다.
HTML → PDF 서비스는 HTML 문서를 받아 PDF 문서로 변환해 돌려주는 간단한 인터페이스를 제공해 줍니다.
9. 데이터
오늘날에는 데이터를 얼마나 잘 활용하는가에 회사의 사활이 걸려 있습니다. 최신 앱의 경우, 거의 모든 앱이 특정 규모에 도달하게 되면 데이터 파이프라인을 도입해 데이터를 모으고, 저장하고, 분석하는 일을 하게됩니다. 파이프라인은 일반적으로 세가지 주요 단계로 구성되어 있습니다.
앱이 일단 데이터(보통은 사용자 상호작용 이벤트에 대한 데이터)를 데이터 "firehose"에 보냅니다. 데이터 firehose는 데이터를 받아들여 처리하는 스트리밍 인터페이스를 제공해 줍니다. firehose는 보통 원시 데이터를 받아서 이를 변형하거나 증분하여 다른 firehose로 보내는 일을 합니다. 이 목적으로 가장 많이 사용하는 기술은 AWS Kinesis와 Kafka 두가지가 있습니다.
원시 데이터 및 변형/증분 완료된 최종 데이터는 클라우드 스토리지에 저장됩니다. AWS Kinesis는 "firehose"라는 세팅을 제공해 주는데, 이를 사용하면 원시 데이터를 클라우드 스토리지(S3)으로 저장하는 작업 설정을 매우 쉽게 할 수 있습니다.
변형/증분된 데이터는 분석용으로 데이터 웨어하우스에 적재되는 경우가 종종 있습니다. 스타트업 업계에서 많이들 그러듯이 저희는 AWS Redshift를 사용했는데, 대기업에서는 Oracle이나 기타 독점적인 웨어하우스 기술을 자주 사용하기도 합니다. 데이터 셋의 크기가 충분히 크다면, Hadoop 같은 NoSQL 맵리듀스 기술이 분석에 필요할 수도 있습니다.
아키텍처 다이어그램에 그리지는 않았으나 또 다른 단계가 있는데, 바로 앱이나 서비스 운영 데이터베이스에서 데이터를 불러와 데이터 웨어하우스에 저장하는 작업입니다. 예시를 들자면 Storyblocks에서는 VideoBlocks, AudioBlocks, Storyblocks, 계정 서비스 및 contributor portal 데이터베이스를 Redshift로 매일 밤 불러옵니다. 분석가는 이 덕분에 핵심 비즈니스 데이터와 사용자 상호작용 이벤트 데이터를 같이 놓고 볼 수 있는 통합적인 데이터셋을 제공 받을 수 있습니다.
10. 클라우드 스토리지
AWS에 따르면, "클라우드 스토리지는 데이터를 저장하고, 접근하고, 인터넷 상에 공유할 수 있는 간단하면서 확장성을 갖춘 수단입니다". 로컬 파일 시스템에 저장하는 파일은 거의 모두 HTTP RESTful API를 통해 클라우드 스토리지에 저장할 수 있고, 이에 접근할 수도 있습니다. 아마존의 S3 서비스는 클라우드 스토리지 서비스 중 아직까지는 가장 대중적인 서비스이며, Storyblocks에서도 비디오, 사진, 오디오 에셋, CSS, 자바스크립트 파일, 사용자 이벤트 데이터 등등을 저장하는 용도로 폭넓게 사용하고 있습니다.
11. CDN
CDN은 "Content Delivery Network(콘텐츠 전송 네트워크)"를 뜻하며 정적인 HTML, CSS, 자바스크립트, 이미지 파일 에셋을 단일 원천 서버에서 제공할 때보다 더 빠른 속도로 제공해주는 기술입니다. 전세계에 퍼져 있는 수많은 "엣지" 서버에 콘텐츠를 배분해 두기 때문에 사용자는 원천 서버가 아닌 "엣지" 서버에서 에셋을 다운로드하게 됩니다. 예를 들어 아래 이미지를 보면, 스페인에 사는 사용자가 원천 서버가 뉴욕에 있는 사이트에서 웹 페이지를 요청합니다. 그러나 페이지에 들어가는 정적 에셋은 영국에 있는 CDN "엣지" 서버에서 불러오기 때문에, 대서양을 가로지르는 HTTP 요청 횟수를 많이 줄일 수 있습니다.
좀 더 세세한 개론을 원한다면 이 글을 읽어 보세요. 보통 웹 앱은 CSS, 자바스크립트, 이미지, 비디오 및 기타 에셋 전송 용도로 CDN을 항상 사용해야 합니다. 정적인 HTML 페이지 전송할 때도 사용할 수 있습니다.
마치며
이제 웹 아키텍쳐 101 막을 내리도록 하겠습니다. 여러분께 이 글이 유용하기를 바랍니다. 내년이나 내후년 쯤에 기회가 된다면 여기서 설명한 컴포넌트 중 몇가지를 추려 자세하게 알아보는 201 시리즈 글을 연재하도록 하겠습니다.