DynamoDB와 Rest API with Node.js
Explanation of DynamoDB and development of a REST API using DynamoDB Client in Node.js
Aug 30, 2023
DynamoDB 테이블 구조1. 기본구조2. Key3. 처리량 과 파티션4. 사용 방법싱글테이블을 위한 키 디자인 Tenet1. Use case 정의2. 액세스 패턴을 식별3. 데이터 라이프 사이클 / Primary keys 식별3가지 공통점실습사전지식간단한 테이블 생성 과정 DynamoDB Client만날 수 있는 ERROR LIST
DynamoDB 테이블 구조
1. 기본구조
- 기본 구조는 테이블(Table) 단위이다.
- RDMBS에서 row라고 부르는
item
을 하나의 테이블에 무한히 저장한다.
- 하나의 아이탬은 여러개의
Attribute
를 가지는데, 이는 RDBMS의 column과 같은 역할을 한다.
- 하나의 아이탬은 최대 400kb를 가진다.
- dynamoDB는 하나의 아이탬(row)에서 한 글자만 변경되도 전체 아이탬(row)를 변경시키기 때문에 아이탬 크기는 최대한 작게 가져가면서, 여러 아이탬으로 개수를 많이 가져가는 것이 좋다.
⇒ Attributes 수를 줄이면서 데이터를 늘리는 것이 더 유리하다.
2. Key
- 각 테이블은 필수 요소인 파티션 키(
Partition Key
)를 가진다. RDBMS의 Primary Key와 같다. - PK는 Equal(
=
)로서 작용해야 한다.
- 각 테이블은 서브 요소인 정렬 키(
Sort Key
)를 가질 수 있다. - sort key는 1:N의 관계를 모델링 할 수 있고 오름차순 내림차순으로 정렬할 수 있다.
- sort key는 등호(
>,<
) 로서 작용해야 한다.
- 각 키를 통해 무엇으로 시작하는지 beginsWith / 무엇을 포함하는지 between 등의 조건으로 빠르게 검색할 수 있다.
- DynamoDB는 두 가지 Secondary Index를 제공한다.
- Global Secondary Index (GSI)
- 테이블의 PK 키 외에 다른 검색조건이 필요한 경우 언제든 추가 / 삭제가 자유롭다.
- 베이스 테이블의 Attributes 중 원하는 값을 지정하면 된다.
- Attributes 중 일부만 지정할 수도 있고, 전체를 지정할 수도 있다.
- GSI는 최대 25개까지 만들 수 있다.
- GSI의 Eventual Consistency가 각각 WCU와 RCU를 가지고 있어 사용비용 상승과 직결되 있기 때문에 유연한 사용이 필요하다.
- Local Secondary Index (LSI)
- 베이스 테이블에서 다른 sort key를 사용하고 싶을 때 생성할 수 있다.
- LSI는 테이블 생성시점에만 생성가능하고, 도중 삭제 불가능하기 때문에 쿼리의 유연성이 떨어져 일반적으로 사용이 권장되지 않는다.
- LSI는 생성 도중 최대 5개까지 생성 가능하다.
- Strong Consistency가 필요하다면 사용을 고려해 볼 수 있다.
- pk와 sort키 조합을 통해 원하는 아이탬을 최대한 빨리 찾아 return할 수 있는 것이 장점이다.
- key 디자인을 잘못 한다면 특정 파티션으로 데이터 쏠림이나 핫블럭이 유발될 수 있기 때문에 키 디자인을 잘 해야한다.
- DynamoDB에는 키가 될 값에 대하여 #을 사용하여 prefix나 sufix를 사용
- DynamoDB는 HTTP 통신을 합니다. 다른 Database가
TCP connection
기반인 것에 비해Connectionless
합니다.
- AWS Lambda와 DynamoDB가 아주 적합한
Severless
선택지이다.
- NoSQL의 탄생 목적이, 트래픽이 아주 많아져 연산의 속도를 극대화해야 하는 상황에서 탄생했기 때문에 scale in/out 능력이 아주 중요해졌다.
Partition Key
와Sort
키를 제외한 테이블의Attribute
들은 미리정의될 필요가 없다.
PK키와 Sort키를 제외한 키는 Schema가 미리 생성되어, 스키마대로 움직여야 하는 RDBMS와는 달리, 임의로 정의될 수 있다.
3. 처리량 과 파티션
파티션
- DynamoDB는 테이블 아래에는 물리적인 파티션 단위로 작업이 진행되고, 각 파티션 관리는 db(AWS)에서 알아서 관리하게되는 완전관리형 서버리스 DB 서비스다.
- 테이블이 새롭게 생성된다면 이전과 마찬가지로 여러 물리서버에 파티션단위로 분리되어 저장된다.
- 각 파티션은 초당 처리 최대용량이 존재하는데, 개별 파티션의 처리량은 변하지 않기 때문에 키 디자인을 통해 여러 파티션이 골고루 사용될 수 있도록 / 더 많은 파티션 단위로 분리될 수 있도록 키 설계를 하는 것이 매우 중요하다.
처리량
- 필요한 처리량이 있다면 테이블에 필요한 만큼만 설정할 수 있다.
- DynamoDB에서는 Write와 Read에 대한 처리량 단위로 Capacity Unit을 사용한다.
- 1WCU는 1초에 1kb 데이터를 write할 수 있다.
- 1RCU는 초당 4kb 데이터를 read할 수 있다.
- 각 테이블은 읽기 쓰기 처리량을 구분해서 정할 수 있어 초당 1만 RCU 혹은 3천 WCU 등으로 값을 지정해서 사용할 수 있고, 사용한 만큼만 과금된다.
4. 사용 방법
REST API 에서…
- GetItem
- pk의 정확한 값을 지정하고, 정확히 0개 또는 1개의 아이탬을 반환한다.
- QueryCommand
- pk의 정확한 값을 지정하고, 일치하는 아이탬(여러개 가능)을 반환한다.
- 선택적으로 non-key Attributes에 필터링 조건을 추가할 수 있다.
- key 조건과 일치하는 아이템의 크기에 따라 RCU를 소비하여 단일 결과를 반환한다.
- Scan
- 키를 지정하지 않는다. 키가 아닌 Attributes에 대한 필터 조건을 지정한다.
- 필터 표현식과 일치하는 테이블 모든 아이템을 반환한다.
- 테이블의 모든 아이탬을 읽기 위해 RCU를 소비, 주의깊게 사용을 생각해야 한다.
- 일반적으로 데이터를 검색할 때는 GetItem과 QueryCommand가 권장된다.
- 응답메세지가 1MB 가 넘어가는 경우 LastEvaluatedKey를 이용하여 페이징 처리 해야한다.
- 이 외에도 batch와 같은 기능을 통해, 최대 16MB 또는 100개의 아이탬을 한 번에 읽을 수 있다.
싱글테이블을 위한 키 디자인 Tenet
일반적으로 RDBMS처럼 관계를 짓지말고, 빠른 조회를 위해 테이블간 키 디자인을 잘 해야한다.
1. Use case 정의
- 첫 번째로 Use case가 DynamoDB와 잘 맞는지 고민해야 한다.
- 무한의 데이터 중에서 하나 혹은 원하는 데이터를 PK로 빠르게 찾는 것을 잘하는 서비스이다.
- 대량의 Range Scan, Full Text Search, Aggregation과 같은 것들은 잘 하지 못한다.
2. 액세스 패턴을 식별
- 데이터 모델링은 엔티티별로 생성하는 것이 아닌 하나의 테이블에서 시작하는 것이다.
- 하나하나의 테이블이 모두 리소스다. 운영환경이 200개가 있다고 가정하면, 200개의 테이블이 모두 관리 대상이 될 것이다.
- 각각의 테이블에 대해 WCU/RCU , 백업 복구 등을 해줘야 하는데 운영관리를 위해 완전관리인 DynamoDB를 사용하는 것인데 운영부담을 만들어서는 안된다.
- 테이블에 파티션 수가 많을 수록 Application에 데이터 액세스가 여러 개 파티션 동시에 일어날 확률이 높아 테이블 전체 성능이 올라가 핫 파티션 확률도 줄어들게 될 것이다.
- 그렇기 때문에 사이즈가 작은 여러 테이블 사용은 지양해야 한다.
- 반대로 사이즈가 큰 테이블을 지향해야 한다.
- Application의 종류는 OLTP여야 한다.
- OLAP인 분석이 필요한 경우는 외부로 분석 파이프라인을 만들어서 분석을 처리할 수 있게 만들어야 한다.
3. 데이터 라이프 사이클 / Primary keys 식별
- 데이터 라이프 사이클은 데이터 특성에 따라 TTL의 필요 여부를 고민해야 하고 백업 정책또한 반드시 고민해야 한다.
- DynamoDB는 PK키로만 데이터를 조회할 수 있기 때문에 중요하다.
- 싱글 테이블 디자인이 Application의 Entity를 하나의 테이블에서 Access할 수 있도록 만드는 것이다.
- 싱글 테이블은 일반적으론 다수의 테이블에 비해 운영 부담이 적거나 거의 없어지고, 높은 테이블 최대 쓰로틀링이 줄어들 수 있다는 장점이 있다.
- 하지만, 처음 NoSQL을 접하면 러닝커브가 높고 시계열 데이터나 엔티티 별로 다른 액세스 패턴을 가지는 경우는 적합하지 않다.
3가지 공통점
- 파티션 키를 UserID로 고정하고 시작
- 확장성 있는 규모 있는 서비스 구축을 위해 사용해야 하는데, userId로 파티션 키를 고정하면 한 사용자의 모든 데이터가 하나의 pk를 벗어날 수 없다는 점이 있다. 대량의 트래픽을 유발하는 헤비 유저를 염두해 두고 키 디자인을 할 필요가 있다.
- Entity별로 테이블을 만드려고 하는 것
- dynamoDB는 테이블 간 조인이 지원되지 않기 때문에 Application 별로 부담이 증가
- GSI를 액세스 패턴별로 만드려는 경향
- GSI는 하나하나가 비용과 직결되기 때문에, 사용을 최소화하는 것이 좋다.
실습
사전지식
DynamoDB를 먼저 사용하기 전에 알아야 하는 것이 있다.
- Data Structure
- Scalar Type : Number(숫자, N으로 표시), String(문자, S로 표시), Binary(B로 표시), Boolean and Null
- Document Types : 리스트와 맵
- Set Types : 숫자 집합(Number Set), 문자 집합(String Set), Binary Set(이진자료형 집합)
- Atribute : 하나의 아이템에 있는 하나의
element (요소)
를 의미한다. RDB에서의 column 한 개와 동일한 개념이다.attribute의 타입은string
,integer
,boolean
과 같은 심플한 타입과list
와maps
와 같은 복잡한 타입 모두 가능하다. - Number : N?
- String : S?
- 단순 기본 키의 경우 첫 번째 속성 값(파티션 키)의 최대 길이는 2048바이트입니다.
- 복합 기본 키의 경우 두 번째 속성 값(정렬 키)의 최대 길이는 1024바이트입니다.
2016-02-15
2015-12-21T17:42:34Z
20150311T122706Z
- Binary : B?
- 단순 기본 키의 경우 첫 번째 속성 값(파티션 키)의 최대 길이는 2048바이트다.
- 복합 기본 키의 경우 두 번째 속성 값(정렬 키)의 최대 길이는 1024바이트다.
- Boolean : BOOL?
- Null : NULL?
[]
배열 형식 : SS? OR NS?- Map : M?
DynamoDB의 Number타입은 쌩뚱맞게도, number로 지정하지만 서버에서 Put으로 넣을 때는 string으로 넣는다. 이에 대한 AWS 공식문서의 설명이 있다.
모든 숫자는 언어와 라이브러리 간의 호환성을 극대화하기 위해 네트워크를 통해 DynamoDB에 문자열로 전송됩니다. 하지만 DynamoDB는 해당 문자열을 연산 작업에서 숫자 형식 속성으로 처리합니다.
문자열은 UTF-8 이진수 인코딩을 사용하는 유니코드다. 속성이 인덱스 또는 테이블의 키로 사용되지 않고 최대 DynamoDB 항목 크기 제한인 400KB로 제한되는 경우 문자열의 최소 길이는 0일 수 있다.
DynamoDB에서는 문자열을 사용해 날짜 형식을 표시해야 한다. Date Type이 따로 존재하지 않기 때문이다. (ISO 8601 string으로 저장하는 것이 좋다.)
이진수 형식의 속성에는 압축 텍스트, 암호화 데이터 또는 이미지 같은 모든 이진수 데이터가 저장될 수 있다.
이진수 형식 속성으로 기본 키 속성을 정의하는 경우 다음 추가 제약이 적용된다.
데이터를 DynamoDB로 보내기 전에 Base64 인코딩 형식으로 이진수 값을 인코딩해야 한다.
사용 전 미리 바이너리 데이터 형태로 바꿔서 보내란 뜻이다.
true나 false상태를 저장할 수 있다.
Null은 알려지지 않았거나 정의되지 않은 상태의 속성을 나타낸다.
FavoriteThings: ["Cookies", "Coffee", 3.14159]
맵 형식 속성은 정렬되지 않은 이름-값 페어의 모음을 저장할 수 있다. 맵은 중괄호(
{ ... }
)로 묶는다.맵은 JSON 객체와 유사하다. 맵 요소에 저장할 수 있는 데이터 형식에는 제한이 없으며, 한 맵에 형식이 다른 요소도 함께 있을 수 있다.
맵은 DynamoDB에 JSON 문서를 저장하는 데 이상적이다. 다음은 문자열, 숫자 및 다른 맵을 포함하는 중첩 목록이 저장된 맵을 나타내는 예제이다.
{ Day: "Monday", UnreadEmails: 42, ItemsOnMyDesk: [ "Coffee Cup", "Telephone", { Pens: { Quantity : 3}, Pencils: { Quantity : 2}, Erasers: { Quantity : 1} } ] }
- Return Value
기본적으로 DynamoDB의 return 값은 각 메서드의 Output이 된다.
export interface UpdateItemOutput { Attributes?: Record<string, AttributeValue>; ConsumedCapacity?: ConsumedCapacity; ItemCollectionMetrics?: ItemCollectionMetrics; }
예를들어,
UpdateItemOutput
은 위처럼 사용해야 하는데, 2번째를 제외하고 Attributes만 본다면const params = { TableName: tableName, Key: { user_email: { S: email }, }, UpdateExpression: "SET #user_name= :user_name", ExpressionAttributeNames: { "#user_name": "user_name", }, ExpressionAttributeValues: { ":user_name": { S: name, }, }, ReturnValues: "UPDATED_NEW", };
위에
params
가 모두 Attributes가 된다. 놀랍게도, ReturnValues
를 제외한 다른 값들 중 하나라도빠진다면 바로 에러가 발생한다.
결국 UpdateItemOutput을 만족시키지 못한다면 Client 실행에 문제가 생길 수 있기 때문에 자료형을 받기 위해 생성한, Dto를 입력하기 보다는 각 커맨드의 Output을 return type으로 지정하는 것이 작성 시 더 효율적이라는 결론이 나왔다.
한편, 각 메서드마다 Output만 존재하는 것이 아니라, Input도 같이 존재하는데 Input과 Output이 같은 형태를 지니고 ( 같지 않더라도 매우 유사한) 있기 때문에 Output과 Input중 한 가지만 작성해도 된다는 결론이 나왔고 메서드의 Output만 작성하게 됐다.
// 이런식으로 Input의 타입도 잡아줄 수 있다. const params : UpdateItemCommandInput = { };
위의 방식으로 Input타입을 잡는다면, 굳이 Output을 사용할 필요가 없기 때문에(형식이 이미 Input Type으로 정해짐)
제네릭에 return value를 정해서 사용할 수 있다.
public async isExistByHostEmail( dynamoClient: DynamoDBClient, tableName: string, hostEmail: string ): Promise<Boolean> { const params: GetItemInput = { TableName: tableName, Key: { user_email: { S: email, }, }, }; const command = new GetItemCommand(params); const result = await dynamoClient.send(command); if (result.Item === undefined) return false; else return true; }
예를 든다면, 이런식이다.
Promise<Boolean>
으로 dyanmodb에 사용된 커맨드를 통해 나온 결론 혹은 value를 리턴하고 params에 itemInput으로 형식을 잡아주는 것이다.그렇기 때문에, 리턴될 값의 사용 유무에 따라 반환 타입을 지정해주면 된다.- 집합
DynamoDB에서도 숫자, 문자열 또는 이진수 값의 집합을 나타내는 형식을 지원한다. 집합 내의 모든 요소의 형식은 동일해야 한다. 예를 들어, 숫자 집합 형식의 속성은 숫자만 포함할 수 있으며, 문자열 집합은 문자열만 포함할 수 있는 식이다.
집합 내 값의 수에는 제한이 없다. 단, 값을 포함하는 항목이 DynamoDB 항목 크기 제한(400KB)을 초과하지 않아야 한다.
집합 내의 각 값은 고유해야 한다. 집합 내 값의 순서는 유지되지 않는다. 따라서 애플리케이션이 집합 내에서 요소가 특정 순서로 유지된다는 가정 하에 실행되지 않아야 한다. DynamoDB는 빈 집합을 지원하지 않지만 집합 내에서 빈 문자열과 이진 값이 허용된다.
["Black", "Green", "Red"] [42.2, -19, 7.5, 3.14] ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]
Item: {}
를 제외하고, 하나씩 설명하면Key
OrKeyConditionExpression
은
내가 사용하려는 테이블 혹은 보조 인덱스의 파티션키와 정렬키를 의미한다.
ConditionExpression
은
작업이 성공되기 위해서 표현되야하는 하나 이상의 조건을 의미한다.
GetItem, PutItem, UpdateItem, DeleteItem 등 다양한 DynamoDB 작업에 사용된다.
// ~와 일치하는지. ConditionExpression: "user_name= :user_name", ExpressionAttributeValues: { ":user_name": { S: name }, }, // ~보다 크다면. "ConditionExpression": "attribute_not_exists(price) OR price >= :val", "ExpressionAttributeValues": { ":val": {"N": "10"} }
ExpressionAttributeNames
은
작업에 사용하려는 Key값에 alias를 붙여서 대신 사용하려는 목적으로 사용하는데, 보통은 Key값 그대로 받아서 사용한다.
ExpressionAttributeValues
은
실제로 대체될 Value가 들어갈 자리다. ExpressionAttributeNames 에서 alias 혹은 key를 그대로 받았다면, 그 값을 입력하거나 혹은 ExpressionAttributeNames 없이 ExpressionAttributeValues 만을 사용해서 검색할 수도 있다.
KeyConditionExpression: "user_name = :user_name and user_created_at= :user_created_at", ExpressionAttributeValues: { ":user_name": { S: name }, ":user_created_at": { S: createdAt }, },
간단한 테이블 생성 과정
생성 버튼을 누르면 가장 먼저 위처럼 나온다.
파티션 키(PK)와 정렬 키가 (SORT) 있다. 일반적으로 정렬키는 초기 사용자에게 잘 설계해서 사용하는 것이 아니라면 추천하지 않는다.
그 이유는 일단 기본적으로 데이터 탐색 시 정렬키가 있다면 꼭 정렬 키를 받도록 반드시 keyCondition에 포함시켜야 하기 때문에 조회할 때 에로사항이 존재했고, 유저 Email을 통한 탐색으로 회원 테이블을 구성한 자사 서비스와는 어울리지 않았다는 경험에서 비롯된 이유이다.
차라리 LSI(로컬 보조 인덱스) 혹은 GSI(글로벌 보조 인덱스)를 활용해 DB를 조회하는 것이 더 효율적이었다.
그 다음 아래로 내려보면,
테이블 설정이 있는데 기본 설정을 선택하면 Dynamo DB가 프로비져닝(사용 안해도 리소스를 일부 할당) 되기 때문에 요금이 부과되기 때문에 사용한 만큼 요금이 청구되는 온디맨드가 초반 선택에서는 무난하다.
이제 온디맨드까지 골랐다면 LSI를 만들 수 있는데 주의 할 점은 LSI는 테이블 생성시에만 만들 수 있기 때문에 테이블을 확정짓기 전 끝까지 고민하는게 좋다.
간단한 테스트 환경 구성에서는 GSI를 사용하여, 자유롭게 구성하여도 좋겠지만 결국 Production Level까지 끌어올려야 한다면 테이블 설계를 신중하게 하여 초기 테이블의 LSI를 잘 설정하는 것이 중요하다.
태그를 달고 각 Key Value까지 모두 설정이 완료 됐다면, 해당 DB에 접근할 수 있는 권한을
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AccessTableAllIndexesOnBooks", "Effect": "Allow", "Action": [ "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:BatchWriteItem", "dynamodb:GetItem", "dynamodb:BatchGetItem", "dynamodb:Scan", "dynamodb:Query", "dynamodb:ConditionCheckItem" ], "Resource": [ "arn:aws:dynamodb:ap-northeast-2:account-ID:table/table_name", "arn:aws:dynamodb:ap-northeast-2:account-ID:table/talbe_name/index/*" // table의 인덱스에 접근 ] } ] }
이렇게 설정해준다면 이제 Dynamo DB에 접근하여 사용할 수 있다.
Typescirt에서 DynamoDB를 사용하는 방법은 크게
- aws-sdk v2에서 사용한
new Dyanmo()
로 생성자를 만들어 사용하는 방법
- aws-sdk v3에서 사용되는 Client를 만들어 사용하는 방법
이렇게 2가지가 존재하고, 내가 설명할 Dynamo DB를 사용하여 CRUD하는 방법은 Dynamo Client를 사용한 방식이다.
DynamoDB Client
- PutItemCommand. (Create)
PutCommand는 DynamoDB Client가 데이터를 DyanmoDB에 생성할 때 사용되는 메서드다.
PutCommand
const params: PutItemInput = { TableName: tableName, Item: marshall( { object Property... }, { convertClassInstanceToMap: true } ), }; const command = new PutItemCommand(params); return await dynamoClient.send(command);
나는 marshall을 사용했지만, 다른 방식을 알고 있다면 다른 방식을 사용해도 좋다.
마샬을 사용할 경우, convertClassInstanceToMap도 true가 아니라면 메서드가 실행되지 않는다. convertClassInstanceToMap가 true여야 JSON형태의 자료형을 DynamoDB의 자료형으로 매핑시켜주고, DB에 데이터가 삽입된다.
PK나 정렬키, 혹은 보조 인덱스에 사용되는 Key 타입은 미리 정해놓고 사용하는 것이기 때문에 저런 키값들로 사용되는 값들은 null로 사용될 수 없다.
또한, nulluable한 값들을 자유롭게 사용하는 것은 권장되지 않는다.
- QueryCommand. (Get)
QueryCommand는 DynamoDB Client가 테이블에서 데이터를 불러올 때 사용되는 메서드다.
QueryCommand
const params = { TableName: tableName, IndexName: "GSI Index Name", KeyConditionExpression: "user_name = :name and user_id = :id", ExpressionAttributeValues: { ":name": { S: name }, ":id": { N: id }, }, }; const command = new QueryCommand(params); const result = await dynamoClient.send(command);
LSI나 GSI(글로벌 보조 인덱스)를 사용할 때는 꼭 내가 사용할 TableName과 IndexName이 필요하다.
- UpdateItemCommand. (Update)
UpdateItemCommand는 DynamoDB Client가 데이터를 수정할 때 사용되는 메서드다.
UpdateItemCommand
const params = { TableName: tableName, Key: { user_email: { S: email }, }, UpdateExpression: "SET #user_name = :user_name", ExpressionAttributeNames: { "#user_name": "user_name" }, ExpressionAttributeValues: { ":user_name": { S: name, } }, ReturnValues: "UPDATED_NEW", }; const command = new UpdateItemCommand(params); return await dynamoClient.send(command);
여러개의 값을 업데이트 할 때는,
SET #ID_1 = :ID_1, #ID_2 = :ID_2
형태로 사용한다.Querycommand를 사용할 때는 KeyCondition… 이었던 것에 비해 Update에서는
UpdateExpression라는 표현을 사용한다.
UPDATE문을 잘못 사용한다면, DB를 덮어씌우는 개념이기 때문에, 기존의 값 말고 이상한 값이 추가로 생성될 수 있다. 구문을 정확히 이해하고 사용하길 권장한다.
- DeleteItemCommand. (delete)
Delete문은 Update문과 기본적으로 구조가 비슷하다.
DeleteItemCommand
const params = { TableName: tableName, Key: { user_email: { S: email }, }, ConditionExpression: "user_name = :user_name", ExpressionAttributeValues: { ":user_name": { N: name }, }, }; const command = new DeleteItemCommand(params); return await dynamoClient.send(command);
- TransactionCommand ( All )
Transaction 사용법은 일반 메서드와 다르다.
TrasactionCommand
public async transactUserData( dynamoClient : DynamoDBClient, email:string): Promise<number> { const params : TransactWriteCommandInput = { TransactItems : [ { /** has_invite to true */ Update : { TableName : this.tableName, Key : { user_email: { S : email} }, UpdateExpression : "SET #is_accces= :is_accces", ExpressionAttributeNames : { "#is_accces" : "is_accces" }, ExpressionAttributeValues : { ":is_accces" : { BOOL : true } } } }, { /** has message true */ Put: { TableName: this.tableName, Item: { user_email: { S: "my-item-id" }, user_name: { S: "My Item" } .... }, }, } ] } const command = new TransactWriteItemsCommand(params); const send = await dynamoClient.send(command); return send.$metadata.httpStatusCode; }
DyanmoClient의 Transaction Command를 통해 트랜잭션을 진행하게 되는데,
RDBMS처럼 connection을 따로 받아서 한다기 보다는 메서드 자체가 트랜잭션으로 돌아간다. 여기서 주의할 점은, 굳이 하나의 트랜잭션에 같은 메서드를 여러번 사용할 수 없다는 것이다.
일반적으로 하나의 테이블에 여러 테이블로 사용될 데이터를 넣어 놓는 것이 권장되지 않기 때문에 하나의 transaction에 같은 메서드를 중복하여 사용할 수 없다.
하나의 트랜잭션에 같은 메서드의 작업을 2번 하려고 하면,
ValidationException: Transaction request cannot include multiple operations on one item ( 트랜잭션 요청에 하나의 항목에 대한 여러 작업을 포함할 수 없습니다. )
라는 에러가 발생하게 된다.
만날 수 있는 ERROR LIST
- DynamoDB : Attribute name is a reserved keyword
DynamoDB TABLE 의 컬럼명과 DynamoDB 의 예약어가 충돌이 나서 발생하는 ERROR
내 케이스의 경우 user라는 예약어를 사용해서 에러가 발생했다.
예약어 목록
ABORT ABSOLUTE ACTION ADD AFTER AGENT AGGREGATE ALL ALLOCATE ALTER ANALYZE AND ANY ARCHIVE ARE ARRAY AS ASC ASCII ASENSITIVE ASSERTION ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTH AUTHORIZATION AUTHORIZE AUTO AVG BACK BACKUP BASE BATCH BEFORE BEGIN BETWEEN BIGINT BINARY BIT BLOB BLOCK BOOLEAN BOTH BREADTH BUCKET BULK BY BYTE CALL CALLED CALLING CAPACITY CASCADE CASCADED CASE CAST CATALOG CHAR CHARACTER CHECK CLASS CLOB CLOSE CLUSTER CLUSTERED CLUSTERING CLUSTERS COALESCE COLLATE COLLATION COLLECTION COLUMN COLUMNS COMBINE COMMENT COMMIT COMPACT COMPILE COMPRESS CONDITION CONFLICT CONNECT CONNECTION CONSISTENCY CONSISTENT CONSTRAINT CONSTRAINTS CONSTRUCTOR CONSUMED CONTINUE CONVERT COPY CORRESPONDING COUNT COUNTER CREATE CROSS CUBE CURRENT CURSOR CYCLE DATA DATABASE DATE DATETIME DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DEFINE DEFINED DEFINITION DELETE DELIMITED DEPTH DEREF DESC DESCRIBE DESCRIPTOR DETACH DETERMINISTIC DIAGNOSTICS DIRECTORIES DISABLE DISCONNECT DISTINCT DISTRIBUTE DO DOMAIN DOUBLE DROP DUMP DURATION DYNAMIC EACH ELEMENT ELSE ELSEIF EMPTY ENABLE END EQUAL EQUALS ERROR ESCAPE ESCAPED EVAL EVALUATE EXCEEDED EXCEPT EXCEPTION EXCEPTIONS EXCLUSIVE EXEC EXECUTE EXISTS EXIT EXPLAIN EXPLODE EXPORT EXPRESSION EXTENDED EXTERNAL EXTRACT FAIL FALSE FAMILY FETCH FIELDS FILE FILTER FILTERING FINAL FINISH FIRST FIXED FLATTERN FLOAT FOR FORCE FOREIGN FORMAT FORWARD FOUND FREE FROM FULL FUNCTION FUNCTIONS GENERAL GENERATE GET GLOB GLOBAL GO GOTO GRANT GREATER GROUP GROUPING HANDLER HASH HAVE HAVING HEAP HIDDEN HOLD HOUR IDENTIFIED IDENTITY IF IGNORE IMMEDIATE IMPORT IN INCLUDING INCLUSIVE INCREMENT INCREMENTAL INDEX INDEXED INDEXES INDICATOR INFINITE INITIALLY INLINE INNER INNTER INOUT INPUT INSENSITIVE INSERT INSTEAD INT INTEGER INTERSECT INTERVAL INTO INVALIDATE IS ISOLATION ITEM ITEMS ITERATE JOIN KEY KEYS LAG LANGUAGE LARGE LAST LATERAL LEAD LEADING LEAVE LEFT LENGTH LESS LEVEL LIKE LIMIT LIMITED LINES LIST LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCATOR LOCK LOCKS LOG LOGED LONG LOOP LOWER MAP MATCH MATERIALIZED MAX MAXLEN MEMBER MERGE METHOD METRICS MIN MINUS MINUTE MISSING MOD MODE MODIFIES MODIFY MODULE MONTH MULTI MULTISET NAME NAMES NATIONAL NATURAL NCHAR NCLOB NEW NEXT NO NONE NOT NULL NULLIF NUMBER NUMERIC OBJECT OF OFFLINE OFFSET OLD ON ONLINE ONLY OPAQUE OPEN OPERATOR OPTION OR ORDER ORDINALITY OTHER OTHERS OUT OUTER OUTPUT OVER OVERLAPS OVERRIDE OWNER PAD PARALLEL PARAMETER PARAMETERS PARTIAL PARTITION PARTITIONED PARTITIONS PATH PERCENT PERCENTILE PERMISSION PERMISSIONS PIPE PIPELINED PLAN POOL POSITION PRECISION PREPARE PRESERVE PRIMARY PRIOR PRIVATE PRIVILEGES PROCEDURE PROCESSED PROJECT PROJECTION PROPERTY PROVISIONING PUBLIC PUT QUERY QUIT QUORUM RAISE RANDOM RANGE RANK RAW READ READS REAL REBUILD RECORD RECURSIVE REDUCE REF REFERENCE REFERENCES REFERENCING REGEXP REGION REINDEX RELATIVE RELEASE REMAINDER RENAME REPEAT REPLACE REQUEST RESET RESIGNAL RESOURCE RESPONSE RESTORE RESTRICT RESULT RETURN RETURNING RETURNS REVERSE REVOKE RIGHT ROLE ROLES ROLLBACK ROLLUP ROUTINE ROW ROWS RULE RULES SAMPLE SATISFIES SAVE SAVEPOINT SCAN SCHEMA SCOPE SCROLL SEARCH SECOND SECTION SEGMENT SEGMENTS SELECT SELF SEMI SENSITIVE SEPARATE SEQUENCE SERIALIZABLE SESSION SET SETS SHARD SHARE SHARED SHORT SHOW SIGNAL SIMILAR SIZE SKEWED SMALLINT SNAPSHOT SOME SOURCE SPACE SPACES SPARSE SPECIFIC SPECIFICTYPE SPLIT SQL SQLCODE SQLERROR SQLEXCEPTION SQLSTATE SQLWARNING START STATE STATIC STATUS STORAGE STORE STORED STREAM STRING STRUCT STYLE SUB SUBMULTISET SUBPARTITION SUBSTRING SUBTYPE SUM SUPER SYMMETRIC SYNONYM SYSTEM TABLE TABLESAMPLE TEMP TEMPORARY TERMINATED TEXT THAN THEN THROUGHPUT TIME TIMESTAMP TIMEZONE TINYINT TO TOKEN TOTAL TOUCH TRAILING TRANSACTION TRANSFORM TRANSLATE TRANSLATION TREAT TRIGGER TRIM TRUE TRUNCATE TTL TUPLE TYPE UNDER UNDO UNION UNIQUE UNIT UNKNOWN UNLOGGED UNNEST UNPROCESSED UNSIGNED UNTIL UPDATE UPPER URL USAGE USE USER USERS USING UUID VACUUM VALUE VALUED VALUES VARCHAR VARIABLE VARIANCE VARINT VARYING VIEW VIEWS VIRTUAL VOID WAIT WHEN WHENEVER WHERE WHILE WINDOW WITH WITHIN WITHOUT WORK WRAPPED WRITE YEAR ZONE
- ValidationException: Invalid UpdateExpression: An expression attribute name used in the document path is not defined; attribute name: #put_user
사용하고 있는 변수가 정의되어 있지 않거나 undefined인 경우 발생한다.
해결하기 위해선, 코드를 수정하거나 undefined인 데이터를 구분하여 존재하지 않는 항목을 생성해 DynamoDB에 저장해야 한다.
- Key element does not match the schema
기본키의 절반만 Key값만을 포함하고 있기에 생긴 에러이다. 메인키가와 정렬키가 모두 들어가야 한다.
Key : { "id" : id, }, // 정렬키가 빠져있기 때문에 에러가 발생했다. Key : { "id" : id, "createdAt" : createdAt }, // 기본키와 정렬키 모두 들어가 에러가 해제됐다.
Share article