[AWS] DataBase — DynamoDB
DynamoDB는 NoSQL 데이터베이스로 완전 관리형이자 서버리스이며 대규모 확장성을 갖습니다. 초당 1백만 개의 요청을 처리할 수 있죠. Apache Cassandra의 아키텍처와 유사한데요. Cassandra 사용자는 DynamoDB로 이전(Migrate)할 수 있죠.
예를 들어 프로비저닝할 디스크 공간이 없다면 객체의 최대 크기는 400KB가 될 거예요. DynamoDB에 더 큰 객체를 저장하는 방법은 객체를 Amazon S3에 저장하고 그리고 객체의 참조를 DynamoDB에는 저장하는 거죠.
용량에는 두 가지 모드가 있는데요.
우선 프로비저닝 모드는 쓰기와 읽기 용량 단위를 정하거나 오토 스케일링을 원할 때 사용합니다. 온디맨드 모드도 사용할 수 있는데요. 모든 읽기와 쓰기에 대해 비용을 지불하죠. 이 모드는 예상치 못하거나 기대하지 않은 형식의 워크로드가 많을 때 적합한 모드입니다.
프로비저닝 모드가 가장 매끄러운 형식으로 확장되고 비용 면에서도 낫습니다. 반면 온디맨드 모드는 패턴을 미리 알 수 없거나 개발 중인 경우에 매우 적합한 모드죠.
CRUD 를 지원하므로 생성, 읽기 업데이트, 삭제가 가능하고 읽기는 최종 일관성과 강력한 일관성을 지원합니다. 다중 테이블 간의 트랜잭션을 지원하므로 DynamoDB로 ACID 지원을 받을 수 있죠. 백업과 지정 시간 복구(PITR)도 가능합니다. 마지막으로 테이블 클래스가 두 개 있습니다.
데이터 접근 빈도에 따라 Standard와 Infrequent Access로 나뉘죠.
DynamoDB — Basics
DynamoDB 는 테이블로 구성되며 각 테이블은 생성 당시에 결정해야 하는 기본 키를 갖습니다. 가질 수 있는 항목은 무한하며 항목이 들어가는 곳이 행 입니다. 그리고 각각 속성을 갖는 게 열이 되죠. 속성은 계속해서 추가가 가능하고 null 이 될 수도 있죠. 말했다시피 항목의 용량은 400KB 를 넘을 수 없습니다. 지원하는 데이터 유형은 스칼라 유형의 문자열 숫자, 바이너리, 불리언, Null 입니다.
문서 유형에는 목록과 맵 이 있고 집합 유형으론 문자열 집합 숫자 집합, 바이너리 집합이 있죠
DynamoDB — Primary Keys
첫 번째 옵션은 해시(HASH)인 파티션 키입니다. 각 항목의 파티션 키는 고유해야 합니다.
기본 키에서 가장 중요한 게 고유성 이거든요. 그러니 파티션 키는 데이터가 분배되도록 다양해야 합니다. 가령 사용자 테이블에는 user_id 가 훌륭한 파티션 키가 되겠죠.
파티션 키가 있으면 이름과 나이 같은 다양한 속성이 있을 거예요. 사용자 정보가 이렇게 만들어지겠죠. John 과 Katie 에 해당하는 각 user_id 가 있을 겁니다. user_id 는 하나만 있으니 고유하기 때문에 파티션 키로 적절한 선택지가 됩니다.
두 번째 옵션도 파티션 키를 사용하는 건데요. 기본 키가 되고 컴포지트(composite) 키가 됩니다. 파티션 키와 정렬 키의 조합이죠. 이 경우에는 두 키의 조합이 고유 해야 합니다. 파티션 키와 연결된 정렬 키가 매번 바뀐다면 파티션 키는 계속 같아도 됩니다.
데이터는 파티션 키에 따라 논리적으로 테이블에 그룹화 되죠. 정렬 키를 흔히들 범위 키 로도 부릅니다. 가령 users-games 테이블이 있다면 파티션 키 로는 user_id 가 좋고 정렬 키로는 game_id 가 좋겠죠. 그래야 사용자가 여러 게임을 할 수 있으니까요. game_id 와 user_id 의 조합은 고유하니까 탁월한 선택이 되겠죠.
정렬 키 로는 타임스탬프를 사용할 수도 있습니다. 시간에 따라 발생하는 동작을 특정 user_id 등에 나타내는 거죠. 파티션 키로는 user_id 를 선택하고 정렬 키 로 는 타임스탬프 열을 선택합니다.
user_id 와 game_id의 예시입니다. 파티션 키와 정렬 키가 있죠. 두 키의 조합으로 고유한 컴포지트 키가 되죠. 조합의 결과가 속성이 될 수 있고요.
물론 이건 예시입니다. user_id 인 12broiu45 의 경우 게임을 두 번 했지만 한 번은 이기고 한 번은 졌죠. 그러니 user_id 와 game_id 의 조합이 달라서 고유하다는 걸 알 수 있습니다.
DynamoDB — Indexes
인덱스를 살펴봅시다. 객체는 기본 키로 구성 되었죠. 선택 사항인 정렬 키와 일부 속성이 있습니다. 거기에 인덱스를 정의할 수 있는데요.
첫 번째는 로컬 보조 인덱스인 LSI 입니다. 이 경우 기본 키는 같은 키를 사용하고 정렬 키로는 다른 키를 선택해야 합니다. 게다가 LSI 는 테이블 생성 당시에 키를 정의해야 하고 이후에는 정의할 수 없습니다.
기본 키를 바꾸고 싶을 때 사용하는 두 번째 인덱스가 있죠. 바로 글로벌 보조 인덱스인 GSI 인데요. 이 경우는 기본 키를 바꿀 수 있습니다. 필요에 따라 정렬 키를 가질 수도 있고요. 게다가 테이블 생성 이후에 키를 정의해도 됩니다. DynamoDB 의 핵심은 기본 키와 메인 테이블의 정렬 키 조합과 인덱스 로만 쿼리할 수 있다는 겁니다. 단일 열과 행을 쿼리할 수 있는 RDS 와는 다르죠. DynamoDB 에선 어떤 쿼리인지 생각하고 해당 쿼리가 작동하도록 올바른 인덱스를 생성해야 합니다.
이게 바로 RDS인 전통적인 RDBMS와 DynamoDB 인 NoSQL 데이터베이스의 큰 차이점이죠.
DynamoDB 의 경우 쿼리할 게 있다면 해당 쿼리를 위한 인덱스가 미리 생성되었는지 확인해야 합니다. DynamoDB 는 솔루션 아키텍트 전문가보다는 개발자 시험을 위한 내용입니다. 세부 내용은 몰라도 되지만 LSI 와 GSI의 차이는 기억하세요. DynamoDB 에서 특정 속성을 쿼리 하고 싶을 땐 인덱스가 필요하다는 점도요.
- LSI : ‘Orders’ 라는 테이블이 있고, 기본 키가
UserID
(파티션 키)와OrderID
(정렬 키)입니다. 여기서 LSI 를 사용해UserID
는 동일하게 유지하면서OrderDate
를 새로운 정렬 키로 사용할 수 있습니다. 이를 통해 특정 사용자의 주문을 날짜별로 정렬할 수 있습니다. - GSI 예시 : 동일한 ‘Orders’ 테이블에서, GSI를 사용하여
ProductID
를 파티션 키로,OrderDate
를 정렬 키로 설정할 수 있습니다. 이렇게 하면 특정 상품에 대한 모든 주문을 날짜별로 검색할 수 있습니다.
DynamoDB — Important Features
DynamoDB 의 중요한 특징을 봅시다.
첫 번째는 TTL 이죠. 특정 시간이 지나면 행이 만료되게 하죠.
DynamoDB Stream 도 있습니다. DynamoDB 테이블의 변동에 실시간으로 대응할 수 있죠. 아주 흔하게 보는 아키텍처예요. 이 스트림을 흔히 Lambda 나 EC2 로 읽을 수 있습니다. 이 스트림에서는 24 시간 동안 데이터를 보존하죠.
자주 쓰이는 아키텍처를 하나 봅시다. 테이블에 CRUD 를 하는데 수정된 부분이 있으면 그러면 CRUD 는 DynamoDB Stream 에 저장되고 이를 Lambda 로 읽을 수 있습니다. 그다음 Lamda 를 Amazon ES 에 데이터를 저장하고 ElasticSearch를 사용할 수 있죠. 다운스트림을 원하면 Kinesis 에 저장 하고요. 혹은 Kinesis Analytics 애플리케이션에 사용할 수도 있고요. 아주 흔하게 볼 수 있는 솔루션 아키텍처 입니다
뿐만 아니라 글로벌 테이블도 있는데요. 모든 리전 에서 복제될 테이블임을 알 수 있죠. 복제 타입이 활성/활성 이므로 여러 리전 에서 가능합니다. DynamoDB Streams 부터 가동해야 하죠. 다른 리전의 데이터 접근 지연 시간을 낮추고 싶거나 재해 복구 전략을 만들고 싶을 때 유용하죠. 복구 시간 목표(RTO)가 매우 낮거든요.
예시를 볼게요. 테이블이 두 개 있죠. 하나는 us-east-1 이고 다른 하나는 ap-southeast-2 입니다. 우리가 us-east-1 에 한 작업은 ap-southeast-2 로 복제됩니다. ap-southeast-2 에 한 작업도 us-east-1 로 다시 복제됩니다.
Amazon Kinesis Data Streams for DynamoDB
DynamoDB Stream 을 이용해 Kinesis Data Streams 를 사용할 수 있습니다. 어떻게 가능 할까요? DynamoDB Stream 은 항목 레벨의 변화를 감지합니다.
해당 변동 사항을 Kinesis Data Streams에 직접 보낼 수 있습니다. 왜 그렇게 할까요? DynamoDB Stream 에선 데이터를 24시간 동안만 보존합니다. Kinesis Data Streams에서는 365일까지 더 길게 보존할 수 있죠.
또한 데이터를 분석하기에 적합한 Kinesis 관련 도구 세트도 사용할 수 있죠. 어떻게 작동할까요? 우선 DynamoDB 테이블이 Kinesis Data Streams로 변동 사항을 푸시하면 Kinesis Data Firehose를 사용해 데이터를 읽고 해당 데이터를 Amazon S3 Redshift, OpenSearch 등에 저장하죠.
Kinesis Data Analytics 로 데이터를 실시간으로 분석할 수도 있죠. 필터링, 집계, 변환 같은 실시간 컴퓨팅을 하고 Kinesis Data Streams나 Firehose 또는 Lambda 로 나중에 보낼 수 있습니다.
DynamoDB Solution Architecture Indexing objects in DynamoDB
이제 솔루션 아키텍처를 살펴봅시다.
알다시피 Amazon S3 는 객체를 찾기에 좋은 방법이 아니죠. 인덱싱 기능이 없으니까요. 그러므로 DynamoDB 에 메타데이터 정보를 저장해야 합니다. 작동 방식은 Amazon S3 버킷 에 객체를 추가하거나 업데이트하기 위해 쓰기를 수행해서 S3 이벤트로 Lambda 함수를 트리거하는 겁니다.
Lambda 함수는 Amazon S3 에 삽입 하려는 객체의 메타데이터를 취한 후 DynamoDB 테이블에 삽입하죠. 올바른 인덱스인 LSI 와 GSI 를 생성함으로써 객체 메타데이터를 찾는 API 를 만들 수 있다는 장점이 있죠.
이러한 아키텍처 덕분에 우리는 Amazon S3에 있는 모든 데이터를 날짜별로 가져오도록 요청하거나 특정 사용자 별로 사용한 저장 용량을 요청할 수 있습니다.
특정 속성을 가진 객체를 모두 가져올 수도 있고요. 이러한 객체를 S3 에만 넣으면 이런 쿼리에 대해 절대 응답받을 수 없지만 DynamoDB Table 에 넣으면 아주 간단해지죠.
DynamoDB — DAX
마지막으로 DynamoDB 의 캐싱을 살펴봅시다.
DynamoDB Accelerator인 DAX는 DynamoDB 를 위한 완벽한 캐시로서 애플리케이션에 다시 쓸 필요 없이 DAX 를 통해 DynamoDB로 모든 쓰기가 처리되죠. 캐싱된 읽기와 쿼리를 위한 지연 시간이 마이크로초 단위이니 매우 유용 하겠죠. 핫키 문제도 해결합니다.
DynamoDB 에서 특정 행이 인기가 많아서 여러 번 읽히면 스로틀링에 문제가 생길 텐데요. 이런 경우 DynamoDB 앞에 DAX 클러스터를 생성해서 해당 행을 캐싱함으로써 문제를 해결할 수 있습니다. DynamoDB 에 도달할 수 없게 하는 거죠.
데이터가 DAX 에서 캐싱 되는 시간인 TTL은 기본적으로 5분입니다. 그다음 DynamoDB에서 새로 고침 되는데요. 클러스터에서 노드를 10개까지 가질 수 있고 이 경우에는 Multi AZ 가 될 수 있고 AWS는 프로덕션을 위해 최소한 3개의 노드를 추천합니다.
게다가 안전하기 때문에 KMS, VPC IAM, CloudTrail 등으로 저장 데이터를 암호화할 수 있죠. DynamoDB 에서의 캐싱을 위한 기본 선택지지만 곧바로 올바른 전략에 대해 이야기해 볼게요.
핵심은 애플리케이션이 DynamoDB 테이블에 쓰고 그로부터 읽는 거죠. 중간 과정에는 DAX가 있을 거예요 우리에게는 보이지 않지만 DAX는 올바른 행을 캐싱해서 우리가 필요한 알맞은 수행을 처리하도록 할 거예요.
DynamoDB — DAX vs ElastiCache
아키텍처의 관점에서 봤을 때 DynamoDB 에 DAX 나 ElastiCache 중 무엇을 써야 할까요?
매우 간단한 문제입니다.
DynamoDB 에 직접 접근하려는 클라이언트가 있다면 DAX 를 사용하는 게 적절합니다. 캐시를 스캔하고 직접 캐싱과 인덱스화와 DAX 화 된 각 객체를 가질 테니까요 쿼리와 스캔 결과까지도 인덱스화할 거예요.
이번엔 클라이언트가 많은 양의 데이터를 검색한 후 고강도의 컴퓨팅을 수행한다고 가정합시다. 이 컴퓨팅이 10초나 30초와 같이 길게 걸린다고 칠게요. 오랜 시간이 소요되죠. 여러분은 해당 컴퓨팅 된 결과를 어딘가에 캐싱하고 싶을 거예요. ElastiCache가 될 수 있겠죠. 예시로 든 ElastiCache 의 경우 집계 결과를 저장하는 공간이 될 수 있습니다. 다른 클라이언트가 집계 결과를 가져오기 위해 DynamoDB 로부터 데이터를 가져오기 전 ElastiCache를 먼저 살펴볼 수 있도록요. 아키텍처 방식 중 하나입니다
ElastiCache을 DynamoDB 로 바꿔서 테이블 방식을 사용할 수도 있습니다. 그것도 가능하죠 DAX 와 ElastiCache 의 각기 다른 목적을 설명하고 싶었습니다. DAX는 개별 객체 캐시와 DynamoDB에서의 쿼리와 캐시 스캐닝을 위한 것이고 ElastiCache은 여러분이 넣고 싶은 결과를 위한 것이니 매우 무거운 컴퓨팅을 수행하고 ElastiCache에서 중앙 집중식으로 캐싱할 수 있습니다.
DAX 와 DynamoDB 로 돌아가는 것을 막고 다시 고도의 컴퓨팅을 할 수 있도록요.