Youtube : https://youtu.be/FUEZibcZEkg
서론
실전 프로젝트를 Stateful 하게 구현 해야한다는 생각을 마음속에 간직하고 있었습니다.
2,000ms 기준으로 사용자의 위치를 실시간으로 받아오면서 접속한 반경 이내에 있는 사용자에게 위치를 배포하는데, 이러한 행위를 매번 DB에 저장하고 반환받는 불필요한 행위를 할 이유가 없다고 생각하였습니다. 하지만 어떤 방식으로 Stateful 하게 구현해야 할지 알 수 없었고, 해당하는 기능을 구현한 프로젝트나 발표 자료를 찾아보았습니다. 그러던 도중 NDC2021에서 데브시스터즈의 서버 아키텍처 관련 발표가 있었습니다. 단순히 Stateful 에 대한 자료만 존재하는 것이 아닌 세부적인 로직과 구현하게 된 이유에 대해 들을 수 있었습니다.
내용 요약
1. 액터 모델을 바탕으로 분산 Stateful 서버 구현
2. 액터 상태를 DB에 저장하기 위해 이벤트 소싱 사용
3. 액터 요청 처리 (콘텐츠 로직) 을 위해서 함수형 프로그래밍과 프로그램 DSL 사용
4. 여러 액터간의 분산 트랜잭션 (유저간의 상호작용) 을 위해서 2PC 구현
5. 액터외의 다양한 컴포넌트들간의 통신을 위해서 이벤트 기반 아키텍처 도입
CRUD의 단점
- 서비스가 복잡해질 수록 점점 많아지는 DB 테이블과 쿼리들
- JOIN등을 활용한 복잡한 쿼리를 사용할 수록 부하가 몰렸을 때 어떤문제가 발생할지 파악이 잘 안된다.
- 특정 쿼리를 잘 못 짜면 그게 DB 전체의 성능에 악영향을 주기도 한다.
- 서버 코드내에 설계된 도메인 객체들과 DB 스키마가 잘 매핑되지 않는다.
이벤스 소싱 ☆
- DB 에는 최종 상태가 아닌 발생한 이벤트 들을 정의한다.
- 이벤트가 많이 쌓이면 리플레이할 때 시간이 오래걸릴 수 있다.
- 이벤트 소싱을 사용할 때는 스냅샷이라는 방법을 같이 사용해야한다.
- 스냅샷 테이블에서 최신의 상태를 먼저 가져오고 이후의 이벤트들에 대해서만 리플레이를 수행
- 서버에서는 INSERT, SELECT만을 사용한다.
이벤트와 스냅샷의 DB 저장 방법
- 가시성이 중요할 경우 JSON
- 저장공간 압축과 명시적이 스키마 관리가 중요할 경우 protofub
이벤트와 스냅샷의 삭제 시점
- 이상적 : 영구 저장
- 현실적 : 상태 리플레이에 필요없는 옛날 스냅샷은 실시간으로 삭제
이벤트 소싱을 위한 데이터베이스 선정
- 이벤트 소싱은 DB 사용 패턴이 매우 단순하기 때문에 DB 선택에 제약사항이 없다.
- 성능, 스케일링, 안정성, 노하우 등 다른 중요한 요소들을 바탕으로 결정하면 됨.
- 굳이 NoSQL 일 필요는 없다.
유저간의 상호작용
- 하나의 요청이 여러 유저의 상태를 동시에 변경시킨다.
- 하나의 정보가 여러개의 액터에 나뉘어서 저장되므로 일관성 보장에 주의해야한다.
일관성보장 실패의 해결 방안
- x 계속 재시도 한다.
- x 클라이언트에게 성공 응답을 준 뒤 계속 재시도한다.
- x 액터에 미리 반영됐던 결과를 다시 롤백한다.
- 2PC (2 Phase Commit) : 변경없이 여러 액터의 상태를 변경할 수 있도록 구성
- 강력한 일관성 보장이 가능
2PC (2 Phase Commit)
1. 유저가 요청을 보냄
2. 해당하는 모든 액터가 트랜잭션 상태로 진입, 그리고 메시지를 처리
3. 발생한 이벤트들을 DB에 직접 저장하는 것이 아니라 API에게 반환
4. API가 액터 A와 B 에서 발생한 이벤트를 모아 한번에 DB에 저장 ☆
5. 모든 액터에 성공했다는 Commit 메시지를 보내 트랜잭션을 종료한다.
- 데드락 등 많은 케이스들을 꼼꼼히 고려해야한다.
- 부하테스트를 통해 성능, 데드락 등의 문제가 없는지 꼼꼼히 검토해야함.
2PC (2 Phase Commit) 단점
- 분산 트랜잭션을 잘 지원하는 DB를 사용해야 한다.
- DB에서 샤딩된 데이터간의 트랜잭션 (분산 트랜잭션)을 지원해야한다.
- 샤딩된 RDBMS 또는 NoSQL 들은 일반적으로 분산 트랜잭션이 지원되지 않거나 좋지않을 경우가 많다.