완전관리형의 내구성이 뛰어난 다중 리전, 다중 마스터 데이터베이스로서, 인터넷 규모 애플리케이션을 위한 보안, 백업 및 복원, 인 메모리 캐싱 기능을 기본적으로 제공합니다.
다음은 기본 DynamoDB 구성 요소입니다.
테이블 – 다른 데이터베이스 시스템과 마찬가지로 DynamoDB는 데이터를 테이블에 저장합니다. 테이블은 데이터의 집합입니다. 예를 들어, 친구, 가족 또는 기타 관심 있는 사람에 대한 정보를 저장하는 데 사용할 수 있는 People이라는 예제 테이블을 살펴 봅니다. 또한 Cars 테이블에 사람들이 운전하는 차량에 대한 정보를 저장할 수도 있습니다.
항목 – 각 테이블에는 0개 이상의 항목이 있습니다. 항목은 모든 기타 항목 중에서 고유하게 식별할 수 있는 속성들의 집합입니다. People 테이블에서 각 항목은 한 사람을 나타냅니다. Cars 테이블의 경우 각 항목은 차량 한 대를 나타냅니다. DynamoDB의 항목은 여러 가지 면에서 다른 데이터베이스 시스템의 행, 레코드 또는 튜플과 유사합니다. DynamoDB에서는 테이블에 저장할 수 있는 항목의 수에 제한이 없습니다.
속성 – 각 항목은 하나 이상의 속성으로 구성됩니다. 속성은 기본적인 데이터 요소로서 더 이상 나뉠 필요가 없는 것입니다. 예를 들어 People 테이블의 항목에는 PersonID, LastName, FirstName 등의 속성이 있습니다. Department 테이블의 경우 항목에 DepartmentID, Name, Manager 등의 속성이 있을 수 있습니다. DynamoDB의 속성은 여러 가지 면에서 다른 데이터베이스 시스템의 필드 또는 열과 유사합니다.
예제 (People 테이블)
예제 (Music 테이블)
DynamoDB는 두 가지의 기본 키를 지원합니다.
파티션 키 - 파티션 키로 알려진 하나의 속성으로 구성되는 단순 기본 키.
파티션 키로만 구성되어 있는 테이블에서는 어떤 두 개의 테이블 항목도 동일한 파티션 키 값을 가질 수 없습니다.
다음 다이어그램은 여러 파티션에 걸쳐 데이터가 저장된 Pets라는 테이블을 보여줍니다. 테이블의 기본 키는 AnimalType(이 키 속성만 표시됨)입니다. DynamoDB는 해시 함수를 사용하여 새 항목을 저장할 위치를 결정합니다. 이 경우 문자열 Dog의 해시 값이 기준으로 사용됩니다. 항목은 정렬 순서대로 저장되지 않습니다. 각 항목의 위치는 파티션 키의 해시 값으로 결정됩니다.
파티션 키 및 정렬 키 - 복합 기본 키로 지칭되는 이 형식의 키는 두 개의 속성으로 구성됩니다. 첫 번째 속성은 파티션 키이고, 두 번째 속성은 정렬 키입니다.
복합 기본 키를 사용하면 보다 유연하게 데이터를 쿼리할 수 있습니다. 예를 들어, Artist 값만 제공하는 경우, DynamoDB는 해당 아티스트의 모든 노래를 검색합니다. Artist 값과 함께 SongTitle 값 범위를 입력하여 특정 아티스트의 노래 중 일부만 검색할 수도 있습니다.
Pets 테이블이 AnimalType(파티션 키)와 Name(정렬 키)으로 구성된 복합 기본 키를 가진다고 가정하겠습니다. 다음 다이어그램에서는 DynamoDB가 파티션 키 값 Dog와 정렬 키 값 Fido를 사용하여 항목을 기록합니다.
사전준비
Eclipse를 시작하고 Eclipse 메뉴에서 파일, 새로 만들기, 기타를 차례대로 선택합니다.
Select a wizard에서 AWS, AWS Java Project, Next를 차례대로 선택합니다.
Create an AWS Java project에서 다음을 수행합니다.
프로젝트 이름에 프로젝트 이름(DynamoDBSample)을 입력합니다.
Select Account의 목록에서 자격 증명 프로필을 선택합니다.
Amazon DynamoDB Sample의 체크박스를 선택합니다.
프로젝트를 생성하려면 Finish를 선택합니다.
프로그램을 컴파일하고 실행합니다.
Person 데이터 형식
{
"id": 1,
"firstName": "Kwanwoo",
"lastName": "Lee",
"age":40
}
People테이블을 생성합니다.
Eclipse 프로젝트 탐색기를 사용하여 DynamoDBLambdaJavaProject 프로젝트에서 PuttingPersonHandler.java를 열고, 다음 코드로 바꿉니다.
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class PuttingPersonHandler implements RequestHandler<Person, String> {
private DynamoDB dynamoDb;
private String TABLE_NAME = "People";
private String REGION = "ap-northeast-2";
@Override
public String handleRequest(Person input, Context context) {
this.initDynamoDbClient();
putData(input);
return "Saved Successfully!!";
}
private void initDynamoDbClient() {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withRegion(REGION).build();
this.dynamoDb = new DynamoDB(client);
}
private PutItemOutcome putData(Person person)
throws ConditionalCheckFailedException {
return this.dynamoDb.getTable(TABLE_NAME)
.putItem(
new PutItemSpec().withItem(new Item()
.withPrimaryKey("id",person.id)
.withString("firstName", person.firstName)
.withString("lastName", person.lastName)));
}
}
class Person {
public String firstName;
public String lastName;
public int id;
}
[Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.
새 Lambda 함수 생성을 선택하고 함수 이름(예: PuttingPersonFunction)을 입력합니다.
[Next]를 선택합니다.
함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.
Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.
Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.
Lambda 함수를 호출하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 AWS Lambda를 선택한 후 Run Function on AWS Lambda(AWS Lambda에서 함수 실행)를 선택합니다.
입력 상자에서 아래와 같은 JSON 문자열을 입력합니다.
{
"id": 1,
"firstName": "Kwawnoo",
"lastName": "Lee"
}
Invoke를 선택하여 Lambda 함수에 입력 데이터를 전송합니다.
결과를 확인하기 위해서는, DynamoDB 콘솔로 이동합니다.
디바이스로부터 수신된 정보는 무엇이고, 이를 어떻게 Lambda 함수에서 얻을 수 있는가?
Device gateway는 디바이스로부터 /update 주제와 관련된 페이로드 메시지로 다음의 정보를 수신합니다.
{"state":{"reported":{"temperature":"23.30","LED":"ON"}}}
AWS IoT는 디바이스 섀도우에 대한 변경을 수락할 경우, /update/accepted주제에 응답 메시지를 게시합니다. 이 메시지의 형식은 다음과 같습니다.
{
"state":{"reported":{"temperature":"23.30","LED":"ON"}}
"metadata": { ...}
"version": 2590,
"timestamp":1604467313
}
앞의 메시지를 수신하는 방법은 IoT rule 컴포넌트에 규칙을 만들고, 이 규칙을 통해서 얻은 데이터를 Lambda함수의 입력으로 전달되게 합니다. 구체적인 절차는 다음과 같습니다.
Lambda 함수에 입력되는 정보는 어떤 형식인가?
IoT 서비스 아키텍처
DeviceData테이블을 생성합니다.
Eclipse 프로젝트 탐색기를 사용하여 RecordingDeviceDataLambdaJavaProject 프로젝트에서 RecordingDeviceInfoHandler.java를 열고, package 선언 다음의 코드를 아래 코드로 바꿉니다.
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class RecordingDeviceInfoHandler implements RequestHandler<Thing, String> {
private DynamoDB dynamoDb;
private String DYNAMODB_TABLE_NAME = "DeviceData";
@Override
public String handleRequest(Thing input, Context context) {
this.initDynamoDbClient();
persistData(input);
return "Success in storing to DB!";
}
private PutItemOutcome persistData(Thing thing) throws ConditionalCheckFailedException {
// Epoch Conversion Code: https://www.epochconverter.com/
SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
String timeString = sdf.format(new java.util.Date (thing.timestamp*1000));
return this.dynamoDb.getTable(DYNAMODB_TABLE_NAME)
.putItem(new PutItemSpec().withItem(new Item().withPrimaryKey("time", thing.timestamp)
.withString("temperature", thing.state.reported.temperature)
.withString("LED", thing.state.reported.LED)
.withString("timestamp",timeString)));
}
private void initDynamoDbClient() {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion("ap-northeast-2").build();
this.dynamoDb = new DynamoDB(client);
}
}
class Thing {
public State state = new State();
public long timestamp;
public class State {
public Tag reported = new Tag();
public Tag desired = new Tag();
public class Tag {
public String temperature;
public String LED;
}
}
}
Lambda에 함수를 업로드하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 [AWS Lambda]와 [Upload function to AWS Lambda]를 차례대로 선택합니다.
[Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.
새 Lambda 함수 생성을 선택하고 함수 이름(예: RecordingDeviceInfoFunction)을 입력합니다.
[Next]를 선택합니다.
함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.
Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.
Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.
작성된 Lambda함수가 정상적으로 동작하는 지를 테스트해 보기 위해서 다음 절차를 수행합니다.
입력 상자에서 다음과 같은 JSON 문자열을 입력합니다.
{
"state": {
"reported": {
"temperature": "23.0", "LED":"OFF"
}
}
}
Invoke 버튼을 선택하여 Console 창에 다음과 같은 메시지가 출력되는 지 확인합니다.
...
================== FUNCTION OUTPUT ===================
"Success in storing to DB!"
================== FUNCTION LOG OUTPUT ===============
...
이번 실습에서는 3절의 실습 내용을 다음 관점에서 개선해 본다.
데이터를 저장할 테이블을 파티션 키와 정렬 키로 구성하게 함으로써, 데이블에 저장된 항목을 정렬 키를 기준으로 정렬 시킬 수 있게 한다.
디바이스로부터 수신된 모든 정보를 테이블에 저장하지 않고, 이전에 수신된 정보와 차이가 나는 경우에만 테이블에 저장하도록 수정한다.
이전 값과 차이가 나는 정보를 어떻게 알아낼 수 있는가?
새로운 규칙을 만들고, 규칙 쿼리를 '$aws/things/MyMKRWiFi1010/shadow/update/documents' 로 정의하여, 이 때 수신된 메시지로부터 DB에 저장할 지 말지를 결정하는 Lambda 함수를 연결하면 됩니다.
previous 노드와 current 노드의 차이를 비교하여 차이가 있는 경우에만 DB에 데이터를 저장하면 됩니다.
/update/documents 토픽에 대한 응답 메시지 예시
{
"previous": {
"state": {
"reported": {
"temperature": "24.50",
"LED": "OFF"
}
},
"metadata": {
"reported": {
"temperature": {
"timestamp": 1575167548
},
"LED": {
"timestamp": 1575167548
}
}
},
"version": 4447
},
"current": {
"state": {
"reported": {
"temperature": "24.40",
"LED": "OFF"
}
},
"metadata": {
"reported": {
"temperature": {
"timestamp": 1575178081
},
"LED": {
"timestamp": 1575178081
}
}
},
"version": 4448
},
"timestamp": 1575178081
}
DB 테이블에 정보를 저장하는 규칙(LoggingRule)을 독립적인 규칙으로 분리하여, 해당 규칙을 필요에 따라서 활성화 혹은 비활성화 시킬 수 있도록 수정한다.
Logging테이블을 생성합니다.
Eclipse 프로젝트 탐색기를 사용하여 RecordingDeviceDataLambdaJavaProject2 프로젝트에서 RecordingDeviceInfoHandler2.java를 열고, 다음 코드로 바꿉니다.
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.spec.PutItemSpec;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class RecordingDeviceInfoHandler2 implements RequestHandler<Document, String> {
private DynamoDB dynamoDb;
private String DYNAMODB_TABLE_NAME = "Logging";
@Override
public String handleRequest(Document input, Context context) {
this.initDynamoDbClient();
context.getLogger().log("Input: " + input);
//return null;
return persistData(input);
}
private String persistData(Document document) throws ConditionalCheckFailedException {
// Epoch Conversion Code: https://www.epochconverter.com/
SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
String timeString = sdf.format(new java.util.Date (document.timestamp*1000));
// temperature와 LED 값이 이전상태와 동일한 경우 테이블에 저장하지 않고 종료
if (document.current.state.reported.temperature.equals(document.previous.state.reported.temperature) &&
document.current.state.reported.LED.equals(document.previous.state.reported.LED)) {
return null;
}
return this.dynamoDb.getTable(DYNAMODB_TABLE_NAME)
.putItem(new PutItemSpec().withItem(new Item().withPrimaryKey("deviceId", document.device)
.withLong("time", document.timestamp)
.withString("temperature", document.current.state.reported.temperature)
.withString("LED", document.current.state.reported.LED)
.withString("timestamp",timeString)))
.toString();
}
private void initDynamoDbClient() {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().withRegion("ap-northeast-2").build();
this.dynamoDb = new DynamoDB(client);
}
}
/**
* AWS IoT은(는) 섀도우 업데이트가 성공적으로 완료될 때마다 /update/documents 주제에 다음 상태문서를 게시합니다
* JSON 형식의 상태문서는 2개의 기본 노드를 포함합니다. previous 및 current.
* previous 노드에는 업데이트가 수행되기 전의 전체 섀도우 문서의 내용이 포함되고,
* current에는 업데이트가 성공적으로 적용된 후의 전체 섀도우 문서가 포함됩니다.
* 섀도우가 처음 업데이트(생성)되면 previous 노드에는 null이 포함됩니다.
*
* timestamp는 상태문서가 생성된 시간 정보이고,
* device는 상태문서에 포함된 값은 아니고, Iot규칙을 통해서 Lambda함수로 전달된 값이다.
* 이 값을 해당 규칙과 관련된 사물이름을 나타낸다.
*/
class Document {
public Thing previous;
public Thing current;
public long timestamp;
public String device; // AWS IoT에 등록된 사물 이름
}
class Thing {
public State state = new State();
public long timestamp;
public String clientToken;
public class State {
public Tag reported = new Tag();
public Tag desired = new Tag();
public class Tag {
public String temperature;
public String LED;
}
}
}
Lambda에 함수를 업로드하려면, Eclipse 코드 창에서 마우스 오른쪽 버튼을 클릭하고 [AWS Lambda]와 [Upload function to AWS Lambda]를 차례대로 선택합니다.
[Select Target Lambda Function] 페이지에서 사용할 AWS 리전을 선택합니다. 이 리전은 Amazon S3 버킷에 대해 선택한 리전과 동일해야 합니다.
새 Lambda 함수 생성을 선택하고 함수 이름(예: RecordingDeviceInfoFunction2)을 입력합니다.
[Next]를 선택합니다.
함수 구성 페이지에서 대상 Lambda 함수에 대한 설명을 입력하고 함수에서 사용할 IAM 역할 선택합니다.
Lambda 함수에 대해 새로운 Amazon S3 버킷을 생성하고 싶은 경우에는 함수 구성 페이지로 이동하여 함수 코드에 대한 S3 버킷 섹션에서 생성을 선택합니다. 버킷 생성 대화 상자에 버킷 이름을 입력합니다.
Finish를 선택하여 Lambda 함수를 AWS에 업로드합니다.
규칙 쿼리 설명문에 다음을 입력합니다.
SELECT *, 'MyMKRWiFi1010' as device FROM '$aws/things/MyMKRWiFi1010/shadow/update/documents'
작업 추가를 선택합니다.
작업 선택 아래에서 메시지 데이터를 전달하는 Lambda 함수 호출 (Send a message to a Lambda function)을 선택한 다음 작업 구성을 선택합니다.
선택을 클릭하고, Lambda 함수(RecordingDeviceInfoFunction2)를 선택합니다.
작업 추가를 선택합니다.
[규칙생성] 페이지에서 규칙 생성 버튼을 클릭한다.
LoggingRule 규칙이 목록에 표시되는 것을 확인한다.