
안녕하세요. 일루넥스 개발팀 박두현입니다.
이번 글의 내용은 AWS DynamoDB에 관한 내용입니다.
일루넥스에서 서비스하는 Gitbal for StartUp에서는 VC들이 진행하는 라운드에
스타트업들이 지원할 때, 구글 설문지와 같은 동적 폼을 제출 받고 있습니다.
이러한 데이터들은 형식이 자유롭고, 폼 항목또한 여러 종류가 있기 때문에
지원한 결과 자체를 하나의 JSON 문서로 NoSQL에 저장하고 있습니다.
이번에는 NoSQL을 AWS에서 서비스하는 DynamoDB를 사용해서
간단한 소개와 사용법을 정리해보았습니다.

Amazon DynamoDB
- Amazon DynamoDB는 종합 관리형 NoSQL 데이터베이스 서비스
- 규모에 관계없이 데이터를 저장 및 검색하고, 어떠한 요청 트래픽이라도 처리할 수 있는
데이터베이스 테이블을 생성할 수 있다. - 성능 저하 없이 테이블의 처리 능력을 자동으로 확장 및 축소(Auto Scaling)할 수 있다.
- 온디맨드 백업을 지원하여 최근 35일 내에 특정 시점으로 테이블을 복원할 수 있다.
DynamoDB의 구성요소
테이블/항목/속성, 기본 키, 보조 인덱스, 스트림
1. 테이블, 항목, 속성
테이블 : 다른 데이터 베이스들과 마찬가지로 테이블은 데이터의 집합이다.
(DynamoDB에서 테이블은 항목들의 집합)항목 : 속성들의 집합. 각 테이블에는 0개 이상의 항목이 들어간다.
속성 : 최소한의 데이터 요소. 기존 데이터베이스의 필드 또는 컬럼과 같다.
ex) People 테이블의 하나의 항목과 속성들
{
"ID" : 103, // 기본키
"LastName" : "Stepen",
"FirstNAme" : "Howard",
"Address" : {
"Street" : "123 Main",
"City: "London",
"PostalCode" : "ER3 5K8"
},
"FavoriteColor" : "Blue",
}
- 각 항목에는 기본키가 있다. 여기서는 ID가 기본키 이다.
- 기본키를 제외하면 People테이블에는 별도의 스키마가 없다. 이는 항목 내부의 속성이나
데이터 형식을 정할 필요가 없음을 의미하고 속성들은 얼마든지 늘어날 수 있다. - 대부분의 속성은 스칼라로서, 하나의 값만 가질 수 있다.
- 일부 항목은 내포 속성을 가질 수 있다.(Address)
2. 기본 키
테이블에는 항상 기본키가 존재해야 한다. 각 기본키는 고유하며 중복된 항목이 존재할 수 없다.
DynamoDB에는 두 가지 기본키가 있다.
파티션 키 : 하나의 속성으로 구성되는 단순 기본 키
파티션 키 및 정렬 키 : 복합 기본키로 두 개의 속성으로 구성된다.
첫 번째 속성은 파티션 키이고 두 번째 속성이 정렬 키가 된다.
두 개의 항목이 동일한 파티션 키를 가질 수 있으나 정렬키 값은 달라야 한다.
3. 보조 인덱스
테이블에서 하나 이상의 보조 인덱스를 생성할 수 있다.
기본키에 대한 쿼리 및 대체키를 사용하여 데이터에 대한 쿼리를 실행할 수 있다.
보조 인덱스를 생성한 후에는 인덱스로 데이터를 읽을 수 있다.
DynamoDB에는 두 가지 종류의 인덱스가 있다.
- Global Secondary Index : 파티션 키 및 정렬 키가 테이블의 파티션 키 및 정렬 키와 다를 수 있는 인덱스
로컬 보조 인덱스 : 테이블의 파티션 키와 동일하지만 정렬 키는 다른 인덱스
ex) Music 테이블과 GenreAlbumTitle이라는 새 인덱스 (파티션 키 : Genre, 정렬 키 : AlbumTitle)
-> Music 테이블에서 Artist(파티션 키) 또는 ArtistArtist와SongTitle(파티션 키 및 정렬 키)을 기준으로 데이터 항목에 대한 쿼리가 가능하다.
만약, Genre 및 AlbumTitle로도 데이터를 쿼리 하고 싶다면?
Genre 및 AlbumTitle에 대해 인덱스를 생성한 후 인덱스를 쿼리 하면 된다.
{
"Aritist" : "No One You Know", // Music Table 파티션 키
"SongTitle" : "My Dog Spot", // Music Table 정렬 키
"AlbumTitle" : "Hey Now",
"Price" : "1.98,
"Genre" : "Country"
}
{
"Genre" : "Country", // 새로운 인덱스의 파티션 키
"AlbumTitle" : "Hey Now", // 새로운 인덱스의 정렬 키
"Artist" : "No One You Know",
"SongTitle" : My Dog Spot"
},
모든 인덱스는 테이블에 속해있는데 이를 기본 테이블이라고 한다. (여기서는 Music Table)
기본 테이블의 항목을 추가, 업데이트, 삭제하면 그 테이블에 속하는 인덱스의 해당 항목을 자동으로 추가, 업데이트, 삭제한다.
4. 스트림
스트림은 DynamoDB 테이블의 데이터 수정 이벤트를 선택하는 선택적 기능이다.
각 이벤트는 스트림 레코드에 의해 나타나고, 스트림을 설정하면 다음 이벤트마다 스트림 레코드를 기록한다.
- 항목이 추가된 경우 : 스트림이 해당 속성을 모두 포함하여 전체 항목의 이미지 캡처
- 항목이 업데이트된 경우 : 항목에서 수정된 속성의 사전, 사후 이미지 캡처
- 항목이 삭제된 경우 : 항목이 삭제되기 전 전체 항목 이미지 캡처
ex) 사용 케이스 예시
DynamoDB와 AWS Lambda를 사용하여 관심 있는 이벤트가 스트림에 표시될 때 코드를 실행할 수 있다.
-> 테이블에 항목이 추가되었을 때 람다 함수에서 특정 함수를 실행한다.
Amazon DynamoDB CRUD API
1. 데이터 생성
- PutItem – 테이블에 단일 항목을 쓴다. 기본 키 속성은 꼭 지정해야 하지만 다른 속성은 지정하지 않아도 된다.
- BatchWriteItem – 테이블에 최대 25개의 항목을 쓴다. 여러 항목을 삭제하는 경우에 BatchWriteItem을 사용할 수도 있다.
2. 데이터 읽기
- GetItem – 테이블에서 단일 항목을 가져온다. 항목의 기본 키를 지정해야 하고. 전체 항목 또는 속성 일부만 가져올 수 있다.
- BatchGetItem – 하나 이상의 테이블에서 최대 100개의 항목을 가져온다.
- Query – 특정 파티션 키가 있는 모든 항목을 가져온다. 파티션 키 값을 지정해야 한다.전체 항목 또는 속성 일부만 가져올 수 있다.
경우에 따라 정렬 키 값에 조건을 적용하여 파티션 키가 동일한 데이터의 하위 집합만 검색할 수도 있다. - Scan – 지정한 테이블 또는 인덱스의 모든 항목을 가져온다.전체 항목 또는 속성 일부만 가져올 수 있다. 필터링 조건을 적용하여 필요한 값만 반환하고 나머지는 버릴 수 있다.
3. 데이터 업데이트
- UpdateItem – 항목에서 하나 이상의 속성을 수정한다. 수정하려는 항목의 기본 키를 지정해야 한다.새로운 속성을 추가할 수 있으며 기존 속성을 수정하거나 제거할 수 있다.조건부 업데이트를 수행하여 조건을 만족할 때만 업데이트가 수행되도록 할 수도 있다.
4. 데이터 삭제
- DeleteItem – 테이블에서 단일 항목을 삭제합니다. 삭제하려는 항목의 기본 키를 지정해야 합니다.
- BatchWriteItem – 하나 이상의 테이블에서 최대 25개의 항목을 삭제한다.
BatchWriteItem을 사용하여 하나 이상의 테이블에 여러 아이템을 추가할 수도 있다.
DynamoDB Java SDK 예제
1. 테이블 생성
다음과 같은 기본키를 가지는 Movies라는 테이블을 생성한다.
year : 파티션 키
title : 정렬 키
– createTable 호출에서 테이블 이름, 기본 키 속성 및 데이터 유형을 지정한다.
public class MoviesCreateTable {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
String tableName = "Movies";
try {
Table table = dynamoDB.createTable(tableName, // 테이블 생성
Arrays.asList(new KeySchemaElement("year", KeyType.HASH), // Partition
// key
new KeySchemaElement("title", KeyType.RANGE)), // Sort key
Arrays.asList(new AttributeDefinition("year", ScalarAttributeType.N),
new AttributeDefinition("title", ScalarAttributeType.S)),
new ProvisionedThroughput(10L, 10L));
table.waitForActive();
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
2. 테이블에 샘플 데이터 저장
year 및 title을 Movies 테이블을 위한 기본 키 속성 값으로 사용한다.
info의 나머지 값들은 info라는 단일 속성에 저장한다.
영화 데이터 JSON
{
"year" : 2013, // 파티션 키
"title" : "Turn It Down, Or Else!", // 정렬 키
"info" : { // 나머지 데이터
"directors" : [
"Alice Smith",
"Bob Jones"
],
"release_date" : "2013-01-18T00:00:00Z",
"rating" : 6.2,
"genres" : [
"Comedy",
"Drama"
],
"image_url" : "http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg",
"plot" : "A rock band plays their music at high volumes, annoying the neighbors.",
"rank" : 11,
"running_time_secs" : 5215,
"actors" : [
"David Matthewman",
"Ann Thomas",
"Jonathan G. Neff"
]
}
}
3. 데이터 읽기
getItem 메서드를 사용하여 Movies 테이블에서 항목을 읽어올 수 있다.
기본 키 값을 지정해야 Movies과 year을 알고 있을 때 title에서 항목을 읽어올 수 있다.
public class MoviesItemOps02 {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Movies");
int year = 2015;
String title = "The Big New Movie";
GetItemSpec spec = new GetItemSpec().withPrimaryKey("year", year, "title", title);
try {
Item outcome = table.getItem(spec); // 데이터 읽기
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
4. 데이터 업데이트
updateItem 메서드를 사용해 기존 항목을 수정할 수 있다.
기존 속성의 값을 업데이트하거나 새로운 속성을 추가하거나 속성을 제거할 수 있다.
다음에서는 다음과 같이 업데이트를 수행한다.
기존 속성(rating, plot)의 값 변경
기존 info 맵에 새로운 목록 속성(actors) 추가
public class MoviesItemOps02 {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Movies");
int year = 2015;
String title = "The Big New Movie";
GetItemSpec spec = new GetItemSpec().withPrimaryKey("year", year, "title", title);
try {
Item outcome = table.getItem(spec); // 데이터 읽기
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
5. 데이터 쿼리
다음에서는 다음과 같은 쿼리를 수행한다.
1985year에 개봉한 모든 영화를 조회
1992year에 개봉한 영화 중에 title이 “A”부터 “L”까지의 알파벳으로 시작하는 영화를 모두 조회
쿼리 파라미터를 설명하는 querySpec 객체를 생성한 후 그 객체를 query 메서드로 전달한다.
public class MoviesQuery {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Movies");
HashMap<String, String> nameMap = new HashMap<String, String>();
nameMap.put("#yr", "year");
HashMap<String, Object> valueMap = new HashMap<String, Object>();
valueMap.put(":yyyy", 1985);
QuerySpec querySpec = new QuerySpec().withKeyConditionExpression("#yr = :yyyy").withNameMap(nameMap)
.withValueMap(valueMap); // 쿼리 조건 추가
ItemCollection<QueryOutcome> items = null;
Iterator<Item> iterator = null;
Item item = null;
try {
items = table.query(querySpec); // 데이터 쿼리
iterator = items.iterator();
while (iterator.hasNext()) {
item = iterator.next();
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
valueMap.put(":yyyy", 1992);
valueMap.put(":letter1", "A");
valueMap.put(":letter2", "L");
querySpec.withProjectionExpression("#yr, title, info.genres, info.actors[0]")
.withKeyConditionExpression("#yr = :yyyy and title between :letter1 and :letter2").withNameMap(nameMap)
.withValueMap(valueMap); // 쿼리 조건 추가
try {
items = table.query(querySpec); // 데이터 쿼리
iterator = items.iterator();
while (iterator.hasNext()) {
item = iterator.next();
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
6. 스캔
scan 메서드는 테이블 전체의 모든 항목을 읽고 테이블의 모든 데이터를 반환한다.
선택 사항인 filter_expression을 제공할 수 있으며 그 결과 기준과 일치하는 항목만 반환된다.
★ 그러나 필터는 테이블 전체를 스캔한 후에만 적용된다.
다음에서는 항목이 약 5,000개인 Movies 테이블 전체를 스캔한다.
스캔은 선택 사항인 필터를 지정하여 1950년대 이후의 영화(약 100개 항목)만 가져오고 그 외의 것들은 모두 무시한다.
public class MoviesScan {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Movies");
ScanSpec scanSpec = new ScanSpec().withProjectionExpression("#yr, title, info.rating")
.withFilterExpression("#yr between :start_yr and :end_yr").withNameMap(new NameMap().with("#yr", "year"))
.withValueMap(new ValueMap().withNumber(":start_yr", 1950).withNumber(":end_yr", 1959));
// 스캔 조건
try {
ItemCollection<ScanOutcome> items = table.scan(scanSpec); // 스캔
Iterator<Item> iter = items.iterator();
while (iter.hasNext()) {
Item item = iter.next();
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
7. 테이블 삭제
public class MoviesDeleteTable {
public static void main(String[] args) throws Exception {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8000", "us-west-2"))
.build();
DynamoDB dynamoDB = new DynamoDB(client);
Table table = dynamoDB.getTable("Movies");
try {
table.delete(); // 테이블 삭제
table.waitForDelete();
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
마치며
여기까지 AWS DynamoDB에 관한 내용이었습니다.
읽어주셔서 감사합니다.