[DynamoDB] DyanamoDB의 기본 개념
💡 코드가 보이지 않으시다면 드래그 혹은 오른쪽 아래 🌜 아이콘을 눌러 테마 색을 변경해주세요.
안녕하세요!
키크니 개발자 입니다. 🦒
다이나모 개념에 대해서 다시 복습하고자 정리하려고 합니다.
DynamoDB란?
- NoSQL(Not Only SQL) 데이터베이스
- 매우 빠른 쿼리 속도
- Auto-Scaling 기능 탑재
(중요한 특징입니다. 처음 데이터베이스를 만들면 크기가 정해지는데, Auto-Scaling의 경우는 데이터 크기 초과/축소 될 때 테이블이 알아서 늘어나고 줄어듭니다. 이는 비용에 대한 이점이 있습니다.) - key-value 데이터 모델 지원
- 테이블 생성시 스키마 생성 필요 없음
(실시간으로 들어오는 데이터를 보관하는데 탁월합니다.) - 모바일, 웹, loT데이터 사용시 추천
- SSD 스토리지 사용
(읽고 쓰는데 속도가 빠른 장점이 있습니다.)
DynamoDB의 구성
- 테이블(Table)
- 아이템, 항목(Items) : 튜플 혹은 행(row)과 개념이 비슷합니다.
- 특징, 속성(Attributes) : 컬럼 혹은 열(column)과 개념이 비슷합니다.
속성이 가질 수 있는 타입으로는 스칼라(숫자, 문자열, 이진수, boolean, null)와 중첩 된 속성(객체형식) 이 있으며 최대 32개 깊이까지 중첩이 가능합니다. - Key-Value : key - 데이터의 이름, value - 데이터 자신
예시 ) JSON, XML와 같이 Key-Value 형식입니다. - DynamoDB는 Standard 테이블과 DynamoDB Standard-IA(Infrequent Access) 테이블이 존재하는데 DynamoDB Standard 이 기본값이며, 일반적인 데이터를 처리하는데 사용됩니다. IA의 경우는 로그 및 과거 성과 데이터 등 자주 엑세스 하지 않는 데이터를 저장하는데 사용됩니다.
- DynamoDB 의 특이점은 보통 데이터베이스처럼 사용하는 포트가 따로 있는 것이 아닌 HTTP 프로토콜을 사용한다는 것 입니다.
HTTP 에서 사용하는 인증방식, HTTP Status에 결과 수신 등을 그대로 DynamoDB에서 사용합니다.
Primary Keys (PK: 기본 키)
NoSql 인 만큼 기본키 외에는 별도의 스키마가 존재하지 않습니다.
PK를 사용하여 데이터를 쿼리합니다.
테이블 생성시 고유 식별자를 가져야 하는데 이는 2종류로 나뉩니다.
1. Partition Key (파티션 키)
- DB에서 일반적으로 생각하는 단순 기본키(PK)
- 데이터를 나누고 분리시키는 키
- 고유 특징(Unique Attribute)
- 실제 데이터가 들어가는 위치를 결정해줌
(테이블을 만들 때 파티션 키를 설정하면 데이터가 들어오는데, 데이터가 어디로 저장될 지 파티션 키 내부에 들어있는 해시 함수를 돌리고 해시값을 반환받습니다. 이는 데이터가 저장 될 주소값이 됩니다. 데이터 조회할 시 이 주소값으로 데이터를 찾습니다.) - 파티션 키 사용시 동일한 두 개의 데이터가 같은 위치에 저장될 수 없음. (같은 위치에서는 파티션 키 중복 불가)
- 파티션 키 대신 해시 속성 혹은 해시키로도 불림
2. Composite Key (복합 키)
- Partition Key(파티션 키) + Sort Key(정렬 키 혹은 범위 속성)
예시 : 똑같은 고객이 다른 날짜에 다른 물건을 구매한 경우 -> 여기서 파티션 키는 고객 아이디, 정렬 키는 날짜를 의미합니다. - 같은 파티션 키의 데이터들은 같은 장소에 보관, 그 다음 정렬키에 의해 데이터가 정렬 됨
- 여러 항목이 중복 된 파티션 키 값을 가질 수 있지만, 동일한 파티션 내에서 다양한 정렬 키 값을 가져야 합니다.
위 그림을 예시로 들면 기본키를 AnimalType(파티션 키)와 Name(정렬 키)로 복합키를 구성 하는 것을 볼 수 있습니다.
복합키는 파티션 키로 파티션을 찾고, 정렬 키로 데이터 위치를 찾습니다.
(각 기본 키 속성은 스칼라여야 하며, 문자열, 숫자 또는 이진수가 포함 됩니다.)
데이터 접근 권한
DynamoDB의 데이터는 AWS IAM으로 관리할 수 있습니다.
- 이는 테이블 생성과 접근 권한을 부여할 수 있습니다.
- 특정 테이블만, 특정 데이터만 접근 가능케 해주는 특별한 IAM 역할이 존재합니다.
보조 인덱스(Secondary Index)
RDB의 경우 인덱스를 생성해놓으며 쿼리 옵티마이저가 인덱스를 사용하여 최적의 방식으로 데이터를 조회하지만,
DynamoDB의 경우 아예 인덱스를 사용하여 저장공간을 차별화 합니다.
이 때문에 인덱스 생성시 반드시 파티션 키를 정의해야 합니다.
테이블 당 하나 이상의 보조 인덱스(Secondary Index) 생성 가능합니다.
보조 인덱스를 생성하게 되면 데이터 access 방면에서 편리합니다.
만약 보조 인덱스를 사용하지 않은 필드를 기준으로 조회 쿼리를 사용하게 되면 Scan작업(RDB의 Full Scan을 의미)이 일어나게 되기에 좋지 않습니다.
예로 아래 Music 테이블은 복합 키를 사용하는 테이블로 Artist를 파티션 키, SongTitle을 정렬 키로 사용하는 테이블입니다.
Music 테이블로부터 오른쪽의 GenreAlbumTitile 라는 보조 인덱스를 생성하는데 생성되는 형식을 보면 Genre는 파티션 키이고, AlbumTitle은 정렬 키로 사용했고 원본 Music의 복합키를 가져왔습니다.
이제 Genre와 AlbumTitle 속성을 가지고도 Music 테이블 데이터를 쿼리할 수 있게 되었습니다.
AlbumTitle이 알파벳 H로 시작하는 모든 Country 앨범을 검색하는 조건을 지정할 수도 있습니다.
물론 보조 인덱스를 사용하면 별도의 저장공간에 데이터를 저장하기에 실제 쓰는 데이터 용량보다 높은 비용이 발생할 수 있습니다.
모든 보조 인덱스는 원본 테이블로부터 생성되는 이 원본 테이블을 기본테이블이라고 합니다.
인덱스의 생성방식을 토대로 2종류로 나누는데 아래와 같습니다.
1. Global Secondary Index(GSI)
- 테이블 생성 후에도 추가, 변경 삭제 가능
- 다른 파티션 키, 정렬 키 사용 가능
2. Local Secondary Index(LSI)
- 테이블 생성시에만 정의 가능
- 테이블 생성 후 변경, 삭제 불가능
- 기본테이블과 똑같은 파티션 키를 사용, 다른 정렬키 사용
- 테이블 생성 시 LSI 를 생성하려면 RangeKey를 설정해야 함
DynamoDB의 각 테이블에는 기본 할당량으로 GSI 20개, LSI 5개의 최대 할당량이 있으며 인덱스를 자동으로 유지 관리합니다.
기본테이블에 항목을 추가, 변경하면 DynamoDB는 테이블의 모든 인덱스에서 해당 항목을 추가, 업데이트 또는 삭제합니다.
아래는 PartiQL를 사용하여 인덱스와 함께 테이블을 조회하는 쿼리입니다.
# GSI 생성
CREATE INDEX GenreAndPriceIndex
ON Music (genre, price);
# 인덱스 사용 조회
SELECT *
FROM Music.GenreAndPriceIndex
WHERE Genre = 'Rock'
Query / Scan
DynamoDB에서 데이터를 읽어오는 2가지 방식
1. Query
- Primary Key를 사용하여 데이터 검색
- Query 사용 시 모든 데이터(컬럼) 반환
- ProjectionExpression 파라미터
(우리가 보고 싶은 컬럼만 볼 수 있도록 수정, 일종의 필터링 역할)
2. Scan
- 모든 데이터를 불러옴 (Primary Key 사용하지 않음)
- ProjectionExpression 파라미터
- 순차적 방법(Sequential)
- 테이블 크기가 상대적으로 크지 않고, 테이블에 Primary Key의 정의가 필요없을 경우 사용
3. Query vs Scan
- Query가 Scan 보다 훨씬 효율적임
- Scan 은 데이터의 크기가 일정치 않음 따라서 Query 사용을 추천
Dynamic Query
RDB 에서는 Dynamic Query 지원을 위해 QueryDSL 이나 Criteria 등을 사용하는데
DynamoDB 에서는 별도의 라이브러리가 없고 DynamoDBMapper 클래스를 사용하여 Query, Scan 을 진행합니다.
Query, Scan의 사용방법은 모두 테이블에서 데이터 컬렉션을 읽어오기 위한 것으로 동일하나,
일반적으로 Query 가 파티션 키를 사용하기 때문에 대부분 성능이 더 뛰어나며 문서 역시 Query 메서드 사용을 권장합니다.
간단한 조회 예시
public List<Customer> findAllByUmidAndContractCdAndPortNo(String group, GetCustomerRequestDto requestDto) {
Customer forHash = new Customer();
forHash.setGroup(group);
Map<String, Condition> queryFilter = generateFilter(requestDto);
DynamoDBQueryExpression expression = new DynamoDBQueryExpression() // (1)
.withHashKeyValues(forHash) // (2)
.withConsistentRead(false) // (3)
.withQueryFilter(queryFilter);
return dynamoDBMapper.query(Customer.class, expression);
}
(1) Query 조회를 할 경우에는 DynamoDBQueryExpression을 사용합니다.
(2) Customer 객체의 경우 group 문자열 필드를 GSI 로 설정하여 HashKeyValue 데이터로 사용하였습니다.
(3) GSI 를 사용하다 보니 일관적인 읽기 지원이 불가능하기 때문에 withConsistenRead(false)를 설정해주어야 합니다.
(만약 이를 설정해주지 않으면 아래와 같은 에러가 발생합니다.) 참고 URL
AmazonDynamoDBException: Consistent reads are not supported on global secondary indexes
(Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException
Scan 의 경우 아래처럼 진행해야 하는데 id 리스트를 기반으로 검색을 진행하려면 어쩔 수 없이 scan 요청을 해야 합니다.
public List<Customer> findAllByIdIn(List<String> customerIds, GetCustomerRequestDto requestDto) {
Map<String, Condition> scanFilter = generateFilter(requestDto);
List<AttributeValue> attList = customerIds.stream().map(id -> new AttributeValue(id)).collect(Collectors.toList());
scanFilter.put("id", new Condition()
.withComparisonOperator(ComparisonOperator.IN) // (1)
.withAttributeValueList(attList));
DynamoDBScanExpression expression = new DynamoDBScanExpression() // (2)
.withScanFilter(scanFilter);
return dynamoDBMapper.scan(Customer.class, expression);
}
(1) 타입에 따라 사용할 수 있는 ComparisonOperator 가 있습니다. 자세한 사항은 아래 공식 문서 확인해주세요.
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
(2) Scan 쿼리를 날릴 때에는 DynamoDBScanExpression 을 사용합니다.
DAX란?
DynamoDB Accelerator의 약자
클러스터 In-memory 캐시
In-memory는 데이터를 캐시에 보관하는 것, 필요한 데이터를 찾을 때 빠르게 찾을 수 있다.
10배 이상의 속도 향상
읽기 요청만 해당 (쓰기요청에서는 예외) 따라서 읽기 요청이 클 떄 사용
예시) 블랙프라이데이 쇼핑 웹사이트 운영 (수많은 읽기 요청 예상)
DAX의 원리
DAX 캐싱 시스템
- 직접 캐시에 데이터를 넣는 것이 아니라 DynamoDB 테이블에 데이터 삽입 & 업데이트 시 DAX에도 반영
- 읽기 요청에 맞는 데이터가 DAX에 들어왔을 경우 DAX에서 데이터 즉시 반환
DAX의 단점
- 쓰기 요청이 많은 어플리케이션에서는 부적절함
- 읽기 요청이 많지 않은 어플리케이션에서는 부적절함
- 아직 모든 지역에서 제공하지 않음
트랜잭션
DynamoDB 에서는 트랜잭션 기능을 제공하지만 안타깝게도 Spring Data DynamoDB 프로젝트에서 @Transaction 어노테이션은 작동하지 않는다.
Spring Data DynamoDB 라이브러리 또한 DynamoDBMapper 라는 내부 매퍼 클래스를 구현하여 작성한 라이브러리, DynamoDBMapper의 자세한 내용은 아래 url 참고해주세요
https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/DynamoDBMapper.Methods.html
위 url에 작성 된 데모코드와 같이 DynamoDBMapper 을 사용하면 트랜잭션 기능을 사용할 수 있기는 합니다.
TransactionLoadRequest 를 작성하고 아래와 같이 매퍼에 전달하면 됩니다.
loadedObjects = mapper.transactionLoad(transactionLoadRequest);
References
https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/Introduction.html
https://kouzie.github.io/aws/aws-dynamodb/#%EA%B8%B0%EB%B3%B8-%ED%82%A4