[Designing Machine Learning Systems] 데이터 엔지니어링 기초
ML과 빅데이터는 밀접한 연관이 있다. 이 장에서는 데이터 엔지니어링의 기본을 다룬다. 일반적인 ML 프로젝트에서 사용하는 다양한 데이터 소스를 살펴보고 데이터를 저장하는 포맷을 알아본다. 데이터 저장은 해당 데이터를 *검색(retrieval)할 경우 필요하다. 저장된 데이터를 검색하기 위해 데이터 포맷뿐 아니라 데이터가 어떻게 구조화되었는지 알아야 한다. 데이터 모델은 특정 데이터 포맷으로 저장된 데이터가 구조화되는 방식을 정의한다.
(* ‘search’와 ‘retrieval’의 차이: ‘search’는 검색 엔진에서 키워드와 관련 있는 정보를 찾을 때 사용되고, ‘retrieval’는 이미 데이터베이스나 스토리지에 저장되거나 색인된 정보에 액세스하는 프로세스이다.)
데이터 모델이 실제 세계의 데이터를 표현한다면 데이터베이스는 데이터가 시스템에 저장되는 방식을 지정한다. 데이터베이스라고 하는 데이터 스토리지 엔진과 주요 처리 유형인 트랜잭션 처리와 분석 처리를 살펴본다.
프로덕션에서는 일반적으로 데이터를 여러 프로세스 및 서비스에 걸쳐 처리한다. 3.5절에서 프로세스 간 데이터를 전달하는 다양한 모드를 알아본다.
다양한 데이터 전달 모드를 알아보면서 데이터 스토리지 엔진에서 사용하는 과거 데이터와 실시간 전송에서 사용하는 스트리밍 데이터라는 두 가지 데이터 유형을 살펴본다.
프로덕션용 ML 시스템을 구축하려면 증가하는 데이터를 수집, 처리, 저장, 검색 및 처리하는 방법을 숙지해야 한다.
3.1 데이터 소스
ML 시스템은 다양한 소스에서 온 데이터로 작동한다. 데이터마다 특성, 목적, 처리 방법이 다르며 데이터 소스를 파악하면 데이터를 보다 효율적으로 사용하는 데 도움이 된다.
대표적인 데이터 소스는 사용자가 명시적으로 입력하는 사용자 입력 데이터로 텍스트, 이미지, 비디오, 업로드된 파일 등이다. 사용자가 원격으로 잘못된 데이터를 입력할 수 있기 때문에 포맷이 잘못되기 쉽다. 따라서 철저한 검사와 처리가 필요하다. 또한 사용자는 결과가 즉시 반환되기를 기대하므로 빠른 처리가 필요한 경향이 있다.
또 다른 소스는 시스템 생성 데이터이다. 시스템의 여러 구성 요소에서 생성된다. 구성 요소에는 다양한 로그와 모델 예측 같은 시스템 출력 등이 있다.
로그는 시스템의 상태와 중요한 이벤트를 기록한다. 메모리 사용량, 인스턴스 수, 호출된 서비스, 사용된 패키지 등이다. 로그는 다양한 작업을 기록하고, 시스템이 어떻게 동작하는지에 대한 가시성을 제공한다(디버깅과 잠재적으로 시스템을 개선하는데 도움을 준다). 로그는 시스템에서 생성되므로 포맷이 잘못될 가능성이 적다. 주기적으로 처리되지만, 문제가 발생할 때마다 알림을 받으려면 빠르게 처리해야 한다.
ML 시스템을 디버깅하기는 어려우므로 일반적으로 모든 것을 기록한다. 이로 인해 두가지 문제가 발생하는데 첫번째는 신호에 잡음이 섞여 어디를 봐야 하는지 알기 어렵다는 점과 로그가 급증한다는 점이다. Logstash, Datadog, Logz.io 등의 서비스는 첫번째 문제를 해소하는데 도움을 준다. 급증하는 로그를 처리하기 위해 시스템을 디버깅하는 데 관련이 없어지면 삭제하고, 로그에 자주 액세스하지 않아도 된다면 낮은 액세스 스토리지에 저장하여 비용을 절약할 수 있다.
ML 시스템은 사용자 행동을 기록하는 데이터를 생성한다. 시스템 생성 데이터이지만 사용자 데이터의 일부로 간주되며 개인 정보 보호 규정이 적용된다.
회사의 다양한 서비스 및 엔터프라이즈 애플리케이션에서 생성된 내부 데이터베이스도 있다. 재고, 고객 관계, 사용자를 비롯한 자산이 관리되며 ML 모델에서 직접 사용되거나 ML 시스템의 다양한 구성 요소에서 사용된다.
다양한 서드 파티 데이터도 있다. 대표적으로 스마트폰에서 수집되는 데이터가 있다. 스마트폰마다 사용자의 모든 활동을 집계하는 고유 광고 ID가 있다. 아이폰에는 IDFA(Identifier for Advertisers), 안드로이드 폰에는 AAID(Android Advertising ID)가 있어 사용자의 정보를 수집한다. 이렇게 서드 파티에서 생성된 온갖 종류의 데이터가 공급업체에서 정리 및 처리된 후 판매된다. 한편 사용자가 더 많은 프라이버시를 요구함에 따라 기업은 광고 ID 사용을 제한하려는 조치를 취하고 있다. 2021년 초 애플은 IDFA 수집 방식을 옵트인으로 변경했다. 이는 개인 정보를 활용하기 전 당사자에게 개인 정보 수집과 이용에 대한 동의를 먼저 받아야 함을 의미한다. 반대로 기존 방식인 옵트아웃은 당사자 동의 없이 개인 정보를 수집해 활용하다가 당사자가 거부 의사를 밝히면 활용을 중지한다는 의미이다.
3.2 데이터 포맷
데이터는 일회성으로 사용하지 않는 이상 저장이 필요하다. 데이터는 다양한 소스에서 가져오고 *액세스 패턴도 다르므로 저장하기가 항상 간단하지는 않고 때때로 비용이 많이 든다. 이때 고려해야 할 질문 몇 가지는 다음과 같다.
(* 액세스 패턴: 시스템이나 프로그램이 데이터를 읽거나 쓰는 패턴)
- 멀티모달 데이터는 어떻게 저장할까?
- 저럼하고 빠르게 액세스하려면 데이터를 어디에 저장해야 할까?
- 복잡한 모델을 다른 하드웨어에서 올바르게 로드하고 실행하려면 어떻게 저장해야 할까?
데이터 직렬화(data serialization)란 데이터 구조나 객체 상태를 저장 혹은 전송하고 나중에 재구성할 수 있는 포맷으로 변환하는 프로세스이다. 작업할 포맷을 고려할 때는 액세스 패턴, 사람이 읽을 수 있는지, 텍스트인지 이진인지(파일 크기에 영향) 등 다양한 특성을 고려한다.
3.2.1 JSON(JavaScript Object Notation)
JSON은 자바스크립트에서 파생되었지만 언어 독립적이며 최신 프로그래밍 언어는 대부분 JSON 생성과 파싱을 지원한다. 사람이 읽을 수 있다. 키-값 쌍 패러다임은 단순하지만 강력하며 다양한 수준의 정형 데이터를 처리한다. JSON은 널리 활용되는 만큼 번거로움도 만만치 않다. (스키마를 커밋한 후 스키마를 변경하기 위해 다시 되돌아가야한다. + JSON 스키마란?)
3.2.2 행 우선 포맷 vs. 열 우선 포맷
CSV와 Parquet는 공통적이면서도 별개의 패러다임을 바탕으로 한다. CSV는 행 우선, Parquet는 열 우선 포맷이다. 행 우선 포맷은 행의 연속 요소가, 열 우선 포맷은 열의 연속 요소가 메모리에 나란히 저장된다. 컴퓨터는 비순차 데이터보다 순차 데이터를 더 효율적으로 처리하므로 테이블이 행 우선이라면 열에 액세스하기보다 행에 액세스하는 편이 빠르다.
승차 공유 트랜잭션 데이터에 피처 1,000개가 있고 그 중 시간, 위치, 거리, 가격 등 네 가지 피처만 원한다면?
-> 열 우선 읽기가 빠르므로 열 우선 포맷을 사용 (행 우선 포맷을 사용하면 행의 크기를 모르는 경우 모든 열을 읽은 다음 네 열로 필터링 해야한다.)
행 우선 포맷을 사용하면 더 빠른 데이터 쓰기가 가능하다. 대체로 새로운 데이터를 계속 추가해야 하는 상황이면 행 우선이 유리하다.
결론: 쓰기를 많이 수행할 때는 행 우선 포맷, 열 기반 읽기를 많이 수행할 때는 열 우선 포맷 선택
Note: 부동 소수점 값을 CSV 파일에 쓸 때 정밀도가 일부 손실될 수도 있다! 해커 뉴스에서는 사람들이 CSV 사용에 열렬히 반대했다.
3.2.3 텍스트 포맷 vs. 이진 포맷
CSV와 JSON은 텍스트 파일인 반면 Parquet은 이진 파일이다. 텍스트 파일은 사람이 읽을 수 있지만 이진 파일은 원시 바이트를 해석하는 방법을 알고 있는 프로그램에서 읽거나 사용하기 위한 파일이다.
이진 파일은 간결하고 텍스트 파일에 비해 공간을 절약한다.
숫자 1000000을 저장한다면?
텍스트 파일: 일곱 글자를 저장해야 하므로, 각 문자를 1바이트라고 가정했을 때 7바이트가 필요
이진 파일: int32를 기준으로 저장하면 32비트, 즉 4바이트가 필요
3.3 데이터 모델
데이터 모델은 데이터가 어떻게 표현되는지 설명한다. 자동차를 데이터베이스에서 표현할 때 제조사, 모델, 색상, 가격 정보를 담아 표현하거나, 차량 소유자, 번호판 및 등록된 주소 기록으로 표현할 수 있다.
데이터를 표현하는 방법은 시스템을 구축하는 방식뿐 아니라 시스템이 해결하는 문제에도 영향을 미친다. 첫 번째 데이터 모델은 자동차를 구매하려는 사람에게 도움이 되고, 두 번째 데이터 모델은 범죄자를 추적할 때 도움이 된다.
3.3.1 관계형 모델
관계형 모델에서 데이터는 관계(relation)로 구성되며 각 관계는 튜플(행)의 집합이다. 테이블은 관계를 시각적으로 표현한 것이다. 관계는 순서가 없다. 행, 열의 순서가 섞여도 동일한 관계이다. 관계형 모델을 따르는 데이터는 일반적으로 CSV나 Parquet 같은 파일 포맷으로 저장된다.
관계는 정규화하는 편이 좋은 경우가 많다(데이터베이스 정규화). 정규화의 목표는 이상이 있는 관계를 재구성하여 잘 조직된 관계를 생성하는 것이다. 데이터 중복을 줄이고 데이터 무결성을 향상시킨다.
정규화의 주요 단점은 데이터가 여러 관계로 분산된다는 점이다. 분산된 데이터를 다시 조인할 수 있지만 테이블이 크면 조인에 비용이 많이 든다.
데이터베이스에서 원하는 데이터를 지정하는 데 사용하는 언어를 쿼리 언어라고 한다. 오늘날 관계형 데이터베이스에서 가장 많이 사용하는 쿼리 언어는 SQL이다.
(관계형 데이터베이스: 관계형 데이터 모델을 기반으로 구축된 데이터베이스)
SQL은 선언적 언어이다. 선언적 언어는 명령형 언어와 대비되는 말로 원하는 출력을 컴퓨터에게 전달하면 컴퓨터가 쿼리된 출력을 얻는 데 필요한 단계를 파악한다. 명령형 패러다임에서는, 예를 들면 파이썬으로 코드를 작성한다면 작업에 필요한 단계들을 하나씩 지정해주고 컴퓨터가 이를 실행하지만 SQL에서는 원하는 결과를 지정하면 컴퓨터가 구체적인 방법은 알아서 결정하고 결과를 보내준다.
임의의 쿼리를 실행하는 방법은 쿼리 옵티마이저가 맡는다. 가능한 쿼리 실행 방법을 모두 검사해 가장 빠른 방법을 찾는다.
3.3.2 NoSQL
관계형 데이터베이스는 전자 상거래, 금융, 소셜 네트워크 등 다양한 유스 케이스로 보편화되었다. 하지만 데이터가 엄격한 스키마를 따라야 하고 스키마 관리가 어렵다는 단점이 있다. 또한 특화된 애플리케이션(specialized application)을 위한 SQL 쿼리를 작성하고 실행하기가 어렵다.
관계형 데이터 모델에 대치되는 최신 트랜드는 NoSQL이다. 비관계형 데이터베이스를 논의하는 모임의 해시태그로 시작했지만 관계형 모델도 지원한다는 점에서 ‘Not only SQL’로 재해석되었다. 비관계형 모델의 주요 유형 두 가지는 문서 모델과 그래프 모델이다.
문서 모델
문서 모델은 ‘문서’라는 개념을 기반으로 구축되었다. 문서 모델에서는 각 문서의 내용이 우선된다. 문서는 종종 단일 연속 문자열로, JSON, XML, BSON(Binary JSON)으로 인코딩된다. 문서 데이터베이스 내 모든 문서는 동일한 포맷으로 인코딩되었다고 가정되며 각 문서마다 고유한 키가 있어 문서를 검색하는데 사용된다.
문서 컬렉션은 관계형 데이터베이스의 테이블과 대응되며, 각 문서는 튜플(행)에 대응된다.
문서 컬렉션이 테이블보다 훨씬 유연하다. 테이블에서는 모든 튜플이 동일한 스키마를 따라야하지만 같은 컬렉션에 있는 문서들 간에는 스키마가 완전히 다를 수 있다. 그래서 스키마리스 모델이라고 불리며 문서를 읽는 애플리케이션이 일반적으로 문서의 구조를 가정한다. 문서 데이터베이스는 구조를 가정하는 책임을 데이터를 쓰는 애플리케이션에서 읽는 애플리케이션으로 넘겼다.
문서 모델은 관계형 모델보다 지역성(locality)이 우수하다. 관계형 데이터베이스에서 특정 정보를 검색하기 위해 여러 테이블을 쿼리해야하는 경우가 많지만 문서 모델을 사용하면 문서 하나에 정보가 응집된 채로 담겨 있어 검색이 쉬워진다. (관계형 모델에서는 중복 제거를 위해 관계를 분리하지만 문서 모델에서는 문서 하나에 개별 문서의 정보를 모두 저장하기 때문으로 이해함)
하지만 문서 간 조인은 관계형 모델에서 테이블 간 조인을 실행할 때보다 어렵고 덜 효율적이다.
그래프 모델
그래프 모델은 ‘그래프’ 개념을 기반으로 구축되었다. 그래프는 노드(node)와 엣지(edge)로 구성되며 에지는 노드 간의 관계를 나타낸다. 그래프 데이터베이스에서는 데이터 항목 간의 관계가 우선이다. 그래프 모델에서는 관계를 명시적으로 모델링하므로 관계를 기반으로 검색하는 것이 빠르다.
위 그림은 그래프 데이터베이스 예시이다. 이 예시를 보고 미국에서 태어난 사람을 모두 찾는다고 하면 ‘USA’ 노드부터 시작해 ‘within’ 에지와 ‘born_in’ 에지를 따라 그래프를 탐색하며 데이터 유형이 ‘person’인 노드를 모두 찾는다.
한번 이 데이터를 그래프 모델이 아닌 관계형 데이터로 나타낸다고 상상해보자. 미국에서 태어난 사람을 모두 찾는 SQL 쿼리는 쉽게 작성할 방법이 없다. 데이터 모델에 따라 수행하기 쉬운 쿼리가 있고 어려운 쿼리가 있다. 따라서 애플리케이션에 적합한 데이터 모델을 선택하는 것이 바람직하다.
3.3.3 정형 데이터 vs. 비정형 데이터
정형 데이터는 미리 정의된 데이터 모델, 즉 데이터 스키마를 따른다. 미리 정의된 구조를 사용하면 데이터를 분석하기가 더 쉽다. 하지만 미리 정의된 스키마에 데이터를 엄격하게 맞춰야 한다는 점은 단점이다. 스키마가 변경되면 모든 데이터를 소급해 업데이트해야 한다.
비즈니스 요구 사항은 시간에 따라 변하므로 사전 정의된 데이터 스키마에 종속되는 것은 많은 제한을 불러온다. 또한 제어할 수 없는 데이터 소스가 있으며 데이터가 동일한 스키마를 따르도록 하기 어렵다. 이러한 경우엔 미리 정의된 데이터 스키마를 따르지 않는 비정형 데이터가 매력적이다.
정형 데이터를 저장하는 저장소를 데이터 웨어하우스라고 하며 비정형 데이터를 저장하는 저장소를 데이터 레이크라고 한다. 일반적으로 데이터 웨어하우스에는 사용 가능한 형식으로 처리된 데이터가 저장되고, 데이터 레이크에는 처리 전 원시 데이터가 저장된다.
3.4 데이터 스토리지 엔진 및 처리
데이터베이스라고도 하는 스토리지 엔진은 데이터가 시스템에 저장되고 검색되는 방법을 구현한다. 애플리케이션에 적합한 데이터베이스를 선택해야 할 수 있으니 다양한 데이터베이스 유형을 이해하면 유용하다.
일반적으로 데이터베이스가 최적화되는 워크로드에는 트랜잭션 처리와 분석 처리, 두 가지 유형이 있다.
3.4.1 트랜잭션 처리와 분석 처리
트랜잭션은 온갖 종류의 작업을 의미한다. 예를 들면 트윗 보내기, 승차 공유 서비스로 차량 호출하기, 신규 모델 업로드하기, 유튜브 동영상 보기 등이 모두 트랜잭션이다. 트랜잭션에 포함되는 데이터는 각기 다르지만 처리 방식은 유사하다. 대체로 생성될 때 삽입, 변경될 때 갱신, 필요하지 않아질 때 삭제의 과정을 거치며 이러한 처리를 OLTP(online transaction processing) 라고 한다.
트랜젝션은 사용자와 관련되는 경우가 많으므로 사용자가 기다리지 않게 하는 것(낮은 레이턴시)과 언제든 사용 가능한 것(고가용성)이 중요하다.
트랜잭션 데이터베이스라고 하면 일반적으로 ACID(atomicity, consistency, islation, durability)를 생각한다. 승차 공유 시스템을 예시로 각각의 정의를 상기해보자.
원자성: 트랜잭션의 모든 단계가 하나의 그룹으로서 성공적으로 완료되도록 보장한다. 한 단계가 실패하면 나머지 단계들도 모두 실패한다. (사용자가 운행료 결제에 실패하면 해당 사용자에게 운전자를 할당하지 않는다.)
일관성: 들어오는 모든 트랜잭션이 미리 정의된 규칙을 따라야 함을 보장한다. (유효한 사용자가 트랜잭션을 수행해야 한다.)
격리성: 두 트랜잭션이 마치 격리된 것처럼 동시에 발생하도록 보장한다. 사용자 두 명이 동시에 동일한 데이터에 액세스하는 경우 둘이 동시에 데이터를 변경하지 않는다. (두 사용자가 동시에 같은 운전자를 예약하지 않는다.)
지속성: 트랜잭션이 커밋된 후에는 시스템 장애가 발생하더라도 커밋된 상태를 유지하도록 보장한다. (운행을 예약한 뒤 휴대 전화가 꺼지더라도 차량이 오도록 한다.)
각 트랜잭션은 종종 다른 트랜잭션과 별개의 단위로 처리되므로 트랜잭션 데이터베이스는 행 우선인 때가 많다. 행 우선 포맷일 때는 “9월에 샌프란시스코에서 발생한 모든 승차 공유의 평균 운행료는 얼마인가요?”와 같은 질문에 대답하기엔 효율적이지 않다. 이러한 질문에 답하기 위해선 여러 데이터 행에 걸쳐 있는 열 데이터를 집계해야 하기 때문이다. 이것이 분석 데이터베이스가 설계된 목적이며, 이러한 처리를 OLAP(online analytical processing) 라고 한다.
OLTP와 OLAP라는 용어는 이제는 많이 사용되지 않는다. 1) 트랜잭션 데이터베이스와 분석 데이터베이스가 분리된 것은 기술의 한계 때문이었으나 이제는 트랜잭션 쿼리와 분석 쿼리를 모두 효율적으로 처리하는 데이터베이스가 등장했고, 2) 스토리지와 처리가 밀접하게 결합되어 있던 과거(데이터 저장 방식 = 데이터 처리 방식, 서로 종속되어 있음)와 달리 스토리지와 처리를 분리하는 패러다임이 관찰되고 있고(데이터가 동일한 위치에 저장되고 처리 레이어에서 각기 다른 유형의 쿼리에 최적화), 3) ‘온라인’이라는 용어는 많은 것을 의미하는 과부하된 용어가 됐다.(단지 ‘인터넷에 연결됨’에서 확대돼 ‘프로덕션 중임’을 뜻하기도 한다.)
3.4.2 ETL: Extract, Transform, Load
데이터가 소스에서 추출되면 데이터베이스나 데이터 웨어하우스 같은 대상에 적재되기 전에 먼저 원하는 포맷으로 변환된다. 이 프로세스를 ETL이라 한다. ETL은 데이터를 범용 처리 및 원하는 모양과 포맷으로 집계함을 의미한다.
추출은 모든 데이터 소스에서 원하는 데이터를 추출하는 일이다. 추출 단계에서 데이터를 검증하고 요구 사항을 충족하지 않으면 거부한 뒤 소스에 알리기도 한다.
변환은 대부분의 데이터 처리가 수행되는 프로세스의 핵심 단계이다. 여러 소스에서 온 데이터를 조인하고 정제하며 값 범위를 표준화한다. 이 외에도 전치, 중복 제거, 정렬, 집계, 새로운 피처 도출, 더 많은 데이터 유효성 검사와 같은 작업을 적용한다.
로드는 변환된 데이터를 파일, 데이터베이스, 데이터 웨어하우스와 같은 대상에 적재할 방법과 빈도를 결정한다.
세상이 발전하며 최근 데이터 수집이 쉬워지고 양이 급증하고 데이터의 성격이 변했다. 데이터를 구조화한 상태로 유지하기 어려워지며 모든 데이터를 데이터 레이크에 저장해 스키마 변경을 처리할 필요가 없도록 하고, 애플리케이션에 데이터가 필요하면 데이터 레이크에서 원시 데이터를 가져와 처리하는 경우도 생겼다. 이처럼 데이터를 먼저 스토리지에 적재한 뒤 나중에 처리하는 프로세스를 ELT라고 한다. 데이터를 신속할 저장할 수 있다는 장점이 있지만 데이터가 증가함에 따라 매력이 떨어진다. 방대한 원시 데이터에서 원하는 데이터를 검색하는 일은 비효율적이기 때문이다.
기업에서는 정형 및 비정형 데이터 저장의 장단점을 저울질한다. 이에 따라 데이터 레이크의 유연성과 데이터 웨어하우스의 데이터 관리 측면을 결합한 하이브리드 솔루션이 등장하고 있다. 데이터브릭스와 스노우플레이크가 대표적인 예시이다.
3.5 데이터플로 모드
프로덕션에서 대개 프로세스가 하나가 아닌 여러 개이다. 메모리를 공유하지 않는 서로 다른 프로세스 간에 데이터를 어떻게 전달할까?
데이터가 한 프로세스에서 다른 프로세스로 전달될 때 데이터가 한 프로세스에서 다른 프로세스로 흐른다고 한다. 즉, 데이터플로가 생긴다. 데이터플로에는 세 가지 주요 모드가 있다. 각 모드를 하나씩 살펴보자.
3.5.1 데이터베이스를 통한 데이터 전달
두 프로세스 간 데이터를 전달하는 가장 쉬운 방법이다. A 프로세스에서 B 프로세스로 데이터를 전달하려면 A 프로세스는 해당 데이터를 데이터베이스에 쓰고 B 프로세스는 단순히 해당 데이터베이스에서 읽으면 된다.
이 모드가 항상 잘 작동하지는 않는다. 1) 두 프로세스가 동일한 데이터베이스에 액세스해야 하는데 이것이 불가능할 때가 있다(두 프로세스가 서로 다른 회사에서 실행될 때). 2) 데이터베이스에서 데이터에 액세스하려면 두 프로세스가 모두 필요하며 데이터베이스에서 읽기 및 쓰기가 느려질 수 있다. 즉, 레이턴시 요구 사항이 엄격한 경우 사용이 제한된다.
3.5.2 서비스를 통한 데이터 전달
두 프로세스를 연결하는 네트워크를 통해 직접 데이터를 전달하는 방법이다. B 프로세스에서 A 프로세스로 데이터를 전달한다고 할 때, A 프로세스는 필요한 데이터를 지정하는 요청을 B 프로세스에 보내고, B는 동일한 네트워크를 통해 요청된 데이터를 반환한다. 프로세스가 요청을 통해 통신하므로 이를 요청 기반(request-driven)이라고 한다.
서로 통신하는 두 서비스가 독립적인 회사에서 별개의 애플리케이션에 의해 운영될 수 있고, 동일한 애플리케이션의 일부일 수 있다. 애플리케이션의 여러 구성 요소를 각각 별도의 서비스로 구성하면 각 요소를 독립적으로 개발하고 테스트 및 유지 관리할 수 있다. 이렇게 애플리케이션을 별도의 서비스로 구조화하는 것이 마이크로서비스 아키텍처이다.
마이크로서비스 아키텍처를 ML 시스템의 맥락에서 알아보자. 내가 ML 엔지니어이고 리프트 같은 승차 공유 애플리케이션을 소유한 회사의 가격 최적화 문제를 해결한다고 가정해보자. 리프트의 마이크로서비스 아키텍처에는 수백 개의 서비스가 있지만 간단히 다음 세 가지 서비스만 고려한다.
- 운전자 관리 서비스: 특정 지역에서 다음 1분 동안 가용한 운전자 수를 예측한다.
- 운행 관리 서비스: 특정 지역에서 다음 1분 동안 운행이 몇 회 요청될지 예측한다.
- 가격 최적화 서비스: 각 운행에 대한 최적 가격을 예측한다.
가격은 공급(가용한 운전자)과 수요(요청된 운행)에 따라 달라지므로 가격 최적화 서비스에는 운전자 관리 서비스 및 운행 관리 서비스의 데이터가 둘 다 필요하다. 사용자가 운행을 요청할 때마다 가격 최적화 서비스는 예상 운행 횟수와 예상 운전자 수를 요청해 해당 운행에 대한 최적 가격을 예측한다.
네트워크를 통한 데이터를 전달에 사용하는 요청 스타일로는 REST와 RPC가 가장 인기있다.
3.5.3 실시간 전송을 통한 데이터 전달
앞서 다룬 예시를 다시 떠올려보자. 운전자 관리 시스템은 운행 관리 서비스에서 운행 횟수 데이터를 받아오면 운전자를 몇 명이나 동원할지 파악할 수 있고, 가격 최적화 서비스에서 예측된 가격을 받아와 잠재적인 운전자 인센티브로 활용할 수 있다(ex. 지금 운행하면 2배 할증 요금을 받을 수 있다!). 마찬가지로 운행 관리 서비스에서는 운전자 관리 및 가격 최적화 서비스의 데이터가 필요할 수 있다.
세 개의 서비스 만으로도 데이터 전달이 복잡해지고 있다. 서비스가 수백, 수천 개라면 서비스 간 데이터 전달이 폭발적으로 증가하고 병목이 돼 전체 시스템 속도를 늦춘다.
요청 기반 데이터 전달은 동기식이다. 가격 최적화 서비스가 운전자 관리 서비스에 데이터를 요청했는데 운전자 관리 서비스가 다운됐다면 가격 최적화 서비스는 시간이 초과될 때까지 요청을 계속 재전송한다. 서비스가 중단되면 해당 서비스의 데이터가 필요한 서비스들이 모두 중단된다.
이런 경우 데이터 전달을 조정하는 브로커를 두면 좋다. 서비스끼리 직접 데이터를 요청하고 복잡한 서비스 간 데이터 전달망을 생성하는 대신 각 서비스가 브로커와 통신하기만 하면 된다. 엄밀히 따지면 ‘데이터베이스를 통한 데이터 전달’에서 데이터베이스도 브로커다. 하지만 레이턴시 요구 사항을 맞추기에 너무 느리므로 실시간 전송은 서비스 간 데이터 전달을 위한 인메모리 스토리지라고 생각해도 된다.
실시간 전송으로 브로커에 브로드캐스트되는 데이터 조각을 이벤트라고 하기에, 이러한 아키텍처를 이벤트 기반(event-driven)이라고도 한다. 그리고 실시간 전송을 이벤트 버스라고도 한다.
실시간 전송의 가장 일반적인 유형으로는 pubsub과 메시지 큐가 있다. pubsub 솔루션의 예로는 아파치 카프카와 아마존 키네시스가 있고, 메시지 큐의 예시로는 아파치 로켓과 래빗이 있다.
3.6 배치 처리 vs. 스트림 처리
데이터는 데이터 스토리지 엔진(데이터베이스나 데이터 레이크 혹은 데이터 웨어하우스)에 도착하면 과거 데이터가 된다. 이는 스트리밍 데이터와 반대이다. 과거 데이터는 주기적으로 시작되는 배치 작업에서 처리되는 경우가 많다.
데이터를 배치 작업에서 처리하는 것을 배치 처리라고 한다. 기업들은 배치 데이터를 효율적으로 처리하기 위해 맵리듀스와 스파크 같은 분산 시스템을 고안했다.
실시간 전송 데이터가 있으면 스트리밍 데이터가 존재한다고 한다. 스트림 처리는 스트리밍 데이터에 대한 계산을 의미한다. 배치 작업처럼 주기적으로 처리할 수 있지만 주기가 훨씬 짧고, 필요할 때마다 계산을 시작할 수도 있다(ex. 사용자가 운행을 요청할 때마다 데이터 스트림을 처리해 가용한 운전자를 확인).
스트림 처리가 제대로 수행되면 레이턴시가 짧다. 많은 사람이 스트림 처리는 맵리듀스나 스파크 같은 도구를 활용할 수 없어 배치 처리에 비해 덜 효율적이라고 생각하나, 항상 그렇지는 않다. 아파치 플링크를 비롯한 스트리밍 기술은 뛰어난 확장성과 완전 분산성이 입증됐고, 상태 유지 계산에 강점이 있다.
배치 처리는 스트림 처리보다 훨씬 덜 빈번하게 일어나며, 따라서 ML에서는 일반적으로 자주 변경되지 않는 피처를 계산하는 데 사용된다(ex. 운전자 평점). 배치 처리를 통해 추출된 피처는 배치 피처이며, 정적 피처라고도 한다.
스트림 처리는 빠르게 변경되는 피처를 계산하는 데 사용한다(ex. 현재 가용한 운전자 수, 최근 1분 동안 요청된 운행 수 등) . 스트림 처리를 통해 추출된 피처는 스트리밍 피처이며, 동적 피처라고도 한다.
배치 피처와 스트리밍 피처가 모두 필요한 문제도 많고, 7장에서 주로 다룬다.
효율적인 스트림 처리 엔진으로는 아파치 플링크, KSQL, 스파크 스트리밍이 있다. 셋 중에서도 특히 아파치 플링크와 KSQL이 업계에서 인정받고 있고, 데이터 과학자에게 멋진 SQL 추상화를 제공한다.
배치 프로세서가 스트림 처리를 하도록 만들기보다 스트림 프로세서가 배치 처리를 하도록 만드는 것이 더 쉽다. 아파치 플링크 메인테이너들은 배치 처리가 스트림 처리의 특수한 경우라고 수년간 주장하고 있다.
3.7 정리
ML 시스템 개발에서 데이터가 지니는 중요성을 알아보았다.
향후 데이터를 더 쉽게 사용하기 위해 데이터를 올바른 포맷으로 저장하는 것이 중요하다.
다양한 데이터 포맷, 행 우선 포맷과 열 우선 포맷의 장단점, 텍스트 포맷과 이진 포맷의 장단점을 논의했다.
주요 데이터 모델 세 가지, 즉 관계형, 문서, 그래프 모델을 다뤘다. 관계형 모델이 가장 잘 알려져 있지만 오늘날 세 가지 모델 모두 널리 사용되며 각각 특정 태스크 세트에 적합하다.
정형과 비정형 데이터 구분은 매우 유동적이다. 이 때 판단 기준은 데이터 구조를 처리하는 책임이 어디에 있는가이다. 구조를 처리하는 책임이 작성하는 코드에 있다면 정형, 읽는 코드에 있다면 비정형이다.
데이터 스토리지 엔진 및 처리를 다뤘다. 즉, 트랜젝션 처리와 분석 처리에 각각 최적화된 데이터베이스를 알아보았다. 전통적으로 스토리지는 처리와 연결되나 최근엔 그렇지 않다.
프로덕션에서 여러 프로세스로 작업할 가능성이 높으며 여러 프로세스 간에 데이터를 전송해야 할 수 있다. 관련된 세 가지 데이터 전달 모드를 알아보았다.
실시간 전송 대상이 되는 데이터는 데이터베이스의 데이터와 속성이 다르다. 따라서 다른 처리 기술(스트림 처리)이 필요하다.