4천만 MAU 를 지탱하는 서비스 설계와 데이터 처리 기술 강의 메모

1. 대규모 서비스에서 발생하는 기술적 이슈 및 장애 사례

1-1. IT 서비스 혁신 = 비즈니스 모델의 혁신 ∋ 기술의 혁신

{기술 발전}이나 {소비자 욕구 변화}에 따라 혁신적인 {제품 또는 서비스}를 제공
aws - {가상화 기술 발전}과 {합리적 소비 확산}에 따라 혁신적인 {컴퓨팅 클라우드 서비스}를 제공
	-> 가상화 컴퓨팅 파워를 온라인 pay-per-use 방식으로 유통해 글로벌 서버 시장 장악
여기어떄 - {위치 기반 기술}과 {온라인 예약 확산}에 따라 혁신적인 {숙박, 레저 서비스}를 제공
	-> 위치 기반 기술을 사용해 사용자에 인접한 객실 상품을 유통해 숙박 예약 시장 장악
그런데 인터넷 스케일(인터넷 고객 트래픽)을 만나면,
	{기술 발전} - 확장성이 없는 디자인 ...
	{소비자 욕구 변화} - Push 발소에 따른 먹통, 빈번한 예약장애, 매출 분석의 불편함 ...
	{제품 또는 서비스} - 느린 앱 성능
	-> 숙박 예약 시장 장악 실패
	이런 현상을 "기술 부채" 라고 함

1-2. 기술부채가 야기하는 문제들

기능 요구사항은 통과하는데 뭔가 잘못 짜여진 코드가 관리가 안될 떄까지 계속 불어나는 것
- 속도
	스프린트 당 처리되는 Task 수가 크게 감소
	-> 생산성 저하
- 스트레스
	개발팀의 일상적인 스트레스
	-> 잦은 퇴사
- 프로덕션
	수정 후에도 문제가 계속 발생
	-> 브랜드 이미지 실추
- 품질 
	릴리스 당 버그 수가 빠르게 증가
	-> 서비스 품질 저하

1-3. 기술 부채의 경고 징후

너무 데이터베이스에 의존적인 경우 절망적인 쿼리가 서서히 자란다
개발 부서가 시장 요구 사항(APIs)과 빈번희 변화하는 사업 요구사항(Back Office)을 들어주려다 보니 발생함
예시
	예약 내역 테이블
	요구사항) 관리툴에서 지난 4년간 매출을 월 단위로 볼수 있게 해주세요.
	Table: reservationTable / 예약 내역 테이블 4년간 예약 건수가 1억 5천건
	[Reserve ID	|	Cust ID	|	Payment	|	Item ID	|	Date]
	단일 쿼리로 추출 시)
		SELECT date_format(date, '%M-%Y') as sdate, sum(payment) as 'netsales'
		FROM reservationTable
		WHERE year(sdate) = year('2016-01-01')
		GROUP BY year(sdate), month(sdate)
		ORDER BY year(sdate), month(sdate)
		-> Payment, Date 컬럼 풀스캔 해야함
		-> 쿼리 수행 시간은 1s 내로 떨어지기 어려움
			data[][] = {
				{ 0, 1 },
				{ 1, 4 },
				{ 0, 2 },
				{ 2, 3 }
			}
			
			1) Group By 
				Map<Integer, List<Integer>> = {
					{ 0, { 1, 2 }},
					{ 1, { 4 }},
					{ 2, { 3 }}
				}
			
			2) Summation
				Output[][] output;
				Map.forEatch((k, v) -> {
					int sum = 0;
					v.forEach(e -> {
						sum += e;
					});
					output[k][0] = k;
					output[k][1] = sum;
				});
				
				output:
					{ 0, 3 },
					{ 1, 4 },
					{ 2, 3 }
			
			3) Sort
	
			복잡도)
				공간 복잡도: 15 * 10^7 * 4 bytes(Payment) * 10 bytes(Date) = 6GB
				시간 복잡도: O(NM)(Summation) + O(KlogK)(Sort)
				-> 1s 내에 해결할 수 있는 쿼리가 아님. 몇 십분이 걸릴 수 있다
	해결 방법)
		느린 쿼리를 새벽마다 배치로 돌리고 쿼리 결과를 테이블에 담는다.
		날짜를 기준으로 해당 월의 매출의 집계를 계산해서 결과를 결과 테이블에 추가한다.
			[MM	|	SUM], 4 * 12 row
		월 단위 매출 조회는 결과 테이블을 대상으로 한다.
		-> Materialized View 방식이라고 한다. 일종의 캐시 방식
	
	한계점)
		배치 쿼리가 도는 동안, 결제 대금 취소 또는 재결제, 즉 DELETE 나 UPDATE 처리가 동시에 일어나면?
		조회한 매출 결과가 실제 결과와 약간 다를 수 있다. 
		더 큰 문제는 예약 내역 테이블이 다른 테이블과 심오하게 관계를 맺고 있거나 너무 많은 배치 쿼리들이 돌아가고 있는 것이다.
			-> 기술부채, 알 수 없는 곳에서 문제가 터지고 관리가 안된다.
어떻게 극복할 수 있나?
		문제				|		원인			
	서비스 장애와 성능 이슈			|	알고리즘과 자료구조 역량
	트래픽 대응 및 유지보수의 어려움		|	확장성 있는 설계, 소프트웨어 디자인 역량
	제품 품질을 위한 매뉴얼 운영 인력의 증가	|	사람 대신 기계, 데이터 활용 역량

2. 확장성 있는 시스템 설계와 소프트웨어 디자인이란?

2-1. 확장성과 안정성 있는 서비스 시스템 설계 패턴

yahoo 구조
	client						server					DB
	http://yahoo.com		-->		Apache + Perl CGI		61.72.103.108
	DNS lookup 61.72.103.24				61.72.103.24
	
	이 구조에서 웹서버에 장애 발생 시 모든 서비스가 중단됨.
	DB 에서 장애가 나도 똑같이 모든 서비스가 중단됨.
	-> 서비스 안정성, 확장성이 떨어진다. 단일 장애 구간(Single Point of Failure)
	
	아래 구조로 단일 장애 구간 해소
	client			L4				server				DB
				요청		\/	Apache + Per CGI #1		\/	Primary DB
				분산		/\	Apache + Per CGI #2		/\	Secondary DB
					
	웹서버나 DB 에 장애가 나도 서비스는 살아 있다.
확장성이란?
	Server-level Scalability
		서버 측 요청 수 증가에 따라 서버 확장이 용이한가?
			소프트웨어 스택 선정
			데이터베이스 설계
			시스템 아키텍처
			배포 관리
	Code-level Scalability
		새로운 요구사항에 대해 코드 변경이 용이한가?
			언어 선정
			테이블 설계
			디자인 패턴
			버전 관리
웹서버는 확장이 용이하지만 DB 는 확장이 어렵다.
	네트워크로 연결된 공유 스토리지를 활용하기도 한다
		DB Instance #1	\ 		
				 >	SAN Storage
		DB Instance #2	/ 
		SAN Storage 는 SPOF 이지만 매우 고가의 장비라서 큰 장애를 만든 경력이 없다.
	웹서버의 확장이 용이한 이유
		서로 아무것도 공유하지 않는 구조이다(Stateless)
	데이터베이스의 확장이 어려운 이유
		데이터베이스는 공유 자원이고 데이터 관계의 복잡도 때문
요즘은 샤딩으로 트래픽을 분산함
	client		L4			server							DB
			요	\  /	Apache + Per CGI #1	--	H	\  /	Shard #1(A~D)
			청	 \/	  Apache + Per CGI #2	--	A	 \/	  Shard #2(E~O)
			분	 /\	  Apache + Per CGI #3	--	S	 /\	  Shard #3(P~U)
			산	/  \	Apache + Per CGI #4	--	H	/  \	Shard #4(V-Z)
	샤딩을 하고 각 샤드를 Active-StandBy 로 HA 구성을 해줘야 함				
	대다수의 인터넷 서비스가 가진 아키텍처
스트리밍이나 채팅 서버 같은 서비스는 L4 의 라운드로빈 방식으로 트래픽을 분산해도 로드밸런싱이 되지 않는다.
	계속 오랫동안 유지되는 트래픽이기 때문에 시간이 지나면 한 곳에 몰려 있을 수 있다.

2-2. 대규모 서비스에서 알고리즘과 자료구조의 중요성

내 주변에 청결한 객실을 보여주세요.
	-> 내 주변의 객실을 검색하라 -> 2차 평면에서 거리 계산 문제
		int[][] point = { {3, 3}, {5, -1}, {-2, 4} }
		1. 유클리드 거리 계산
		2. 원점으로부터 거리 순으로 정렬
		3. 상위 K 개 출력
	정렬 문제
					1천 개		1백만 개		10억 개
		Mergesort(NlogN)	즉시		1초		18분	
		Quicksort(NlogN)	즉시		0.3초		6분
		모텔이 1백만 개고 모텔 별로 객실이 10개가 되면 10 * 1백만을 정력해야 하므로 매우 무거운 계산이다.
	구면 코사인 법칙
		원점: (37, 122)
		SELECT	id, (3959 * acos(cos(radians(37)) * cos(radians*(lat)) * cos(radians(long)-radians(-122) + sin(radians(37)) * sin(radians(lat)))) AS distance
		FROM	myTable
		HAVING	distance < 10
		ORDER BY	distance;
		
		id		name 		Ion			lat
		1		Mark		-76.316528	40.036027
		2		John		-95-995102	4125167
		3		Paul		-82.337036	29.645095
		4		Dave		-82.337034	29.635096
		6		Chris		-76.316528	40.036027
		
		if 10^5 rows * 8 bytes * 8bytes = 64MB 크기의 메모리가 필요하고
		거리 계산 O(n) * 거리 정렬 O(nlogn) 이 걸린다.
		-> 1초 이내 계산할 수 없게 된다.
		-> 여러 건의 요청이 들어오면 데이터베이스에 거리 계산과 정렬 연산 부하가 몰리게 된다.
		-> 분산 처리를 위해 순전히 DB 서버에 의존하지 않고 API 서버의 리소스를 활용하기 위해 DB 에서 좌표만 가져오고 계산은 API 서버에서 한다.
			API Server #1 (좌표 값을 받아와 계산)	\ 		
			API Server #2 (좌표 값을 받아와 계산)	 >	DB Server
			API Server #3 (좌표 값을 받아와 계산)	/
		
		Q, 정렬을 빠르게 하기 위해 Ion, lat 컬럼에 인덱스를 걸면 빨리지지 않을까?
			ORDER BY 구절에서 정렬 문제는 전체 데이터 SET 을 가지고 와야하기 때문에 인덱스와 관계가 없다.
			-> MySQL 는 정렬을 빠르게 하기 위한 공간 색인 같은 알고리즘을 지원하지 않고 B-tree 인덱싱만 제공한다.
내 주변 택시 찾기
	택시처럼 위치가 수시로 변경된다면?
		공간 색인하는 시간이 있기 때문에 다른 고민이 추가로 필요하다.
안심번호 할당문제
	통신사로부터 10,000 개의 안심번호를 할당 받았을 때, 안심번호를 원하는 고객에게 할당해달라.
	
	SELECT id, telNum
	FROM myTable
	WHERE userID is null
	LIMIT 1;
	Then,
	UPDATE myTable
	SET userID = myID
	WHERE id = 3;
	id		telNum		lat
	1		1555-487	Null
	2		1585-5454	Paul
	3		1555-8874	Cris
	...
	10^3	1555-8711	Null
	-> 동시에 접근하는 경우, 경합(Race Condition) 문제가 발생한다.
		API 서버가 분산된 환경에서 USER1, USER2 가 동시에 요청을 하게 되면 동일한 레코드를 반환받고 경합 문제가 발생한다.
		API Server #1 (USER1) SELECT id, telNum ...	\ 		
		API Server #2 			 		 >	DB Server
		API Server #3 (UESR2) SELECT id, telNum ...	/
	-> 경합 문제를 해결하기 위해 lat 가 NULL 인 id 중 랜덤으로 아이디를 뽑는 방법이 있다.
		동시 접속자가 많아지면 중복이 생길 확률이 많아져 완벽하지 않고 간헐적으로 장애가 발생함
		SELECT id, telNum
		FROM myTable
		WHERE userID is null
		ORDER BY rand()
		LIMIT 1;
		Then,
		UPDATE myTable
		SET userID = myID
	-> 공유 자원에 대한 동시 접근 제어 처리 지식이 있어야 해결 가능
		Lock, Mutex 와 Semaphore 
		마이크로서비스 환경에서 여러 서비스들이 동시에 메인 DB 에 접근할 때
			마이크로서비스#1		마이크로서비스#2		락매니저		메인DB
				□──────────────────락 요청─────────────────>□
				□<─────────────────락 회신──────────────────□
							■─────락 요청──────>■
							■<────락 획득실패───■
				□────────────────────────레코드 수정──────────────────────>□
				□<────────────────────────수정 성공────────────────────────□
				□─────────────────락 해제─────────────────>□
간단한 기능을 구현하더라도 알고리즘 내부 동작을 알고 모르고에 따라 구현의 성숙도, 품질, 생산성이 크게 달라질 수 있다.

2-3. 코드 유지보수성과 확장성을 높이는 디자인 패턴

2-3-1. 확장성이란?

_ 서버 레벨 코드 레벨
정의 서버 측 요청 수 증가에 따라 확장이 용이한가? 새로운 요구사항에 대해 코드 변경이 용이한가?
핵심문제 1. 소프트웨어 스택 선정
2. 데이터베이스 설계
3. 시스템 아키텍처
4. 배포 관리
1. 언어 선정
2. 테이블 설계
3. 디자인 패턴
4. 버전 관리

2-3-2. 프로그램 패러다임

1) 절차적 프로그래밍 - 명시된 입력을 받아서 순서대로 처리 - 너무 복잡함 2) 구조적 프로그래밍 - 코드를 함수로 쪼갬(탑-다운) - 데이터 구조화가 너무 어려움 3) 객체지향 프로그래밍 - 데이터와 관련 코드를 결합한 작은 객체를 기본 단위로 큰 문제를 해결(바텀-업) - 역할 중심으로 문제를 Divide and conquer - 소프트웨어가 거대해지면서 복잡해지기 시작 -> 디자인 패턴의 대두

2-3-3. 디자인 패턴

  • 문제를 해결하기 위한 모범 사례의 일반화된 디자인 유형
  • 사례1) 객체 합성
    • 객체합성 구조를 통해 확장성을 획득
    • 클라이언트는 인터페이스만 바라보고 사용
    • 실제 구현이 인터페이스 아래로 추상화 되기 때문에 새로운 기능을 개발해 추가해도 클라이언트에 수정이 필요 없음
    • 기능 추가로 인한 수정사항이 인터페이스 아래로 캡슐화 됨

2-3-4. 의존성 주입

  • 객체지향 설계 패턴의 보조도구
  • 인터페이스와 구체 클래스의 관계를 런타임에 맺어준다.
  • 간편결제 추가 도입 사례
    각 객체 모듈이 아래와 같이 개별적으로 개발되어 있다면,
      Client--------PayModule1(카드결제 모듈)------PG사
          ├---------PayModule2(계좌이체 모듈)--------┤
          ├---------PayModule3(가상계좌 모듈)--------┘
          └---------PayModule4(간편결제 모듈)--------간편결제 업체-----PG사
      새로운 간편결제 모듈을 추가하기 어렵고 각 결제 모듈 구현 방법이 달라
      이슈 발생 시 개발한 사람만 대응할 수 있다.
    
    객체 합성과 의존성 주입을 도입하면,
      Client-----PayService-----PayModule1(카드결제 모듈)-----PG사
          ├--------PayModule2(계좌이체 모듈)-----┤
          ├--------PayModule3(가상계좌 모듈)-----┘
          └--------PayModule4(간편결제 모듈)-----간편결제 업체-----PG사
      ※ 구체 클래스 주입은 xml 같은 설정 파일로 빼둠
      PayService 인터페이스에 맞게 새로운 결제 모듈을 만들면 클라이언트 부분은 수정이 필요없고
    구현 방법과 히스토리를 알 필요 없어 이슈 발생 시 대응하기 쉽다.
    
  • 논리적 아키텍처
    • 대규모의 소프트웨어 클래스를 패키지, 서브시스템 및 레이어로 조직화 한 것
  • 함수형 언어 모델
    • 함수형 언어 모델을 분산 병렬 컴퓨팅에 사용
    • 일급 함수의 조합을 통해 문제를 해결한다.
    • 함수 내에서 상태를 가지지 않아 동시성 문제가 발생하지 않는다.
    • 함수형 언어에는 디자인 패턴이 필요한가?
      • 함수형 프로그래밍은 객체지향 프로그래밍과 다릅니다.
        따라서 객체지향 디자인 패턴은 적용되지 않습니다.
        대신 함수형 프로그래밍 디자인 패턴은 있습니다.
      • 커링 패턴 : Function Composition. 함수를 인자로 받거나 결과로 반환하는 함수, 콜백도 커링 패턴의 일종이다.

2-4. 데이터 모델과 트랜잭션 디자인

서버를 개발할 때 데이터 처리는 중요하다.

2-4-1. 논리적 데이터 아키텍처

데이터 스키마 또는 논리적 데이터 모델

  • 객체 기반의 개체-관계 ER(Entity-Relationship) 모델
  • 행 기반의 관계형(Relation) 모델
  • 중첩된 키, 값 쌍 다차원(NoSQL) 모델

2-4-2. ORM(Object Relational Mapping)

  • 관계형 데이터 모델을 OOP 의 Entitiy 형태로 매핑 시키는 방법
  • 객체지향에서의 상속 관계를 관계형 데이터베이스 모델로 매핑하는 다양한 방법이 있다.
  • ORM 은 지속적인 서비스를 운영하고 소프트웨어를 개발, 배포하기 위해 사용한다.
  • ORM 는 DB 와 SW 중간의 Persistence Layer 라고 한다.
    • DBMS 에 대한 종속성이 줄어든다.
    • 재사용 및 유지보수가 쉽다.
    • 직관적인 코드로 볼 수 있다.
  • 대규모 서비스의 업데이트는 부분적으로 진행되는 경우가 많다. 따라서 데이터의 이전 버전과 새 버전을 동시에 사용할 수 있어야 한다.
    • 이런 문제를 ORM 은 최소화 할 수 있도록 지원한다.
    • 데이터베이스와 관련된 선언문, 할당, 종료 등 statement 관련 코드들이 사라져 객체지향적인 코드만 남아 직관적이다.

2-4-3. 트랜잭션

  • 데이터베이스의 상태를 변화시키기 위해 수행하는 논리적 작업의 단위
  • 예를 들어, 페이스북에서 친구 요청을 수락할 때,
    • transaction: {(1)나의 친구 목록에 추가함과 동시에(insert), (2) 상대방의 친구 요청 대기 상태를 수락으로(update)}
    • 그래서 트랜잭션은 원자성, 일관성, 독립성, 지속성을 지켜야 한다.

2-4-4. 트랜잭션 인터페이스 디자인

  • 클라이언트(DB 서버에 요청하는 API 서버)에게 트랜잭션 존재를 알릴 것인지 말 것 인지가 가장 중요하다.
  • 트랜잭션을 명시해 시작과 끝을 제어할 수 있게 하거나 추상 트랜잭션 메커니즘으로 숨겨 클라이언트가 신경쓰지 않게 개발할 수 있다.

2-5. 개발 프로세스 운영 관리 및 배포 시스템 디자인

2-5-1. 서비스 무정지 배포 패턴

  1. Rolling Deployments
    • 순차적으로 배포
    • 이전 버전과 새 버전 간의 호환 관리를 철저히 해야 한다.
  2. Blue/Green Deployment
    • 전체 서버를 똑같이 복제하고 한 번에 전체 서버를 스와핑 하는 방식
    • 비용이 많이 듬
    • 서비스를 100 대 서버로 운영 중일 때 배포를 위해 200 대의 서버가 필요함
    • 롤백이 필요할 때 로드밸런서에서 이전 서버로 주소만 바꾸면 되기 떄문에 간단하다.
  3. Canary Deployments
    • 트래픽을 순차적으로 보내보는 방식
    • 이전 버전과 새 버전 간의 호환 관리를 철저히 해야 한다.
    • 기존 버전과 신규 버전의 비교가 필요할 때 주로 사용한다.
  4. 경험적 사례
    • 서버 API 구간에서 버그가 있을 때, 개발 서버에서 수정 후 정상 적으로 동작하는지
      운영 서버에 배포해서 확인하고 싶을 수 있다. 이 때, Rolling Deployments 가 유용하다.
    • 1.0 버전에서 2.0 버전으로 차세대 버전으로 업데이트 등 대규모 업데이트가 있을 때,
      비용을 감내 해서라도 Blue/Green Deployments 를 사용한다.
    • Canary Deployments 는 통계적으로 테스트가 하고 싶을 떄 활용하면 좋음

3. 대규모 서비스에서 발생하는 데이터 처리

3-1. 데이터의 개념과 종류에 대한 이해

  • 데이터
    • 명시적(정형 데이터)
      • 질적
        • 명목형 : 속성이나 범주를 갖는 자료 ex) 성별, 지역
        • 순서형 : 순서의 의미를 갖는 자료 ex) 평점, 선호도
      • 양적
        • 이산형 : 이산적 값을 갖는 자료 ex) 출산 횟수
        • 연속형 : 연속적인 값을 갖는 자료 ex) 키, 몸무게행동 로그 - 구조가 없는 데이터
    • 암묵적(비정형 데이터)
      • 주어진 정보를 통해 유추할 수 있는 정보 ex) 3점 미만의 영화는 재미가 없다.
  • Bounded and Unbounded Data
    • Bounded data : 이미 저장되어 있는 데이터
    • Unbounded data : 계속 증가하는, 쏟아지는 데이터
  • 인터넷 서비스에서 수집되는 데이터
    • 시스템 로그
      • 클라이언트 로그
      • 서버 로그
      • 데이터베이스 로그
    • 유저 로그
      • 1st Party 로그
        • 서비스 로그 ex) 결제 금액, 구매, 시청, 평점
        • 행동 로그 ex) 버튼 클릭 이벤트, 검색
      • 3rd Party 로그
        • 내부에서 수집할 수 없는 데이터 ex) 유입 경로, 소셜 로그인

3-2. 관계형 데이터베이스와 NoSQL 그리고 파일 저장소

  • 자료구조
    • 단순구조 : Integer, String…
    • 선형구조 : List, Queue, Stack…
    • 비선형구조 : Tree, Graph…
    • 파일구조 : Text, Sequence, Index…

3-2-1. 1억 개 이상의 elements 를 갖는 List 구현하기

  1. Binary Sequence File 로 구현
[List Size|   idx   |  value  |   idx   |  value  |   idx   |  value  | ... ]
[ 4 bytes | 4 bytes | 4 bytes | 4 bytes | 4 bytes | 4 bytes | 4 bytes | ... ]

idx + value (8 bytes) = Element

구현 링크

  1. DB 를 활용해 구현
id value
bigList(id int(4) auto_increment, value int(4))

get(int index) {
  q = "select value from bigList where id = " + index;
  return q.result();
}

add(int value) {
  q = "insert into bigList (value) values (" + value + ")";
  q.executeStatement();
}

3-2-2. 자료구조와 데이터베이스

Binary Sequence File 에 remove() 가 발생하면?

1, 2, 3, 4, 5 자료가 있는데 3이 삭제되고 3을 조회하면 최악의 경우 3을 기준으로 좌우로 풀 스캔이 일어나 O(n) 연산이 발생한다.

그래서 DB 는 remove 된 다음 검색이 느려지는 것을 방지하기 위해 PK 를 가지고 트리 형태로 인덱스 파일을 생성한다.
인덱스 파일을 통해 특정 값을 검색하는데 O(nlogn) 이 걸린다.
트리 형태의 인덱스 파일은 검색을 위한 구조이고 실제 데이터는 Bytes Array Sequence File 로 저장 되어있다.

프로그래밍 언어에 대부분 존재하는 built-in 자료구조(List, Queue, Stack…) 은 메모리와 디스크에 쓰고 읽는데 사용할 수 있다.
RDBMS 이건 NoSQL 이건 기본적인 컨셉은 자료구조를 저장하는 데이터 저장소 이다.

파일 기반의 경량 데이터베이스 SQLite 같은 것은 파일 데이터를 프로세싱 하는 프로그램으로 볼 수 있다.
Redis 나 MemCache 는 인메모리 해쉬 테이블 저장소이다.
따라서 자료구조에 대한 이해가 있으면 데이터베이스의 동작을 자연스럽게 유추할 수 있다.

3-2-3. 데이터 처리

  1. 데이터 처리 방식

RDBMS 에는 Row wise 저장소, Column wise 저장소가 있다.
A, B, C column 을 가지고 여러 개의 row 가 있다고 할 때,
Column Operation 은 AAABBBCCC… 형식이고 Row Operation 은 ABCABCABC…형식이다.

Row wise 는 행 단위 연산에 최적화 되어 있기 때문에 특정 column 에 대한 집계 연산인 ORDER BY 나 GROUP 같은 연산이 많이 느리다.
그러나 특정 row 를 가져올 때는 데이터 skip 없이 쭉 읽어오면 되기 때문에 빠르다.
그리고 고정된 스키마와 공간 확보가 필요하다.

Column wise 는 특정 column 에 대한 풀스캔이나 집계 연산에 데이터 skip 없이 쭉 읽으면 되기 때문에 빠르게 연산할 수 있다.
그러나 특정 row 전체를 가져오기 위해선 데이터 skip 을 해야하기 때문에 느리다.
그리고 스키마 변경이 용이하고, 탄력적으로 공간을 활용할 수 있다.

  1. 데이터 처리의 성능 차이
id name age gender address number
           

userTable 의 column 이 위와 같고 80 억 개의 row 가 있다고 가정할 때,

“select gender, count(gender) from userTable group by gender;”

성별에 따른 인구 수를 연산하는 쿼리를 실행하면 Row Store, Column Store 에 따라 성능 차이가 많이 난다.

  • Row Store
id name age gender address number
1 aaa 0
2 bbb 1
3 ccc 0
4 ddd 0
5 eee 0
6 fff 1

위 형식으로 저장되고 각 row 의 gender 를 얻기 위해 Stride Access 를 하게 된다.
8 * 10^9 * 64 bytes = 512 GB 를 읽어와야 하고, 약 128,000 ms 가 걸린다.

  • Column Store
id ■■■■■■■■■■■■■■■■■■■■■■■■
name ■■■■■■■■■■■■■■■■■■■■■■■
gender ■■■■
address ■■■■■■■■■■■■■■
number ■■■■■■■■■■■■

위 형식으로 저장되고 Attribute Vector 들을 가지게 된다. gender 이 저장된 Vector 만 읽으면 되므로
8 * 10^9 * 1 bit = 1 GB 를 읽어오고 250 ms 가 걸린다.

3-2-4. 파티셔닝

조회와 연산 성능을 높이기 위해 테이블 파티셔닝을 사용할 수 있다.

  • 수직 파티셔닝 RDBMS 에서 특정 column 풀스캔이나 집계 연산이 많은 경우 수직 파티셔닝을 통해 별도의 테이블을 만든다.
    그러나 수직 파티셔닝이 너무 많이 생기면 JOIN 연산이 빈번해 지고 테이블간 관계가 복잡해 지는 문제가 생긴다.

  • Horizontal Partitioning (샤딩) row 가 많은 경우 같은 테이블을 여러 개로 나누고 PK 에 따라 row 를 분산해 저장한다.
    연산을 분산처리 할 수 있는 장점이 있지만 관리 및 프로세싱 복잡도가 증가하는 문제가 있다.

3-2-5. NoSQL 의 출현

인터넷의 폭발적인 데이터 증가에 따라 빠른 연산을 위해 NoSQL 이 출현했다.
NoSQL 은 Columnar Store 이고 아래와 같은 장점을 가진다.

  • 스키마 변경 용이
  • 분산 저장
  • 분산 처리
  • 압축 저장

3-2-6. 일반적인 저장소 선택

  • 파일 저장 : 시스템 로그 처럼 접근 빈도가 낮은 데이터
  • NoSQL : 행동 로그(검색, 탐색, 클릭…) 처럼 다양한 분석이 필요한 데이터
  • Multi-DB 혼합 : 서비스 로그(구매, 시청, 평점…) 처럼 서비스 상 실시간 성능과 안정성을 요구하는 데이터
    RDBMS, RTDB, Time-series DB, NoSQL, In-memory DB 등을 혼합해 사용한다.

3-3. 서비스를 위한 실시간 속성 데이터 처리

3-3-1. 실시간 시스템

자원이 한정되어 있는 상황에서 작업 수행이 요청 되었을 때 제한된 시간 안에 처리해 주는 것.
작업 처리에 걸리는 시간을 일관되게 유지할 수 있는 정도에 달려있다. 따라서 스케줄링이 중요하다.

  1. 스케줄링 방식
  • 일괄 처리 구동 방식 → FIFO 스케줄링
  • 이벤트 드리븐 구동 방식 → 우선순위 또는 선점형 스케줄링 등
  • 시분할 스케줄링 → 라운드 로빈
  • 멀티 태스킹 방식 → 자원분배(작업의 동시 수행/병렬 처리)
  1. 데이터베이스의 시스템은 어떤 방식인가?

실시간 시스템 + 멀티 태스킹 방식

  • MySQL 은 기본적으로 Multi-threads 로 처리(운영체제가 결정)
    각 스레드는 운영체제에게 시스템 자원을 할당 받아야 실행됨.
  • 데이터 처리가 무작위 순으로 처리됨. 동시성, 락 등의 이슈가 발생함
  • 최근에는 Thread Scheduler (Priority 와 event scheduler) → 이벤트 드리븐 구동 방식

3-3-2. 매출 데이터 처리 사례

client #1   \  /    API server #1   \             /
client #2    \/     API server #2    \    메인   /      Back
client #3    /\     API server #3    /    DB     \      Office
client #4   /  \    API server #4   /             \

위 구조에서 내 주변에 청결한 객실을 보여달라는 시장 요구사항과
매출 통계를 월 단위로 뽑아 달라는 사업 요구사항이 있다.

이 문제를 실시간 시스템 + 멀티 태스킹 방식의 구조에서 새벽 배치 쿼리를 통해 일괄 처리를 하려 했다.
실시간 시스템이 + 멀티태스킹 방식인 RDBMS 에 의존해 문제를 해결하려 한 것은 시스템 선정과 접근 방식이 잘못 되었다고 볼 수 있다.
이 문제는 분산 시스템 + 일괄처리 방식이나 실시간 시스템 + 이벤트 드리븐 방식으로 해결하는 것이 바람직하다.

2018년 1월 매출 데이터 같은 Bounded Data 를 처리할 때는 분산 시스템 + 일괄처리 방식이 유용하다.
그리고 최근 3개월 매출 데이터 같은 Unbounded Data 를 처리할 때는 실시간 시스템 + 이벤트 드리븐 방식이 유용하다.

3-3-3. Batch vs Streaming

다양한 데이터들이 쏟아져서 저장되어 있고 이런 Bounded Data 를 처리할 때, 분산처리 시스템과 배치 방식이 적합하다.
예를 들어 특정 기간 동안 어떤 유저가 어떤 검색어를 얼마나 검색했는지 데이터 추출이 필요할 때 배치 프로세싱을 통해 적절하게 처리할 수 있다.

실시간으로 유입 중인 다양한 데이터 중에 특정 데이터만 보고 싶을때, 이런 Unbounded Data 는 Streaming 처리 방식이 적합하다.
예를 들어 실시간으로 사용자들이 검색어를 입력하고 부적절한 검색어는 허용하지 않을때, 실시간 검색어를 Streaming 처리해 필터 한다면 검색 엔진 서버의 트래픽 부하를 많이 줄일 수 있다.

3-3-4. 실시간 인기 검색어 사례

실시간 순위 프로세싱 = (관측 횟수 - 기대 횟수) / 표준편차 + 순위차 보정

  1. 기대 횟수 기대 횟수 = max(과거 일주일 평균 검색 횟수, 어제 검색 횟수)
    특정 기간의 Bounded Data 를 처리하는 것 이므로 배치 프로세싱이 적합하다.

  2. 관측 횟수 실시간으로 들어오는 검색어는 Streaming 프로세싱을 해야 한다.
    검색 서버에 검색어가 들어 올 때 마다 카운팅을 하고 랭킹을 계산해야 한다.

3-3-5. 실시간 매출 통계 사례

실시간으로 발생하는 매출을 계산하고 결제 취소를 필터하는 Streaming 프로세싱 해, 확정된 결제 건에 대해서만 집계를 해서 materialized view 나 결과 데이터만 저장된 DB 를 통해
실시간 대시보드를 구현할 수 있다.

3-3-6. 고객 포인트 소멸 기술 부채 사례

서비스 유저가 수천만명에 달하고 가입 또는 프로모션 참여로 받은 포인트를
결제 또는 소멸 시효에 따라 차감해야 할 때 어떻게 해결할 것 인가?

마이페이지 조회가 빈번하고, 응답 속도가 빨라야 한다.

포인트 내역 테이블 구조가 아래와 같고 수십억 row 들이 있다.

ID 포인트 내역

이 테이블을 새벽마다 묵직한 배치 쿼리를 통해 잔여 포인트 합계 테이블이라는 materialized view 테이블을 만들어 해결한다고 한다.

잔여 포인트 합계 테이블 구조는 아래와 같고 사용자 수 만큼 수천만 row 들이 있다.

userID P_SUM

그런데 배치가 도는 와중에 포인트 결제가 일어나면,
포인트 내역 테이블과 잔여 포인트 합계 테이블의 정합성 문제가 발생하고
온갖 락 관련 장애와 포인트 오류 문의가 발생할 것이다.

따라서 실시간 시스템 + 배치 프로세싱을 통한 문제 해결은 잘못 되었다.

실시간 시스템 + Event-driven 이 이 문제를 해결하는 방법이다.

결제 이벤트, 소멸 이벤트 등 포인트 내역 테이블에 데이터 변경이 생기고
데이터 변경 이벤트를 뿌리는 Publisher 와 데이터 변경을 구독하는 Subscriber 가 있다면 포인트 관련 데이터를 실시간으로 처리할 수 있다.
이를 통해 마이페이지에 포인트 변경 내역을 실시간으로 보여줄 수 있다.

▲ The Red : 4천만 MAU를 지탱하는 서비스 설계와 데이터 처리 기술

3-3-7. Real-time DB

테이터베이스의 내용을 저장하고 앱 간 실시간 동기화 하는 서비스(Firebase RTDB)

과거에 공장 자동화, 항공 우주, 교통 제어, 주식 거래 등을 위해 실시간 데이터베이스의 연구가 있었다.
그러나 대부분 짧은 주기의 폴링 기법이 활용 되었다.

요즘 나오는 실시간 DB 는 Pub/Sub 패턴을 사용해 real time 에 가까운 성능을 낸다.

3-3-8. 우버 사례

우버는 차량을 호출하는 Demand 와 운행 서비스를 제공하는 Supply 가 있다.
Demand 와 Supply 는 같은 내비게이션 정보가 동기화 되어야 한다.

서버는 nodejs 를 사용하고 데이터 송수신을 위해 웹소켓을 사용했다.
위치 정보는 In-memory DB 의 Consistent Hashing Cluster 로 저장된다.

3-3-9. 실시간 속성의 서비스 데이터 처리

오늘날의 실시간 서비스 구현은 RDBMS 만으로 구현하기 어렵다.
컴퓨터 시스템 구조 그리고 스케줄링, 디스패칭, 멀티태스킹 같은 기본 개념을 이해하고,
RDBMS, Pub/Sub 패턴, 실시간/시계열 DB, NoSQL 을 활용해 문제를 적절히 해겨하는 설계 능력을 키우는 것이 중요하다.

3-4. 데이터 분석과 응용을 위한 배치 처리

3-4-1. 기업에서 데이터 분석의 목적

시장과 고객을 이해하고 제품을 고도화 하기 위해 분석한다.
데이터는 학습 재료 → 시장 학습 → 더 나은 제품 출시한다.

3-4-2. 고객 분석을 위한 테스트 기법

  • 종단적 조사
    • 표본 대상 또는 상황 하나를 시간을 두고 동일한 내용을 반복적으로 측정해 인과 관계를 도출한다.
    • 코호트
  • 횡단적 조사
    • 한 번에 광범위한 표본 집단을 대상으로 집단 분류를 한다.
    • A/B 테스트
    • 다변량 분석

3-4-3. 횡단적 조사 예

신규고객매출비 = Revenue of new customer / Revenu of exsting customer 값을 가지고 기존 고객의 소비를 유도할 지 신규 고객의 소비를 유도할 지 결정할 수 있다.

신규고객매출비 데이터를 내려면 날짜 기준 그룹핑, sum(Revenue), 소팅이 필요하다.
하루 평균 결제가 20만 rows 이고 1년에 73,000,000 rows 이다.
따라서 데이터를 얻는데 어려움이 있다.

이 문제를 해결하기 위해 이벤트 드리븐 방식의 결제 발생 시 이벤트를 보내는 Publisher 를 만들고 Sum() 연산을 하는 Subscriber 를 만들어,
결제 발생 때 마다 일별 매출 합계 테이블을 업데이트 하는 방식으로 해결할 수 있다.

▲ The Red : 4천만 MAU를 지탱하는 서비스 설계와 데이터 처리 기술

3-4-4. MapReduce 분산 프로그래밍 모델

이 문제를 이벤트 드리븐 방식 말고 MapReduce 분산 프로그래밍 모델로 해결할 수 있다.

Map Data X \
            > Group by, Sortby day → Reduce Data
Map Data Y /

(date, payment)
2019-10-21 12:32:04, 3400
2019-10-21 17:07:32, 2400
...

(day, list<p1, p2, ...>)
2019-10-21, <3400, 2400, ...>

<day, dailyRevenue>
2019-10-21, 12,578,600
2019-10-22, 9,235,323
...

3-4-5. 종단적 조사 예

특정일에 추진한 “가입 이벤트” 로 들어온 고객과 평일 가입 고객을 비교하려 할 때, 사용할 테이블은 아래와 같다.

유저 테이블

UserID 가입 날짜 가입 채널 이름
1 20191021 자사홈페이지 윤진석

결제 내역 테이블

UserID 아이템ID 결제 금액 결제 날짜
1 34 34500 20191021

날짜 별 가입자 코호트에 해당 일의 매체별 가입자 수, 그들의 구매 횟수

Cohort 가입 채널 회원가입 첫 구매 두 번째 구매
20191021 네이버쇼핑 780 9 3
20191021 자사홈페이지 210 17 9
20191021 네이버쇼핑 794 12 7
20191021 자사홈페이지 110 11 3

3-4-6. 분산 처리로 해결

이 문제를 아래와 같이 분산처리로 해결할 수 있다.

Map Data X \
            > Shuffle by User → Reduce User to Cohort → Shuffle Cohorts → Reduce Cohorts
Map Data Y /

Data X = user1 → {'cohort':'2019-10-21' 'isgn_up': 1, 'source': 'hompage'} ...
Shuffle by User = user1 → {'cohort':'2019-10-21' 'isgn_up': 1, 'source': 'hompage'}, {'purchased': '1', 'cohort': '2019-10-22'}, ...
Reduce User to Cohort = '2019-10-21:homepage' → {'purchased': 1}
Shuffle Cohorts = '2019-10-21:homepage' → [{'purchased': 1}, {'sign_up': 1}..]
RecueCohorts = '2019-10-21:homepage' → {'purchased': 10, 'sign_up': 5}

3-4-7. ML 사례, 웹툰 개인화 추천

웹툰 장르별 인기도를 보면 연예/순정, 개그/유머가 압도적으로 인기가 많고 스릴러, 시대/역사는 인기가 없는 것으로 보인다.

하지만 나이대 차원을 하나 늘려서 데이터를 확인해 보면 15-25 나이 그룹은 격투, 스릴러를 압도적으로 좋아하는 것을 확인할 수 있다.

웹툰에서 개인화 추천 기능을 만든다고 하면, 인기 요인이 무엇인지 먼저 분석(Feature Engineering) 해야 한다.

Consumer 별로 웹툰의 인기도가 다를 수 있다. 나이대, 성별, 소비 기록, 검색 기록마다 다를 수 있다는 특징을 잡아낼 수 있다.

Provider 별로 고객을 어떻게 빨아들이는지 다르다. 줄거리, 그림체, 작품 신선도 마다 다르다.

Feature Engineering 이 끝나면 유저를 Feature Space (특징 공간) 에 임베딩 한다.
이 과정에서 데이터를 추출하고 변형하는 작업을 진행한다.
수백 GB ~ 수 TB 데이터를 가지고 데이터 추출 및 변형 하면 4천만 user * 1만 features = 4억 cells (8 bytes per cell = 3.2GB) 가 필요하다.

특징 공간이 나오면 다음으로 기계 학습을 수행한다.
기계 학습 = 최적화 된 초평면을 찾는 작업
학습이 끝나면 고객의 특징을 가지고 어떤 장르를 좋아할 지 분류를 할 수 있다.