참고사이트 1 (DynamoDB API) : http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/HowItWorks.API.html
참고사이트 2 (Node.js & DynamoDB) : http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/gettingstartedguide/GettingStarted.NodeJs.html
참고사이트 3 (aws-sdk) : http://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/gettingstartedguide/GettingStarted.NodeJs.04.html
AWS-DynamoDB : 데이터 조회하기
환경
- AWS Lambda blueprint : Node.js 6.10 & microservice-http-endpoint
사용법
- aws-sdk 모듈 이용 :
- aws-sdk 모듈에 있는 DynamoDB객체(?)의 DocumentClient() 를 변수에 담아서 이 변수로 DynamoDB를 써먹는다.
- dynamodb-doc 모듈 이용 :
- dynamodb-doc 모듈에 있는 DynamoDB() 를 변수에 담아서 이 변수로 DynamoDB를 써먹는다.
query()
IndexName :
DynamoDB는 두개 이상의 컬럼을 선택하려면 Secondary-index를 추가해야 한다.
처음에 DynamoDB 테이블을 만들때 지정된 Primary Key 한 컬럼에 대해서는 이 IndexName 옵션이 필요없이 query가 가능하나,
추가로 더 뽑아내고 싶은 컬럼이 있다면 이 옵션을 써야 한다.
그러니까 RDBMS처럼 Select A, B from Table 같은 기능을 쓰고싶다면 IndexName 파라미터가 들어가줘야 한다는 거다.
테이블에 이미 많은 데이터가 있는 상태에서 Secondary-index를 추가하면 테이블 데이터를 모두 스캔하면서 인덱싱을 다시 하므로 시간이 한참 걸릴 수도 있으니 테이블 만들때부터 설계를 잘 해야한다 ㅜ
KeyConditionExpression & ExpressionAttributeValues :
user_id는 예제로 쓰는 테이블의 Primary Key이기 때문에 넣어봤다.
아래 예제의 :user_id 가 ExpressionAttributeValues에 의해 대입될 변수라고 생각하면 되겠다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 'use strict'; var AWS = require("aws-sdk"); var dynamo = new AWS.DynamoDB.DocumentClient(); exports.handler = (event, context, callback) => { var params = { TableName: "[DynamoDB 테이블명]", IndexName: "[DynamoDB테이블의 Secondary-index의 'Name']", // 생략 가능 KeyConditionExpression: "user_id = :user_id", ExpressionAttributeValues: { ":user_id": user_id } }; dynamo.query(params, (err, result) => { if (err) callback(null, err); callback(null, result); }); } | cs |
scan()
테이블 전체의 모든 항목을 읽고 테이블의 모든 데이터를 반환한다. 마치 SELECT * FROM TABLE 처럼..
더 알아보니 파라미터에서 ProjectionExpression과 FilterExpression 옵션으로 필터링을 할 수 있다.
그러나 필터는 테이블 전체를 스캔한 후에만 적용된다는 점에서 자칫하면 비용낭비가 될 수도 있다.
아래 코딩에 대한 결과는 사실상 RDBMS 쿼리로 따지자면 'SELECT * FROM geoseong' 이나 마찬가지 결과가 Items[] 에 출력되지만,
주석을 단 대로 'SELECT val, test, id FROM geoseong WHERE val between 1 and 100' 구문을 설정한 것과 마찬가지이다.
ProjectionExpression :
TableName의 테이블 데이터 중 이곳에 입력된 컬럼만 리턴되게 설정하는 곳이다.
= 출력 열 제어
FilterExpression :
TableName의 테이블 데이터 중 이곳에 입력된 조건에 걸린 값만 리턴되게 설정하는 곳이다.
= 출력 행 제어
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | var AWS = require("aws-sdk"); var docClient = new AWS.DynamoDB.DocumentClient(); exports.handler = (event, context, callback) => { var params = { TableName: "geoseong", // 2. from geoseong ProjectionExpression: "#val, test, id", // 1. select val, test, id FilterExpression: "#val between :start and :end", // 3. where val between :start and :end ExpressionAttributeNames: { "#val": "val", }, ExpressionAttributeValues: { ":start": 1, ":end": 100 } }; docClient.scan(params, onScan); function onScan(err, data) { if (err) callback(null, err); callback(null, data); } }; /** 결과 **/ { "Items": [ { "id": "hey", "val": 100, "test": "test" }, { "id": "geoseong", "val": 10, "test": "geo" } ], "Count": 2, "ScannedCount": 2 } | cs |
dynamodb-doc 모듈을 이용한 데이터 조회▲top
getItem()
Lambda의 blueprint 들 중 microservice-http-endpoint을 선택하면 나오는 템플릿에서는 이 모듈을 쓴다. 이 모듈을 권장하는것인가..
aws-sdk모듈에서 사용하는 param대로 한번 해봤는데, 안된다. 이유는 'Key' 속성이 없으면 안된단다..
KeyConditionExpression이라던가 ExpressionAttributeValues 속성은 존재하지 않는다.
그냥 테이블이름과 Key만 파라미터에 넣을 수 있는 것 같다.
마치 SELECT * FROM TABLE WHERE USER_ID = 'geoseong' 같다.
근데 getItem()은 너무 기본적인기능밖에 없는 것 같다.. 이런 기능을 왜 Lambda의 기본 템플릿으로 해놨지? ㅋ (내가 무식한게 죄다.. getItem말고도 여러가지 조회 방법이 있는 것을 알았다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 'use strict'; const doc = require('dynamodb-doc'); const dynamo = new doc.DynamoDB(); exports.handler = (event, context, callback) => { const params = { TableName: Table.USER_INFO, Key: { user_id: 'geoseong' } }; dynamo.getItem(params, (err, result) => { if(err) callback(null, err); callback(null, result); }); }; | cs |
getBatchItem()
다수의 조건으로 검색 할 수 있는 메소드이다.
여러번의 삽질 끝에 구현에 성공한 코드를 공유한다.
Node.js의 dynamodb라이브러리가 한가지가 있는 것이 아니고, 그에 따라 지원되고 되지않고 하는 것들이 머릿속을 너무 복잡하게 했다 ㅠㅠ
특히 내가 npmjs의 공식문서를 보고 따라도 해 보았는데 잘 되지가 않았다 ㅜ (https://www.npmjs.com/package/dynamodb-doc)
그러던 중에 'dynamodb-doc'의 소스를 보니 'aws-sdk' 를 활용하고 있다는 것을 알게 된 이후부터 getBatchItem()을 파면서부터 열심히 파고 나온 결과물이다 ㅎㅎ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 'use strict'; const doc = require('dynamodb-doc'); const dynamo = new doc.DynamoDB(); exports.handler = (event, context, callback) => { console.log('event!', JSON.stringify(event, null, 2)); const Table = { 'MemberInfo': 'MemberInfo', 'CategoryInfo': 'CategoryInfo' } var tableMember = Table.MemberInfo; var tableCategory = Table.CategoryInfo; var paramsR = { "RequestItems": {}, "ReturnConsumedCapacity": "NONE" }; paramsR.RequestItems[Table.MemberInfo] = { "ConsistentRead": true, "AttributesToGet": ["categories"], "Keys": [ { "id": event.id, "pw": event.pw } ], } paramsR.RequestItems[Table.CategoryInfo] = { "AttributesToGet": ["id", "categoryName", "pages"], "Keys": [ { "id": 'category-geoseong-1' }, { "id": 'category-geoseong-2' }, { "id": 'category-geoseong-22' }] } console.log('[[paramsR]]', JSON.stringify(paramsR, null, 2)); dynamo.batchGetItem(paramsR, function(err1, data1) { if (err1){ console.log('error shit...', err1); // an error occurred return callback(err1, null); }else{ console.log('batchGetItem data', JSON.stringify(data1, null, 2)); callback(null, data1) } }); }; | cs |
* dynamoDB의 테이블 구조
MemberInfo
1 2 3 4 5 6 7 8 9 | { "categories": [ "category-geoseong-22", "category-geoseong-1", "category-geoseong-2" ], "id": "geoseong", "pw": "1234" } | cs |
CategoryInfo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "categoryName": "ehlsid", "id": "category-geoseong-22", "pages": [] }, { "categoryName": "ayy", "id": "category-geoseong-1", "pages": [] }, { "categoryName": "왜그랬어", "id": "category-geoseong-2", "pages": [] } | cs |
* CloudWatch에서 확인한 콘솔로그와 결과
파라미터
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | [[paramsR]] { "RequestItems": { "MemberInfo": { "ConsistentRead": true, "AttributesToGet": [ "categories" ], "Keys": [ { "id": "geoseong", "pw": "1234" } ] }, "CategoryInfo": { "AttributesToGet": [ "id", "categoryName", "pages" ], "Keys": [ { "id": "category-geoseong-1" }, { "id": "category-geoseong-2" }, { "id": "category-geoseong-22" } ] } }, "ReturnConsumedCapacity": "NONE" } | cs |
결과
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | batchGetItem data { "Responses": { "MemberInfo": [ { "categories": [ "category-geoseong-22", "category-geoseong-1", "category-geoseong-2" ] } ], "CategoryInfo": [ { "id": "category-geoseong-22", "categoryName": "ehlsid", "pages": [] }, { "id": "category-geoseong-1", "categoryName": "ayy", "pages": [] }, { "id": "category-geoseong-2", "categoryName": "왜그랬어", "pages": [] } ] }, "UnprocessedKeys": {} } | cs |