본문 바로가기

웹 운영/AWS

[CDA] 섹션 22 - AWS 서버리스 : Dynamo DB

728x90

<319, 320강 Dynamo DB 개요>

 

 

AWS 의 서버리스 DB, Dynamo DB

 


> 서버리스 DB 에 대해 배워보자 (스케일링을 자동으로 해주고, Lambda 및 다른 서비스와 잘 연계된다) 
> DD 는 NoSQL 서버리스 DB 이다

 

 

서비스를 AWS 에서 운영하는 전통적인 Cloud 형태의 구조



> 위와 같은 저통적인 구조가 있음 (위에서 RDS 는 MySQL, PostgreSQL 로 지원)
>> 이는 RDBMS 를 활용하고, SQL 쿼리가 있어 데이터 모델링, 테이블, 스키마 등에 대한 강력한 규약이 있다. 
>> Join / Aggregation 등 복잡한 상황을 잘 해결한다 
> 하지만 이 모델링에서 스케일링 관점에서는 수직 스케일링인데, DB 계층에 관해서 본다면, 수직 스케일링시 DB 자체를 교체해야 한다
> 수평 스케일링도 어느 정도 가능하긴 하지만, reading capability 상향정도이다 (Ec2 증가, RDS Read Replicas (읽기 전용 복제본) 등을 활용)


<NoSQL DB>

Non-Relational DB 로, 분산되기 때문에 수평적 확장이 가능하다 (ex: Mongo, Dynamo 등)
> 이 DBMS 는 JOIN 쿼리를 지원하지 않는다 (제한적인 지원이 있기도 하지만, 없다고 보는게 맞다)
> SUM/AVG 와 같은 집계 또한 지원하지 않는다 (위와 동일) 
>> 더 많은 쓰기 / 읽기 용량이 필요한 경우, 백그라운드에서 인스턴스를 추가하여 수평 확장이 가능하다 


<Dynamo DB>

> Fully Managed DB, 고가용성 (High AV, AZ 에 걸쳐 복제 가능)
대규모 워크로드로 확장 가능, 완벽하게 분산 가능
> Fast & Consistent in performance (지연이 짧다) 
> AWS 서비스이므로 IAM 으로 인증 / 인가가 가능하다
> 이번에 배울 DD Stream 으로 "Event Driven Programming" 이 가능하다
> 저렴하며, auto-scaling 을 지원한다
> 다른 스토리지 Tier 에 대한 Standard & Infrequent Access (IA) 테이블 클래스가 있다고 함 -> 뭔 개소린지 모르겠음


<DDB Basic>

> Table 이 있고, PK 가 있다
item = row 는 NoSQL 에선 같은 말이다. 각 테이블은 item 을 무한대로 가질 수 있다
>> 각 item 은 속성 (Attribute) 를 가지고 있다 - column 과 같은 것.  (열보다 강력, 시간이 지나 추가될 수 있음 : NoSQL 속성)
>> 각 item 의 최대 크기는 400KB 이다
>> 지원되는 Data Type 은 다음과 같다
>>> Scalar Type : String, Number, Binary, Boolean, Null
>>> Document Types : List, Map 
>>> Set Types : String Set, Number Set, Binary Set 등


<PK 고르기 ## 시험에 무조건 나온다 ## >


PK 전략으로 Hash 값을 사용하는 전략

 

 

> Option1 : Partition Key (HASH 전략)
>> 파티션 키는 고유한 값으로 정해지며, 데이터 분산에 적합하다

 

 

PK 를 Parition Key (위 해쉬값 따위) 와 Sort Key 를 복합키로 사용하는 전략

 


> Option 2 : Partition Key + Sort Key (HASH + RANGE) 
> 두 조합의 합으로 나온 값은 Item 마다 unique 해야 한다 
>> 우선 데이터는 Partition Key 로 그룹핑 된다 (좋은 파티션 키 선택이 중요한 이유)
>> 예를 들어 user-game 이라 테이블이면, USER_ID 를 파티션 키로 사용하고, GAME_ID 를 Sort KEY 로 사용할 수 있다 
>> 위 상황은 사용자가 여러 게임이 가능한 상황이다
>> 둘을 합쳐서 Primary Key 가 되면, 각각은 고유한 값이기 때문에 그 합도 둘다 사용하는 테이블에선 고유 값으로 사용할 수 있다 (각 테이블에선 각각 PK 역할을 해줘야 하는 듯) 
>>>  Unique 함을 보증해줘야 하기 때문에 Partition Key 를 잘 골라야 한다 (분산 환경에 적합하도록) 


<예시를 살펴보자 - 시험에 이런식으로 나옴 (어떤 Table 의 Best Partition Key  를 골라라 이런식)>

> 영화 DB 를 구성한다. 이 때, Best Partition Key 를 생각해보자 (for maximizing data distribution)
>> 보기 : movie_id, pd_name, lead_actor_name, movie_lang
> 사실 item 마다 고유한 값이 하나 밖에 없음... -> moivie_id~!
> movie_lang 처럼 추정 값(실제 Value) 에 치우쳐진 데이터는 좋은 선택지가 아님
> 항상 Cardinality 가 높은 것으로 고려, 가장 많은 Value 를 가질 수 있는 것을 활용 (같은 뜻, Cardinality = 집합의 크기)


-----------------------------------------

 


<321강 Dynamo DB 개요 실습>

 

위와 같이 기본적으로 설정해볼 수 있다


> 생성을 해봄, partitionkey 는 user-id, 직접 설정에는 어떤게 있나 살펴보자 (user 테이블)

1. Table Class
> DynamoDB Standard (General Purpose 목적, 대부분임) // IA (오랫동안 보관만 하거나, 읽기/쓰기가 많이 이루어지지 않는 경우) - 비용 이점
2. Capacity Calculator (곧 ㅈㄴ 배움)
3. Read/Write Capacity (바로 다음 섹션에서 배울거임)
OnDemand / Provisioned (Free-tier), 우선 프리티어를 위해 Auto Scaling Off 후 Provisioned 선택, Free Tier 는 10까지 가능
4. Secondary Index (곧 ㅈㄴ 배움)

>  테이블이 생성되었다
> View Item 이동, Create_item 생성 (이하 두가지), User_ID / Age / First_name / LAst_name 순

 

 

처음에 넣어주지 않은 값은 그냥 들어가 있지 않다

 

 


>> john_1234 / 만들지 않음 / john / fuck
>> john_1234 로 시도시 오류나므로, alice_1234 / 41 / alice / 만들지 않음 (PK 중복이기 때문에 john_1234 는 안된다)
>> 애초에 NULL 이나 빈 값으로 넣어준게 아니라, 열을 생성도 안함 >> RDB 같은 경우 정의되지 않은 열 오류가 나겠지만, NoSQL 은 다름 (자동으로 NULL 이 됨)
> 근데 자동으로 NULL 이 되는건 DD 의 특성일까 아니면 NoSQL 의 특성일까? MongoDB 도 그러나? ㅇㅇ 그럴듯
>> 이전 데이터에 영향을 주지 않고 추후 속성 추가가 가능~!

 

user_post 에서는 복합키가 사용된다 (Sort Key)

 

 

> 이번엔 user_post  테이블을 만든다. 이 때, Sort_Key 를 post_ts (Timestamp) 로 한다, 나머지는 모두 위와 동일
> Item 을 생성해보고, 이 때 user_id 를 아까 썼던 john_1234 로 하고, post_ts 는 2024-08-26T12:34:09Z 로 함 
>> 두번째를 생성하는데, 이 때 post_ts 만 2024-07-01T14:53:00Z 로 바꾼다
>> 생성이 된다. (위와는 다르게) 동일한 user_id 이지만, 다른 post_ts 를 사용하므로 정상적으로 생성이 되는 것을 확인할 수 있다!
>> user_id 로 Partition 하여 (john_1234 가 쓴 게시물들 중) post_ts 로 Sort 가능 (시간순으로 정렬) 이런 의미도 있다고 하는 것 같음 (그 의미를 따르는게 자연스러울 듯)


-----------------------------------------

 


<322강 DD WCU 및 RCU 처리량>

> R/W 처리량 지정으로 Table's Capacity 를 조절하는데, 두가지 용량 모드가 있다 (아까 provisioned 로 했던 것)
> 1 Provisioned Mode (default)
>> 초당 read/write 수를 specify 해야 함 (계획 세우는게 필요)
>> Provisioned 된 양만큼 지불하게 된다 (가령 10개의 Read, 5개의 Write 하면 이거에 대해서만 charge 됨)

> 2 On-Demand Mode
>> Read/Write 단에 발생하는 워크로드에 대해 독립적으로 자동으로 up/down 스케일링을 해준다
>> Capacity Planning 이 따로 필요하지 않다, 훨씬 더 비쌈 
> 둘 사이에 변경도 자유롭다

 

<Provisioned Mode 살펴보기>

 

> Capacity Unit 인 RCU/WCU 를 프로비저닝 해야 한다

> auto-scaling 의 옵션이 있긴 있으니, 너무 고민할 필요는 없음. 그리고 모니터링 하면서 변경해도 됨

> 너가 프로비저닝한 양보다 더 많이 사용되어도 "Burst Capacity" 를 일시적으로 사용할 수 있다 (어느정도는 AWS 가 용인해주는 것)

>> 하지만 이 Burst Capacity 역시 초과할 경우, "ProvisionedThroughputExceededException" 이 발생

>> 발생할 경우 재시도를 하도록 하는 전략을 "Exponential Backoff" 라고 한다

> 시험에서 WCU/RCU 계산에 대해서도 물어보니, 좀 더 세부적으로 살펴볼 필요가 있음

 

 

<WCU 계산 - 용량 소숫점 주의>

 

> 1 WCU 는 최대 1KB의 항목에 대해 초당 1개의 쓰기를 의미

> 1KB 보다 클 경우, 더 많은 WCU 를 소모하게 된다 

 

> Ex: 10 item per second, with item size 2KB

>> 2WCU 는 2KB 항목에 대해 초당 1개의 쓰기

>> 따라서 20WCU? (맞았음, 10*(2KB/1KB))

 

> Ex: 6 item per second, with item size 4.5KB

>> 위와 동일하게 계산하면 27WCU

>> 틀렸음. 30WCU 라고 함. 4.5KB 와 같은 소숫점들은 DD 에서는 언제나 올림되어서 계산된다 (따라서 5KB 로 계산)

 

> EX: 120 item per min, with item size 2KB

>> (120/60) * (2KB/1KB) = 4WCU

> WCU 는 사실 이렇게 직관적이고 쉬움. 좀 더 복잡한건 Reading 쪽이라고 한다

 

 

<Reading Strategy>

 

 

DD 뒷단에서 write 및 복제가 이루어진다

 

 

> 우리가 관리하진 않지만, DD 역시 뒷단에는 서버들이 있다 (위 그림처럼 3대 가정)

> 데이터는 모든 서버에 분산되고 복제되어 저장된다

> DD 의 동작 방식은, App 은 한 대에 쓰기를 실행하면, 그 App 이 나머지 서버들에게 복제를 하는 방식이다. 

> 이 때, Read 를 할 때, App 이 썼던 서버가 아닌, 다른 복제 서버로 부터 읽어올 가능성이 높

>> 이 때!!! 두가지 모드에 대해 이해할 필요가 있다

 

> Eventually Consistent Read (default)

>> write 한 직후에 read 를 할 경우, 오래된 데이터를 얻을 가능성이 있다 (신규라면 못 얻을 가능성도 있다는 뜻?)

>> Replication 이 아직 진행되지 않았기 때문이다 (100ms 이후 정도면 충분히 되긴 함)

 

> Strongly Consistent Read

>> 위와 같은 조금의 오차도 허용하지 않아야 할 경우 사용된다

>> API 호출을 할 때 Parameter 중 "Consistent Read": "True" 로 설정해줘야 한다

>> 왜 따로 설정해줘야 할까? 계속 이것만 쓰면 되지 않냐?

>>> RCU 를 두번 소비하기 때문에 더 비쌈 ㅋㅋ 당연한거임. 또한 당연히 Locking 따위의 작업이 더 들어가니까 Latency 도 더 낮긴 할거임

 

 

<RCU 계산 - 용량 소숫점 주의>

 

> 1 RCU 는 조금 더 복잡하게, 다음 두가지 의미가 있다

> 1 RCU 는 최대 4KB의 항목에 대해 초당 1개의 Strongly Consistent Read 

> 1 RCU 는 최대 4KB의 항목에 대해 초당 2개의 Eventually Consistent Read 

 

> Ex: 10 개의 Strongly Consistent Read per second, with 4KB size

>> 1RCU 니까, 초당 1개의 강력한 읽기를 보장한다. 그럼 10RCU 

 

> Ex: 16 개의 Eventually Consistent Read per second, with 12KB size

>> 1RCU 는 초당 2개의 4KB 약한 읽기를 보장한다. >> 3RCU 는 초당 2개의 12KB 약한 읽기를 보장한다 >> 24RCU (초당 x 개나 yKB 순차적으로 비례해서 곱해나가면 됨) > 쉽게 생각하는 방법

>> (16/2) * (12/4) = 24 RCU (아니면 그냥 강사가 제시하는 공식 사용해도 됨)

 

> Ex : 10 개의 Strongly per second, with 6KB

>> 1 RCU 는 초당 1개의 4KB 짜리 강력 읽기 >> 1.5 RCU 는 초당 1개의 6KB 짜리 >> 15 RCU 는 이를 10개 가능

>> 이번에도 틀렸음, 10 * (8/4) = 20 RCU 가 답이라고 함

>>> 어찌 보면 당연한거인듯?? 4KB 당 읽기인데, 6KB 를 읽으려면 두개가 필요, 즉 8KB 를 읽으려고 하는 것임! (용량에 대해선 소숫점 불가를 기억) (위에서도 1KB 당 쓰기인데, 4.5KB 를 쓰려고 하니까 5KB 를 쓰려고 한다고 가는거임) 

 

 

<Partitions Internal : DD 의 파티션 동작>

 

 

DD 에서 접근시 Partition Key 를 Hash Function 을 통과해 파티션을 찾는다

 


> DD 는 테이블로 구성되고, 테이블에는 파티션들이 있는데, 파티션은 데이터 복사본들을 말한다 
> 앱이 write 를 하면, 파티션 키 / 정렬 키 / 데이터를 보내는데, 이 때 Partition Key 값은 "Hash Function" 을 통과해 자신이 가야할 파티션을 알게됨 
> 파티션의 양을 계산하고 싶으면 다음과 같은 수식을 쓰면 되는데, 시험을 위해 알필요는 없음
>> Number of partitions by capacity = (RCU/3000) + (WCU/1000)
>> Num of partitions by size = (total size / 10GB) 

> 알아야 할 것은, WCU 들과 RCU 들이 Partition 들에게 even 하게 분배된다는 것
>> ex: 10 개의 파티션이 있고, WCU/RCU 도 각각 10개씩 있다 ---> 각 파티션에 WCU, RCU 한개씩 분배된다 


<Throttling>

Rcu, Wcu 를 초과할 경우 발생하고, 이유를 살펴볼 필요가 있다
>> Hot Key :: 한 Partition Key 가 너무 많이 읽히고 있다 (Popular Item) - 제대로 distribute 하지 못한 Partition Key 일 수도 있음
>> Hot Partitions
>> Very Large Items :: WCU / RCU 용량이 계산될 때를 생각해보면, item size 에 비례하게 되므로, 큰 항목을 읽으면 많은 RCU / WCU 를 소비하게 된다

> 이를 일반적으로 해결하기 위해선 다음과 같이 진행한다
>> 처음에 나왔듯이, 지수 백오프를 진행 (SDK 에 이미 존재) 
>> 좋은 파티션키를 선택하고, Partition Key Distribution 을 다시 고려해야 한다 
>> RCU 이슈라면 (한 item? 파티션?을 집중적으로 읽는?), DD Accelerator (DAX) 라는 기능을 봐야 함 


<On-Demand Mode>

> 이 모드는 워크 로드에 따라 알아서 up/down 스케일링을 해준다
> WCU / RCU 같은거 신경 안써도 되고, throttle 도 없다. 대신 훨~씬 비싸다 (약 2.5배) :: Use Case 를 잘 고려해서 사용해야함
> 실제로 사용된 read/write 양 즉, RRU / WRU (Read Unit) 에 따라서 Charge 된다
RCU  / WCU 와 다른점은, 용량 기반이 아닌, 실제 처리된 요청만을 기반으로 계산한다는 점이다


-----------------------------------


<323 DD WCU / RCU 실습> 


> 테이블 > 추가 설정 > RCU/WCU 편집 가능 (쉽게 변경할 수 있음, On-Deamnd 로도 변경 가능)
> 용량 계산기로 사용할 Unit 들의 용량과, 발생 비용도 계산해볼 수 있다
> 여기서 랜덤하게 번호를 입력후, 계산된 WCU/RCU 결과를 예상해보는 것도 좋은 연습이 된다 (무조건 시험에 나오기 때문) 

 

 

WCU / RCU 에 대해 Auto Scaling 을 설정해 둘 수 있다

 


> 내가 궁금했던 Provisioned Mode 에서의 Auto-Scaling 에 대해서도 볼 수 있다
>> Min / Max Cap Unit 을 정해두고, 활성화 % 를 정해두는 것 (ASG 같은 느낌) 

> 당장 Min 을 1 로 하고 지정은 2로 한 후 활성화 해보면, 활성화 후 바로 WCU / RCU 를 1로 설정하는 것을 볼 수 있다 (테이블을 사용하고 있지 않기 때문)
 

-----------------------------------


<324 DD 기본 API> 


> 시험에서는 DD 의 API 호출을 이름으로 구분할 줄 알아야 한다 

 

 

<Write Data 영역>

 

> PutItem (API) 

>> 새 Item 을 생성하거나, PK 가 같은 Item 을 완전히 대체한다

>> WCU 를 소모

 

> UpdateItem (API)

>> 기존 항목을 편집하거나, 존재하지 않으면 전달된 속성으로 새 Item 을 생성한다 (PATCH 에 해당)

>> Atomic Counters 를 사용할 수 있다 (이제 나옴) 

 

> Conditional Writes

>> API 에 추가할 수 있는 속성으로, 특정 조건을 전달하여 조건이 충족되었을 때에만 쓰기/업데이트/삭제를 진행

>> Concurrent Access 활용에 용이 (특정 조건 만족할시에 수정해라, 임계영역 지정 가능?)

 


<Read Data 영역>


> GetItem (API) 

>> HASH 값 혹은 HASH + Range 에 대한 PK에 기반하여 Item 을 읽는다

>> Read 할 Mode 들을 선택할 수 있다 (Eventually, Strongly) 
>> ProjectionExpression 을 지정할 수도 있는데, 이를 통해 원하는 Item 의 속성 일부만을 반환받을 수 있다 

> Query

>> KeyConditionExpression 에 따라 Query 를 설정하여 전달, 그에 해당하는 항목만 반환받을 수 있다 (Key 값들로 검색하는거임) 

>>Key Condition Expression 은 다음과 같은 값을 기준으로 전달된다
>>> 1. Partition Key (필수) : 일치 조건으로 검색된다
>>> 2. Sort Key (선택) : 값에 대해 정렬뿐 아니라 비교 연산자, Between, Begins with 등의 연산도 가능하다 


>> Filter Expression 을 선택적으로 지정할 수 있는데, Query 작업 이후 데이터 반환 전에 필터링을 추가할 수 있다
>>>  (주의) non-key 속성들로만 지정할 수 있다  (Hash, Range 속성을 허용하지 않음) 

>> Query 의 결과 반환
>>> List of Item 을 반환하며, specify 한 Limit 만큼 가져온다 (이 limit 만큼 가져오거나, 데이터 용량 1MB 까지 반환된다) 
>>> 데이터를 더 가져오기 위해 Pagination 을 진행할 수 있다
 


> Scan 
>> GetItem 은 하나의 항목에 대함, Query 가 특정 파티션 키, 정렬키에 대한 것이라면, Scan 은 Table 전체에 대한 것이다 
>> Scan 은 테이블 전체를 조회하기 위해 사용된다. 이 때 Filtering 을 할 수는 있는데, DD 가 하는게 아니라 Client 측에서만 할 수 있으므로 비효율적이다 
>> 제한은 동일하게, 최대 1MB 까지 데이터를 반환하고, 계속 읽고 싶으면 Pagination 기법을 사용해야 한다 
>> 테이블 전체를 읽기 때문에 RCU 를 상당히 소비한다
>> 따라서 다른 작업에 영향을 주지 않으려면, Limit 문을 적용하거나, result 값을 최소화하며 진행해야 함
>>  아니면 차라리 RCU 를 뽞! 사용해서 최대한 빠르게 Scan 작업을 수행하도록 하려면 Parallel Scan 을 수행하면 된다 
>>> 이렇게 하면 여러 작업자가 동시에 데이터 세그먼트 여러개를 스캔하기 때문에 처리량/RCU 가 증가
>> Scan 도 Query 와 같이, Projection & Filter Expression 과 함께 사용될 수 있다 - 전자는 특정 속성을 가져올 때, 후자는 클라이언트 측에서 필터링을 할 때 사용 (RCU 소모량은 동일하다) 


<Delete Data 영역>


> DeleteItem

>> 개별 Item 을 삭제 한다 / 조건부 삭제도 가능하다 (is_deletable=true 인거만 삭제하라 따위)

 

> DeleteTable

>> 테이블과 테이블 내 모든것을 삭제. Scan 한 후 각 Item 을 DeleteItem 쿼리 날리는 것보다 훨씬 빠름 (시험에 나올 수 있음) 

 

<Batch Operation (효율성 측면)>

 
> 지연을 절감하고, API 호출을 절약한다 (모든 동작은 병렬로 처리되어 효율성을 매우 높인다)
> Batch 의 고질적인 단점, 일부 실패 가능하다 : Failed Item 에 대해서 retry 로직이 필요 


> BatchWriteItem
>> 한번 호출로 최대 25번의 PutItem and/or DeleteItem 을 수행한다
>> 최대 16MB 의 데이터 기록 가능 (항목마다 400KB 의 제한은 동일)
>> (이런거 유의) UpdateItem 은 불가하다
>> WCU 부족 따위로 Write 행위에 실패할 경우, "UnporcessedItems" 라는 것이 반환된다 :: 이 항목들에 대해서 다시 시도할 수 있다 (지수 백오프 / WCU 추가 등을 통해) 


> BatchGetItem
>> 1개 이상의 테이블로부터 Items 들을 반환받는다
>> 최대 100개의 Item 혹은 16MB of Data 를 반환받을 수 있다
>> RCU 부족 따위로 Read 에 실패한 경우, "UnprocessedKeys" 가 반환된다 :: 지수 백오프로 다시 시도하던지, RCU 를 추가하여 진행하던지 할 수 있다 


<PartiQL>


> 기본적인 API 들로 DB와의 상호작용이 가능하지만, 개발자/데이터 엔지니어로서 SQL 을 활용하고 싶을 수 있다. 그 때 사용한다
> DD 에 연동되는 SQL 언어이다.

 

 

Parti QL 은 DD 에서 사용할 수 있는 SQL 이다



> SELECT / INSERT / UPDATE / DELETE 작업 가능 (API 대신 SQL 을 사용) 
> JOIN 은 수행할 수 없다!!!! (API 의 범위 내에서만 가능)
> 이 Query 는 콘솔 / DD 용 NoSQL Workbench / DynamoDB API / AWS CLI / AWS SDK 등에서 실행할 수 있다 

>> 참고...  Partition Key 와 Primary Key 를 잘 구분하는게 좋은 시작일듯..? 자꾸 이해가 안됨...

다음에 볼 때는 둘의 차이를 잘 정리해볼 수 있도록 여기에

 


-----------------------------------


<325 DD API 실습> 


> 콘솔에서 테이블에 들어가면, 테이블 Scan 을 통해 전체 테이블을 로딩할 수 있다 (Scan API 를 콘솔에서 쓰는거임) 
Create Item 에서 하던 것도 PutItem API 였던 것
> 콘솔에서 특정 Item 을 누르고 (이 때 GetItem API 사용) 그 이후 Edit 을 하면, 특정 속성을 변경할 수 있는데, 이 때는 UpdateItem API 가 사용된다 

> 항목들을 일부 선택 후, Actions 에 Delete Item 을 누르면, 한번에 수행되는 Batch Delete Item 을 수행하는 것이다 
>> 따라서 콘솔에서 모든 항목을 선택후 Delete Item 을 누르면, Full Scan 후 Batch Delete 를 하는 것이다. 이것보단 Drop Table 을 통해 바로 DeleteTable 을 하는 것이 더 빠르다

 

 

Scan 과 Query 를 콘솔에서 수행할 수 있고, 필터링도 수행해보는 모습이다 (단, 배웠듯이 Filtering 은 클라 단에서 수행되는 것)



> Scan 은 배웠듯이 모든 Table 을 가져온다
>> Filter 을 할 수는 있지만, Client 측에서 진행하는 것이므로 속도나 성능, RCU 에는 영향을 주지 않는다 (DD 에서 하는게 아님)


> Query 도 할 수 있다 
>> 에시로, sort key 를 "Greater than or equal to 2021-11' 로 하면, 그 날짜 이상인 날짜 (지난 날짜) 들만 반환된다 
>> Query 는 ONLY user_id / post_ts 즉 Partition Key 와 Sort Key 에 대해서만 사용할 수 있다
>> 속성들에 대해서 필터링은 가능하지만, 이 역시 Client Side 에서 진행 (DD 에서 하는게 아님)


-----------------------------------


<326 조건부 쓰기 > 


> 조건부 쓰기는 Write 를 위한 영역으로, 배웠듯이 PutItem/ UpdateItem / DeleteItem / BatchWriteItem 이 있다 
> 수정할 항목 결정할 조건 표현식을 지정할 수 있다
>> attribute_exists , attribute_not_exists, attribute_type (속성이 있는지, 없는지, 원하는 유형인지) 
>> contains, begins_with (String 비교)
>> IN 조건 (n 가지 조건 제시 가능) , BETWEEN 조건 (범위 제시 가능) 
>> size (string length) 

> 이전에 봤던 "Fiter Expression" 은 "읽기 쿼리" 의 결과를 필터링 한다
> "Condition Expression" 은 "쓰기 작업" 에만 적용된다 


<예시>

 

 

해당 API 를 날리면, value.json 의 값들을 참조하여, 우측 칼럼을 찾아서 조건에 충족하면 변경하게 된다



> ProductCatalog 란 테이블 항목 업데이트 한다고 하는데, 이 때 "가격이 특정 값을 넘을 경우 (Write 에 대한 조건부), 현재 가격을 할인된 가격으로 조정 (Write Action 부)"
> Condition 값이 되는 건 JPQL 처럼 :discount, :limit 으로 표현하는데, value.json 을 통해 전달한다
>> 위 상황에서는 현재 가격이 제시 가격인 500 을 초과했기 때문에, 할인 가격 150 이 까여 조정됨
>> 이걸 또 쿼리 하면, 적용되지 않음 (500 을 넘지 않기 때문) 


<attribute 에 대한 조건에 대해 더 살펴보기>

attribute_not_exists : 그 속성이 없을 때만 업데이트 해줌 (ex : 특정 item 삭제 or Batch 삭제 할 때, 가격 속성이 존재하지 않으면 삭제해라) 
attribute_exists : 반대로 동작 (존재할 경우 업데이트) - (ex : deletable 이란 속성이 존재할 경우, 삭제해라)


<위 조건에 대한 좋은 Use Case>

> 데이터를 쓸 때, attribute_not_exists(partition_key) 를 전달하면, 해당 PK 가 없을 때 작성을 보장한다 (기존 값 덮어씌워지는걸 방지
> partition_key / sort_key 를 같이 PK 로 활용 중이라면, AND 조건으로 둘을 attribute_not_exists 로 묶는다 

   
<그밖에>

 

 

Conditional Write 조건에 IN, BETWEEN 절을 사용할 수 있고, String 에 대한 begins_with, contains 등도 사용할 수 있다



> 그림 처럼 IN 절, Between 절도 똑같이 value.json 에 값을 동적으로 명세해서 조건부 쓰기를 할 수 있다 (DELETE 도 조건을 걸 수 있는 거임)
begins_with, contains 도 사용예시를 보여주는데, 걍 똑같음. value.json 에 원하는 값을 넣음 

 

 

----------------------------

 

 

<327강 DD 인덱스 (GSI + LSI)>


Index 들은 속성들을 기반으로 Query 기능을 더 다양하게 날리기 위해 사용 (당연히 전체 Scan 한다음에 Filter 하는게 아니니, 더 빠르다는 의미도 있음) 
LSI 는 Sort Key 를 바꾸는 것, GSI 는 Partition Key 를 바꾸는 것


<Local Secondary Index (LSI)>

> Partition Key 는 동일하지만, additional Sort Key 를 갖는 것을 말한다
> 하나의 Scalar attribute 를 갖는다 (String, Number, Binary) 
> 테이블당 5개의 LSI 를 가질 수 있으며, 테이블 생성시에 지정해야 한다 (추후 추가 불가) - 설계가 필요한 이유
> 테이블의 일부 혹은 전체 속성을 포함시켜 만들 수 있다 = Attribute Projections (KEYS_ONLY, INCLUDE, ALL) 

>> 참고로 여기서 이 ALL 이라는 부분 Projection 잘 이해를 못함..! (실습 후 더 헷갈림)

 

 

LSI 는 속성 중 하나를 지정하여 추가적인 Sort Key 로 만들 수 있다



> 위와 같은 상황에서, User_ID 와 Game_ID 에 대해 편하게 쿼리를 할 수 있다 (PK + SK 조합이므로) 
> User_ID + Game_TS 에 대해서는 쿼리를 할 수 없다 (스캔 후 필터링 해야함) (이렇게 원하는 쿼리 조합이 생길 수도 있다)
> 따라서, Game_TS 기반 쿼리를 추가하려면, LSI 를 생성하고, Game_TS 속성에 대해 LSI 를 정의해야 한다 
>> 그럼 다음과 같은 쿼리가 가능하다: "2020~2021 사이에 유저가 실행한 게임들을 반환해줘" 


<Global Secondary Index (GSI)> 

Alternative Primary Key (HASH or HASH+Range) 를 갖게 해준다 (Partition Key 는 Primary Key 의 일부라고 하는데, 정확히는 모르겠음) 
> (기존에) non-key 인 attribute 를 가지고 쿼리를 빠르게 하고 싶을 때 사용 
> 역시 attribute-projection 으로 정의된다
> 이 인덱스는 새로운 테이블의 관점으로 보기도 하기 때문에, 이 인덱스에 대해서는 RCU / WCU 를 프로비저닝 해야 한다  (인덱스 다움)
> LSI 와는 다르게 테이블 생성 이후에도 추가/생성이 가능


GSI 는 PK 를 특정 속성과 대체하여 종속된 테이블(?) 을 만들어 준다



> 두가지 키와 한가지 속성이 있다
> User_ID 기반 쿼리가 가능, but Game_ID 에 대해서는 쿼리를 할 수 없다
> 따라서 Game_ID 에 대해 GSI 를 정의한다

> RDB 처럼 Index Table 을 새로 구성해주는 것, GSI 에서는 Partition Key 가 Game ID 가 된다 (Sort Key 는 그대로, 자신이 PK 로 사용한 속성과 자리를 바꾼다) 
>> 완전히 새로운 쿼리가 가능해짐  


<Indexes and Throttling> 

> GSI 
>> GSI 에서 write 가 Throttling 되면, 메인 테이블 역시 Throttle 된다 (매우 중요 사항, 시험에 자주 출제) 
>> 메인 테이블의 WCU 에 아무 문제가 없어도 발생 (따라서 GSI PK 를 신중하게, 그리고 WCU 용량도 신중하게 설정해야 한다) 

> LSI
>> 메인 테이블의 WCU / RCU 를 사용한다고 보면 된다 (딱히 신경 쓸 것 없음) 


----------------------------------------


<328강 DD Indexes 실습>


사용자 설정으로 테이블을 생성하다보면 보조 인덱스 설정을 만날 수 있다



> 설정에 Secondary Indexes 를 지정할 수 있다 (LSI 는 오직 이 때만 생성할 수 있다) 
> Local Index 로 다른 Sort Key 를 넣을 수 있다 (예시에는 game_id 로 했다) - Project 하고 싶은 Attribute 형태를 선택, ALL 로 함 

 

 

LSI 로 쿼리 시도시 Sort Key 가 내가 지정한 속성으로 바뀐다



> Table 쿼리로 이동
>> 쿼리할 항목을 "현재 테이블" 혹은 "Index" 중 하나 선택할 수 있음 
>> 기존 테이블 할 때랑 똑같이 Partition Key 로 조회를 하지만, 다른 Sort Key 로 Query 를 더 날릴 수 있음 


테이블 내 인덱스 탭에서 GSI 를 생성할 수 있ㄷ



> GSI  는 이 시점에도 생성할 수 있다
>> Partition Key 를 game_id 로 하고, Sort Key 는 그대로 game_ts 로 하는 Index 테이블을 만들었다
>> 이 때, WCU, RCU 용량도 프로비전 해야 함. Base Table 설정할 때랑 똑같, Auto Scaling 도 가능 (완전히 다른 WCU , RCU 라고 생각하면 됨, 완전히 다른 테이블이라고 생각!!)
>> 생성 후에 Query 로 가면, 이 Index 테이블로 query 가 가능하며, 새로운 Partition Key 로 쿼리를 날릴 수 있다!!


----------------------------------------


<329강 PartiQL 과 실습>

> DD 제어를 위해 SQL 형식의 구문을 사용 (INSERT/ UPDATE/ DELETE / SELECT 구문 가능)
> 필요시 Batch 동작도 지원한다 

 

 

SQL 에 익숙한 사용자들을 위해 콘솔에서 SQL 을 통한 쿼리 확인도 지원해준다



> 콘솔에서 PartiQL Editor 로 이동
>> 특정 테이블에 대해서 Scan Table 을 누르면, Query 가 SELECT * FROM "그테이블" 로 표시된다 
>> 다음과 같은 Query 를 실행해본다 
>>> SELECT * FROM {table_name} WHERE "{partiton_key_name}"= '{some_val}'; > 쿼리가 잘 돌아간다
>> 인덱스에 대해서 스캔도 가능하다 (인덱스 테이블 구성으로 테이블을 로딩함) 
> 이건 내 생각인데 이 콘솔에서만 사용 가능한 거인 듯! 


----------------------------------------------


<330강 Optimistic Lock - 시험에 나올 예정!>

> DD 에는 조건부 Write 이 있었음 (내 예상이 맞았당! 동시성 처리에 유용하다고 했었) 
> Op Lock :: update / delete 하려는 대상이 내가 최근 바라본 값 이후에 바뀌지 않았음을 보장하는 것을 말함 (기본 개념)
> 각 항목들은 version_number 로 동작하는 attribute 을 가지고 있는데, 이를 활용한다 (내재된 속성인듯)

 

조건부 설정을 통해 낙관적 락을 사용할 수 있다

 


> 위 그림과 같이 User_ID, First_name, Version 이 있다고 해보자
> 두개의 클라이언트가 동시에 이 item 을 업데이트 하려고 한다
> 1,2 번이 각자 위와 같은 요청을 던졌음
>> 두 요청 중 하나는 반영이 되지만, 하나는 반영이 안된다 (Version 이 일치하지 않기 때문임) 
>> 조건부 요청을 던졌기 때문임!! version 을 명시함으로써 Optimistick Lock 이 가능한 것! (변경을 시도하는 도중 대상 데이터의 락을 보장)
>> 한 Client 는 올바른 데이터가 아니다라는 에러를 받게 된다 (Get 을 다시 받아보고 재시도 하든가 해야함) 



-------------------------------------

 


<331강 DD DAX (DD Accelerator)>

 

 

> DD 를 위한 완전 관리 / 높은 고가용성의 인메모리 캐시이다 
> 가장 조회율이 높아서 인메모리에 저장 -> 캐싱에서 읽으므로 매우 낮은 latency 보장
> 앱 단에서 로직 수정이 불필요하다 (DD API 와 호환) 
> DAX 는 Hot Key 문제를 해결한다 (특정 키, 특정 항목에 대한 지나친 read 가 발생 -> RCU Throttle 위험) 

 

DAX 는 DD 를 위한 인메모리 캐시이다

 

 


> 위 그림과 같이 DAX 클러스터를 만들면, 앱이 클러스터와 직접 상호작용
> DAX 는 DD 로부터 가져옴. 이 때부터 일부 캐싱되기 시작하고, 기본적인 TTL 은 5분이다
> DAX 는 클러스터성으로 NODE 단위로 구성된다. 최대 10개의 노드로 구성할 수 있다 
> 다중 AZ 에 배포시 최소 3개의 노드를 구성하여 운영하는 것을 권장한다 (권장이 아니라 최소 3개 노드 구성 필요)
> 미사용시에는 암호화가 되므로, 안전하다 (KMS 암호화, VPC 보안, IAM, CloudTrail 통합 등등으로)

 

 

<DAX vs Elasticache>

 

DD 및 Elastic Cache 와의 동작성

 


> 결합 사용도 가능하지만, 그래도 어떤걸 사용하는게 적합한 상황인지 잘 물어봄
> DAX 로는  개별 객체에 대한 캐시 / 특정 Query & Scan 에 대한 캐시 를 갖게 된다 
>> 정말 간단하고 간편 : Simple Types of Queries (Object, Query, Scan  을 캐싱함) 

> 스캔 사용, SUM 을 사용, 데이터 필터링 등등 계산적으로 비용이 많이 드는 로직측면을 수행한다면, 매번 하기가 번거로움
>> 앱이 방금 수행한 결과를 저장할 수 있는게 ElastiCache! (그리고 그 결과를 이캐에서 가져올 수 있다, 앱이 동일 로직을 또 하지 않아도 됨) 
> 둘다 같이 사용할 수 있다

 

 

----------------------------------------------


<332강 DD DAX 실습>

 

 

> DD 로 가서 좌측 탭에 DAX 조회 가능, 무료 아니라서 그냥 구경함 (찾지도 못하겠음)
> Node Family 
>> t-type : 버스팅 - 낮은 처리량일 경우 권장  
>> r-type : 메모리 - always ready 해야 하는 경웈
> Cluster Size 에서 Niode 갯수 3 부터 다중 AZ 설정 제공

> 특정 서브넷 그룹 정의 필요 (특정 VPC 내부 특정 서브넷들을 골라야 함) 
> Security Group 에 대해 8111, encrypted 경우 9111 포트 개방이 필요하다

> IAM Permission 설정도 해야 함 > DD 에 접근할 수 있도록  (DAX 에서 DD 에게 접근)
>> 콘솔에서 직접 설정 가능 - read/write 다 가능한지 read 만 가능한지 // 모든 테이블에 접근 가능한지 특정 테이블들에만 가능한지 등등

> Parameter Group 으로 TTL 을 얼마나 줄지 설정 가능하다 

> 생성하고 나면 DAX 의 클러스터 엔드포인트가 제공된다
> 이 엔드포인트로 앱이 활용할 DAX 기능임 
> Node 갯수는 변경 가능하지만 Node Type 은 변경 불가 
> 캐시가 잘 활용되는지 모니터로 확인할 수 있다

 

 

----------------------------------------------


<333강 DD Stream>

 

 

> 테이블에서 발생하는 Item-Level Modification (Create/Update/Delete) 들의 정렬된 목록이다

> 항목을 추가 / 수정 / 삭제할 때마다 스트림에서 그 사항을 확인할 수 있다 (시간에 따른 테이블의 변경 사항들을 보여줌

>> Stream 들이 KDS 로 보내져서 작업을 할 수 있다

>> Lambda 와 같은 곳에서 Stream 으로부터 직접적으로 읽을 수 있다

> 보존 주기는 24시간으로, 더 보존하려면 꼭 KDS 같은 곳에 보존해야 함

 

> Use Case (실시간 대응이 필요)

>> 변경사항에 실시간으로 대응 (welcome email 보내기 등) 

>> 분석하거나 DD 에 파생 테이블 (derivative table) 을 제어해야 할 때

>> ElasticSearch 로 데이터를 보내기 (인덱싱 하거나 DD 테이블에 검색 기능 구현 등등)

>> Global Table 혹은 Cross-Region Replication 구현 

 

 

DD Stream Flow 의 예시들

 

 

> 변경이 DD Stream 에 드장, KDS 로 수신될 수 있다

>> KDS 에서는 원래 용도처럼 쓸 수 있음

 

> 다른 EC2 에서 러닝하는 KCL 앱 (Kinesis Client Lib App) 이나 람다에서도 수신할 수 있다

>> 다양한 짓을 할 수 있다

 

 

< Stream 의 기능 >

 

> Stream 에 표시할 정보들을 선택할 수 있음

>> KEYS_ONLY : 키 속성들만 보여진다

>> NEW_IMAGE : 수정된 이후 item 전체가 보여진다

>> OLD_IMAGE : 해당 Item 의 수정 이전의 전체 모습이 보여진다 

>> NEW_AND_OLD_IMAGES : 둘다

 

> KDS 처럼, DD Stream 역시 Shard 들로 구성되어 있다 (KCL 앱이 KDS 와 DDS 모두에서 동작 가능한 이유)

> DD Stream 은 샤드들을 프로비저닝 할 필요가 없다. AWS 에서 자동으로 해줌

> Stream 을 활성화하면 지금까지 있던 것들을 소급해서 보내주지 않는다 (not retroactively populated). 활성화 이후만 보내줌. 

 

 

<Stream 과 람다의 연동>

 

 

Stream 과 람다는 ESM 기반으로 동작한다

 

 

> 람다와 연동시키려면 ESM 이 필요하다 (ESM 이 DDS 로부터 Polling 을 한다)

> 람다가 적합한 IAM Role (Execution Role) 을 가지고 있는지 확인한다 (DD 로 부터 당겨오기 위한) 

> 이후 ESM 은 DDS 로 부터 Batch 형태로 데이터를 받는다

> 이후 ESM 은 이 배치 Record 를 기반으로 람다 함수를 동기적으로 호출한다

 

 

----------------------------------------------


<334강 DD Stream 실습>

 

 

DD Stream 을 켤 수 있다

 

 

> 원하는 테이블로 이동하여 DDStream 활성화를 찾는다

> 아까 설명된대로 보기 유형을 선택할 수 있다. 스트림 켜기로 마무리 하면 "Trigger" 란 곳이 생성됨

> Trigger 란 이 Stream 이 어떤 행위를 할지 연동하는 부분이다

 

 

Trigger 를 생성할 수 있다

 

 

> 람다 함수를 지정할 수 있는데, DDS 가 업데이트 될 때마다 trigger 하는 람다 함수를 정의할 수 있다

> 새로 생성 (blue print 로 람다 함수, process dynamo db in python 을 찾아서 생성) 

 

 

import json

def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))
    for record in event['Records']:
        print(record['eventID'])
        print(record['eventName'])
        print("DynamoDB Record: " + json.dumps(record['dynamodb'], indent=2))
    return 'Successfully processed {} records.'.format(len(event['Records']))

 

 

> 위와 같은 람다 함수를 자동으로 생성해줌

> Dynamo DB 블루프린트를 쓰기 때문에, 하단에 Dynamo DB Trigger 란 공간을 찾을 수 있음

 

 

이 블루프린트를 사용시 바로 dynamo db 를 지정해줄 수 있다

 

 

> 이 트리거는 아마 100% 권한을 끼어 넣어주기 위해 설정하는 부분일 것임 (아니넹 ㅋㅋ)

> ExecutionRole 로 이동해서 DDB 로부터 읽는 권한인 "LambdaDBReadOnlyAccess" 정책을 추가한다

> 다시 트리거로 돌아가 이 람다를 지정해주고 생성을 완료할 수 있다. 그럼 최종적으로 다음과 같이 트리거가 추가되고 구성이된 모습을 확인할 수 있다 

 

 

아까 람다 생성시 한 설정들까지 다 구성이 된 모습을 볼 수 있다

 

 

> 이제 테스트를 해볼 수 있다

> Item 하나를 생성해봄 (john_1234 라는 PK 를 가진)

 

 

로그 그룹에 바로 생성에 대한 기록이 표시된 것을 확인할 수 있다

 

 

> 정상적으로 람다 함수가 invoke 되었고, Log 에 전달되는 모습을 볼 수 있다

> 마찬가지로 Edit 을 해도 Logging 이 되는 모습까지 확인할 수 있다

>> 3가지 행위를 함 ==> CW Log 로 가보면 로그 스트림에 3가지 행위가 모두 로깅되었음을 확인할 수 있다!

>> 설정을 OLD_NEW_IMAGE 모두로 했기 때문에, "Old Image" 라는 key 값과  "New Image" 라는 키값으로 객체 정보들이 들어와서 기록되는 것을 확인할 수 있다!



----------------------------------------------


<335강 TTL>

 

 

> 특정 시간 이후에 해당 ITEM 을 삭제하도록 해줌 
> WCU 를 소비하지 않으므로 추가적인 비용도 없다
> TTL 속성은 "Number" 라는 데이터 타입에, "Unix Epoch timestamp" 형식의 값을 가져야 한다 
> 즉시 삭제는 아니고, 만료후 48시간 이내에 삭제된다

<그림 참조>

> 테이블에 ExpTime 이란 이름의 속성을 추가하고, 이걸 TTL 속성으로 지정
> DD 에는 Expiration Process 가 기동중인데, 이 Process 가 테이블을 스캔하고 TTL 속성이 지났으면 표시를 한다 
> DD 에는 또한 Deletion Process 가 기동중인데, 얘가 다시 테이블을 스캔하면서 해당 아이템들을 삭제한다

> Expired 아이템들은 삭제되기 전까지 계속 read/query/scan 에 등장한다 (원하지 않으면 클라측 필터링을 해야 함)
> 메인 테이블에서 삭제시, 당연히 LSI, GSI 도 삭제된다
> 삭제처리가 된 후에는 DD Stream 에 들어가게 된다 (복구 지원)
> 현재 아이템만을 유지하여 용량을 보존하거나, 서비스 운영 규제 의무를 지키려는 경우에 사용된다 
>> Session Data 는 TTL 에 좋은 예시 (24시간 후 해당 세션 삭제 등) (세션을 DB 에 저장함?)


<실습>

> user_id 를 갖는 테이블 하나를 만듬
> 데이터 추가함, 추가할 때 속성으로 expire_on 이라는 속성을 추가해줌 (Number Type) 
>> exp date 를 해야 하니, epoch converter 를 구글에 입력후 5분 후 이런식으로 입력해서 epoch timestamp 를 입력해준다
> 또다른 item 은 expire_on 을 1시간 뒤로 설정함 
> 이제 TTL 을 정의하기 위해 추가 구성으로 이동, 아래쪽에 TTL 활성화 후 속성을 expire_on 으로 입력해줌
> 확인할 수 있는 기능이 있는데 (Preview), 10 분 뒤에는 어떤 데이터가 삭제되는가? 이런걸 볼 수 있음 (시뮬레이션 기능임)

 

 

----------------------------------------------


<336강 CLI>

 

 

<다양한 옵션들>

> --project-expression : 한개 이상의 속성을 지정해서 item 전체가 아닌 일부 속성만 받을 수 있음 (SELECT 절) 
> --filter-expression : 조건을 지정해서 너에게 반환되는 (DD가 해주는게 아님) 데이터를 필터링
>  Paginatiion Option 들도 있음 (커서 기반으로만 지원해주는 느낌)
>> --page-size : 대용량 호출을 가능하게 해주는 것 (Pagination 이 맞음? ㅋㅋ). 1000개의 item 을 retrive 해야 함 (Full List of Items). 이 때, 이걸 한번에 다 호출하면 TO 이 날 수 있다. 이 때 --page-size = 100 으로 하면, 100개를 가져오는 요청 10개로 분할되어 백그라운드에서 이루어져, 모든 호출을 성공시킨다
>> --max-item :  결과에서 제한된 숫자의 아이템을 반환. NextToken 값을 반환하여, --starting-token 에 명시할 수 있다 (이게 페이지내이션 ㅇㅇ)
>> --starting-token : 지난 NextToken 을 명시하여 그 다음의 item 들을 가져올 수 있다


<실습>

> CLI 를 연다. 
> 1. Projection Expression
$ aws dynamodb scan --table-name {테이블명} --projection-expression "user_id, content"
> Full Scan 을 때려서, 테이블 전체를 반환한다. 이 때, user_id, content 속성만 반환한다 
> JSON 형태로 아이템들이 반환되는 것을 볼 수 있음

> 2. Filter Expression
$ aws dynamodb scan --table-name {테이블명} --filter-expression "user_id = :u" --expression-attribute-values '{":u":{"S":"john123"}}'
> expression-attribute-values 에는 지난번에는 values.json 을 명시해서 넣어줬는데, 이번엔 String 으로 JSON 을 넣어줌
> WHERE 절처럼 filtering 을 해줄 수 있다. "S" 란 String 을 명시한다. String 값으로 john123 을 넣으라는 것.
> user_id 가 john123 인 것들만 반환해주는걸 볼 수 있다
>> 모두 클라이언트 사이드에서 해주는 필터링
>> 이 user_id 가 Partition Key 이기 때문에 Server Side 에서 가능하다 - 쿼리 기능을 사용할 수 있기 때문 

> 3. Page Size
$ aws dynamodb scan --table-name {테이블명}
> Full Scan 으로 데이터를 반환해준다

$ aws dynamodb scan --table-name {테이블명} --max-items {소분사이즈}
> 여러개의 API 를 날려서 TO 을 방지해줌
> 위와 결과는 똑같음 

> 4. 하나의 limit 절 걸기
$ aws dynamodb scan --table-name {테이블명} --max-items {limit 사이즈}
> 1 로 하니까 위 요청에서 제일 먼저 찾은 하나만 반환. 대신, NextToken 값을 반환하는 것을 알 수 있음

> 5. Next  Item Fetch 하기, limit 절도 같이 하기
$ aws dynamodb scan --table-name {테이블명} --max-items 2 --starting-token {위에서 토큰}
> 토큰값을 넣어서 위 요청 이후 다음 item 두개를 반환받는다 
> 마지막에 도달하면 NextToken 값이 더이상 오지 않는다. 그럼 그 때 아 끝났구나 하면 됨

 

 

----------------------------------------------


<337강 Transaction>

 

 

> All or Nothing 작업 (add / update / delete) 를 하기 위함

> Atomicity, Consistency, Isolation, Durability (ACID) 보장하는 작업

> 두가지 모드로 적용된다

>> Read Mode : Eventual Consistency, Strong Consistency, Transactional (세가지 모드 있음)

>> Write Mode : Standard, Transactional (두가지) 

 

> 평상시 소모되는 WCU, RCU 보다 2배를 소모한다

>> 한 작업당 Prepare & Commit 두가지 operation 을 모든 아이템마다 수행하기 때문

 

> 한 트랜잭션의 기준 작업 (25가지의 unique 한 item 까지 가능 or 4MB of Data 까지 가능) 

>> TransactGetItems - 한가지 혹은 그 이상의 GetItem 작업 수행

>> TransactWriteItems - Put / Update / DeleteItem 작업을 한가지 혹은 그 이상으로 수행

>> 여기서 말하는 "그 이상" 이 모두 수행됨을 보장하는 것

 

> Use Case : 일관성이 반드시 필요한 경우  (금융권, 멀티플레이 게임 등) 

 

 

DD 에서의 트랜젝션

 

 

> Account 와 계정의 돈에 대한 테이블, 은행 내부에서 이루어지는 모든 입출금 데이터를 보관하는 BankTransaction 두개의 테이블 있음

> 앱은 하나의 트랜젝션을 트리거하는데, 이 때 Account 에는 UpdateItem API 가, BankTx 에는 PutItem API 가 호출된다

> 하나의 트랜젝션 작업이기 때문에, 두 API 가 반드시 같이 수행이 완료되거나, 중간에 에러나 오류시 중단하여 none 이 수행된다

> 예시와 같은 금융권에서는 이정도의 Tx 보장이 반드시 필요

 

 

<Transaction 을 위한 Capacity Computation - 매우매우 중요 - 맨 위에 했던 기본 계산 복습도 중요!>

 

> Ex: 초당 3 번의 Transaction Write 를 수행하는데, item size 는 5KB 이다. WCU 는 얼마나 필요한가?

>> 1WCU 는 1KB 의 데이터를 초당 1개 쓰는 것이므로, 5KB 면 5WCU 필요, 3번이면 *3 해야 해서 15WCU 필요 

>> 이 때 Transaction 의 대가로 *2 까지 하면 됨 = 30WCU

>> 평소 하던 계산에서 *2 하면 된다

 

> Ex: 초당 5번의 Tx Read, item size 5KB. RCU 는? (Strong consistent 그거인듯??)

>> 1RCU 는 4KB의 데이터를 초당 1개 읽는 것 >> 5KB 읽으려면 8KB 읽는것으로 취급

>> 2RCU 가 8KB 의 데이터를 초당 1개 읽는 것

>> 5번 하니까 10

>> Tx 보장 *2 하니가 20RCU 가 정답 (나는 이렇게 비례해서 풀어나가는게 더 좋은듯)

>> 풀이 (5) * (8/4) * 2 = 20 (이걸로 검산하고) 

 

 

----------------------------------------------


<338강 DD Session State Cache - 세션 상태 저장소들 비교>

 

 

> DD 는 세션 상태를 저장하는 용도로도 사용할 수 있다 (웹에서 활용 가능) - 사용자 로그인 상태 등을 웹 앱에 공유 가능

 

> ElastiCache 에서도 본 적 있음 (차이 알아야 함)

>> 둘다 key / value 저장소이다

>> 그 차이: EC 는 완전히 인 메모리, DD 는 완전히 서버리스 (자동 스케일링 등등을 지원) 

 

> 디스크에도 세션 상태를 저장할 수 있다 (EFS)

>> EFS 는 네트워크 드라이브로서 EC2 인스턴스에 부착된다

>> 차이점: EFS 는 파일 시스템, DD 는 데이터 베이스

 

> EBS 와 Ec2 인스턴스 스토어는??

>> 이들은 로컬 캐싱으로만 사용 가능하다 (shared caching 은 불가) 

>> 이들은 오직 하나의 EC2 인스턴스에만 부착될 수 있기 때문이다 (인스터들간 공유할 수 없음)

 

> S3 는?

>> 세션 상태 저장으로 물론 쓸 수는 있지만, 용도가 적합하지 않다

>> 레이턴시가 큰 편이고, 큰 파일드를 위해서 사용하는 거임

 

> 세션 상태 저장소로 가장 적합한 애들은 DD 와 ElastiCache 이지만, 많이 봐줘서 EFS 까지 가능한 정도

> 이 안에서는 인메모리를 원하는지, 오토 스케일링 같은 지원을 원하는지 파악 필요

 

 

----------------------------------------------


<339강 DD 파티셔닝 전략>

 

 

<Write Sharding>

 

> 투표를 할 수 있는 기능이 있는 앱이 있다고 해보고, A, B 두 후보가 있다고 해보자

> Partition Key 를 후보자 ID 로 할 경우, 모든 결과에 대한 Item 이 두 후보 Item 에게로만 집중되어 Hot Partition, Hot Key 이슈가 발생한다

>> 하지만 후보자 ID 같은 경우는 매우 유력한 Partition Key 임이 확실할 수 있음

 

 

Partition Key 에 Suffix 를 추가하는 전략을 사용할 수 있다

 

 

> 더 분배를 효과적으로 하기 위해, Partition Key Value 에 Suffix 를 추가할 수 있다

> Random Suffix 를 추가해서 Partition Key 를 저장한다

>> 이전에는 Sort Key 만 다른채 동일한 Partition Key 로 Voting 상황을 저장해서 Hot Key 문제 발발

>> 현재는 훨씬 많은 Partition Key 로 분배되므로 데이터 셋들이 완전하게 쓰기 및 읽기를 할 수 있다

 

> 보통 Suffix 에는 다음 두 가지 방법을 사용한다

>> Sharding with Random Suffix

>> Sharding with Calculated Suffix with Hash Algo

>> 잘 분배된 파티션 키를 얻는 것이 목적

 

 

----------------------------------------------


<340강 DD 의 그밖의 Write Types>

 

 

DD Write 중 Concurrent Write 와 Conditional Write

 

 

> Concurrent Write

> 둘다 성공은 하지만 한 명은 실패하게 된다 (결과적으로는 덮어씌워지기 때문)

> Conditional Write

> Concurrent Write 의 해결법으로 (위에서 나왔던거랑 같은건가?) 특정 조건을 만족할 시 업데이트를 하라는 것

> 위 예시와 같은 조건부 Write 를 요청하면, 두번재 요청은 Fail 하게 된다 (쓰기를 허용하지 않음. 적절한 Exception 을 발생 시킬 수 있다)

> 위에서 살펴봤던 Optimistic Lock 부분과 동일한 것 ㅇㅇ

 

 

DD Write 중 Atomic Write 와 Batch Write

 

 

> Atomic Write

> 마찬가지로 위 그림과 같이 요청하는데, 두 요청 모두 성공시키는데, 전체 값을 3만큼 증가시키는 방식. 

> 근데 이건 "증가시켜라" 이기 때문인데, Concurrent Write 같은 상황이면? 그냥 이런 상황에 쓰는 방식이다 이건가?

> Batch Write

> 여러 Item 들을 한 요청으로 Write / Update 을 한 번에 하는 것

 

> 그냥 Dynamo DB 에서 발생할 수 있는 모든 Write 들의 종류에 대해 복습 / 학습한 부분임!

 

 

----------------------------------------------


<341강  DD & S3 의 연계>

 

 

Dynamo DB 에서 Large 한 이미지를 띄워주는 방법

 

 

> DD 테이블에는 최대 400KB 의 데이터까지만 저장 가능, 이미지 / 동영상은 당연히 불가함

> 1. 앱에서 S3 버킷에 업로드하여 Image Key 를 반환받는다

> 2. 앱에서 이런 metadata 들을 구성하여 테이블에 저장한다 (이미지 URL 포함, S3 에 대한 포인터 역할)

> 3. 앱에서 View 해줄 때는 역으로 진행한다

>> 그냥 이미지 저장할 때 당연하게 일반적으로 사용되는 방식임 ㅇㅇ

 

 

< DD 를 S3 객체 메타데이터 Indexing 으로 활용 > 

 

 

설계안

 

 

> DD 를 way to index S3 object's metadata 로 사용! (그냥 결과는 위랑 같은건데 람다를 활용해서 구성하는 방식이 다른 거인듯)

> 앱에서 S3 저장, 저장시 람다 trigger, 람다에선 Database 에 metadata 저장

>> 이렇게 하는 이유: DD 테이블을 활용한 쿼리를 구축하는 것이 S3 버킷을 활용하는 것보다 훨씬 효율적이기 때문

>> S3 는 스캔 및 쿼리의 대상이 아니기 때문 (걍 당연한 소리 ㅇㅇ) 

> 아~ 근데 위에 Use Case 는 앱에서 View 해주는 목적이 좀 큰 것 같고 (이미지 URL), 여기서는 메타데이터를 많이 저장하여 DD 를 활용해서 쿼리 하는 거에 더 중점을 두는 것 같음

 

 

----------------------------------------------


<342강  DD의 또다른 작업>

 

 

> 테이블 정리 (Table Cleanup) = 트렁케이트 부분?

>> Option 1 : 전체 Scan + Delete Each Item 

>>> 매우 느리고, RCU / WCU 를 많이 소모하기 때문에 비싸짐 (Delete Each Item 부분 때문) 

>> Option 2 : Drop Table + Recreate Table

>>> 훨신 빠르고 간단함. 단, 기존 설정과 그대로 테이블을 잘 생성할 수 있다는 가정 하에. 

 

 

DD 복제시 등장할 수 있는 Datapipeline 리소스

 

 

> 테이블 복제 (Copying DD Table) = 계정별, 리전별, AZ 별 등등

>> Option 1 : AWS Data Pipeline 을 사용

>>> Datapipeline 은 시험 내 이 부분에서만 유일하게 나온다

>>> 복제를 하려고 할시, EMR Cluster 란걸 실행해서 Scan 을 통해 DD 를 읽고 S3 에 저장

>>> 그 후에 다시 S3 로 부터 읽어오고 새로운 DD 테이블에 Insert 를 진행

 

>> Option 2 : DD 백업 실시 및 새로운 테이블에 복원시키기 (좀 걸림)

>>> 시간은 좀 더 걸리지만 훨씬 효율적, 다른 외부 서비스를 요구하지 않음

 

>> Option 3 : 직접 스캔으로 받아온 다음에 PutItem or BatchWriteItem 을 사용해서 스스로 진행한다

>>> 스스로 프로그램을 개발해야 함. 

 

 

----------------------------------------------


<343강  DD의 보안 및 기타 사항>

 

 

> Security

>> VPC Endpoint 가 있어 인터넷 없이도 접근할 수 있고, VPC 내로 트래픽이 한정된다 (잘 모르겠 ㅎ) 

>> IAM 으로 접근을 완벽히 제어한다 (AWS 내에서 DB 로 훌륭한 선택) 

>> 평상시에는 AWS KMS 로 암호화 되고, 전송 중에는 SSL/TLS 로 암호화를 AWS 내에서 지원한다

 

> Backup and Restore 기능

>> PITR (Point-in-time Recovery) : RDS 와 동일, 성능에 영향을 주지 않는다

>> 일박 백업으로 복원할 수 있다고 함

 

> Global Table

>>  Multi-Region, multi-active(?), fully replicated, high performance 인 테이블을 말한다

>> DD Stream 활성화 필요

 

> DD Local (시뮬레이션)

>> 웹 서비스 없이도 앱을 개발/테스트 할 수 있는 로컬 DB 임 (로컬에서 개발시 사용하는 MySQL 같은거) 

 

> AWS DMS (Database Migration Service)

>> Dynamo DB Export & Import 를 지원해준다 (MongoDB, MySQL, Oracle 등등) 

 

 

< Fine Grain Access : 사용자들이 직접적으로 DD 와 교류> 

 

 

Client 측 직접 접근을 지원하고 싶을시 Identity Providers 들을 사용할 수 있다

 

 

> Client (앱 포함) 측에서 DD 에 직접 접근하려는데, AWS 리소스가 아니니까 IAM 을 사용할 수 없다 (매우 비효율적 & 위험하다고 함) 

> Identity Provider 를 사용할 수 있다 (우측 그림) 

> 유저들이 이 IP 들을 사용해서 로그인을 하면 일시적인 자격증명을 얻을 수 있다 (일시적이며 훨씬 안전)

>> 단, 자신들 (Client) 이 소유한 데이터와 DD 만의 상호 작용이 이루어질 수 있도록 해야 한다 (보안)

>> 이를 Fine-Grained Access Control 이라고 한다는 듯

 

 

< Fine-Grained Access Control >

 

IP들을 활용한 로그인 이후 얻게 되는 IAM Role 예제

 

 

> IP들을 통해 일시 접근 권한을 얻으면 IAM Role 이 생성된다고 함?? (??)

> IAM 역할은 위와 같이 Condition 이 붙는데, 이 Condition 에서는 이 "User" (Client 측) 이 할 수 있는 일들을 명시한다

>> 위 예시처럼 할 수 있는 동작 (GetItem, BatchGet, Query, Put, Update, DeleteITem 등) 을 명시

>> 특정 테이블인 MyTable 에 대해서만 이를 허락한다

>> LeadingKeys 가 Dynamo DB 의 것(?) 이며, 특정 유저에 대한 것이므로 Runtime 시 주입될 cognito-identity 로 이루어져야 한다고 함... 시발 뭔소리야

>>> LK 는 기본 키 값(Primary Key) 을 기반으로 해서 row-level access 만을 허용하는 것 (전체를 허용하지 않는듯!! 이부분이 중요한 access control 같네요)

>> 유저가 볼 수 있는 특정 attribute 을 제한할 수도 있다

>> 정리 : row-level 로 제한을 하고 싶으면 LK 조건을 지정하면 된다.

 

>> 솔직히 마지막 부분들은 실습이 없었어서 감을 잡기 좀 어려웠음. 대충 내용만 이해해두자!!

 


---------------------------------------------- DD 끄으으으읏


<퀴즈>

 

 

1. RCU 만 개별적으로 늘리면 된다. 둘은 분리되어 있음
2. DD 는 서버리스임
5. DD 는 서버리스 이기 때문에 수평 확장 가능
6. item 최대 크기 400KB
7 (시간 좀 걸림, 틀림). 이건 근데 기준이 좀 불명확한거 아님? ㅋㅋㅋ 무조건 비용 적게면 당연히 프로비저닝이지
>> 개발을 위해 온디멘드, 프로덕션을 위해 오토 스케일링 활성화 된 프로비저닝 모드라고 함. 
>> 문제에서 "개발 모드에서 예측할 수 없는 양 요청" 이라고 함. 근데 ㅅㅂ 이것만 가지곤 부족하지 ㅅㅂ
8. DD Stream 으로 Update 순간을 캡처할 수 있다. Stream + 람다로 이런 기능 구현 가능
9. 자주 읽힐 것으로 예상되는 상황에서는 해당 항목에 대해서 캐시를 만들 수 있는 DAX 활성화 (최대 10배 성능 향상 제공, 고가용성 인메모리 캐시)
>> 가장 자주 사용되는 데이터를 캐시하여 핫 키에 대한 대량 읽기 문제를 해소해준다. 
>> "ProvisionedThroughputExceededException" 상황을 방지하기 좋음
10. CLI 명령어 복습. page-size 가 그게 아니였음! (page-size 는 백그라운드에서 돌리는 거였음). 실제 pagination 해주려면 max-item & token 활용해야함
11 (틀림). filter expression vs projection expression 확실하게 알아야 함
>> 문제상 : 가져온 항목 속성의 하위 집합을 검색할 수 있는 것은?
>> 확인해본 바로 projection expression 은 SELECT 구문 같은거고, filter expression 은 검색한 대상 내에서 Client 단에서 보여지는 항목을 보는 것(DD에 영향X)
>> 내가보기엔 문제가 잘못 번역된 것 같음.
13 (중요한 문제), 번역 씨발 ㅈ같음 ㄹㅇ 조절 문제가 뭐야 ㅆㅂ. Throttle 인 듯. 쓰로틀을 누가 조절이라고 해 이 상황에서 미친놈들아
>> 테이블에 문제 없는데 Throttle 이 발생하는 것이므로, LSI 가 아닌 GSI 측 문제임을 확인해야 함
>> LSI 는 메인 테이블 WCU, RCU 쓰는데, GSI 는 독립적으로 씀. GSI 에서 Throttle 발생시 메인 테이블도 발생하므로, 해당 상황이 맞다
15,16: 낙관적 락은 조건부 쓰기로 가능하며, 동시성 문제 해결 간으
17. 파티션키 동일한데 키가 아닌 속성으로 쿼리 해야 하므로, Sorting Key 변경하는 LSI 가 필요하다
18 (질문함). 모르겠으. 속성을 사용해서 DD 에 대한 쿼리 만들고 싶다고 함. 이것만으로 씨발 GSI 인지 LSI 인지 어떻게 알아 미친새끼들아

21 (어려움, 틀림, 이해가 안됨). DD 테이블에 많은 ProvisionedThroughputExceededException 이 발생하고 있다고 함. 근데 CW 지표 확인했는데 RCU 초과하지 않았다고 함. 가능한 이유?
>> Hot Partition / Hot Key 발생
>> RCU 와 WCU 는 테이블의 모든 파티션에 분산되어 있기 때문이라고 하는데, 이거 이해가 안됐음.

22. Eventually Consistent Read (번역 ㅆㅂ ㅋㅋ) > 1RCU - 최대 4KB 항목에 대해 초당 2개 가능
> 16KB 인 데이터를 초당 12개 읽는다
> 4RCU 는 16KB 에 대해 초당 2개 가능 >12개 읽으려면 24RCU
(주의, 만약 18KB 이런 애매한 단위면 5RCU 로 시작해야함 (20KB 로 읽어야 하기 때문)

23. SCR > 1RCU 4KB 초당 1개 >10KB 니까 12KB 취급. *3 하고 10개니까 *10 하면 30RCU
24. Write, 초당 12개, 각 항목 8KB > 1WCU 는 초당 1KB 의 데이터 1개 쓰기 > 12WCU * 8 = 96WCU
26. Stream 레코드는 SQS 로는 보낼 수 없다! (보낼 수 있는 영역 외우기)
27. TTL 이후 남아 있어도 냅두면 됨. 48 시간 이내에 삭제됨.

 

 

 

====================================================================================

 

 

교육 출처

 

- AWS Certified Developer Associate 시험 대비 강의 - Udemy

 

https://www.udemy.com/share/105Hxw3@z0Wqme5D9voly52i8MuQYl_c8462GP25Oul4H38G3nVfgD4fMrYPJE5LOB88iDuZ/

728x90