음성으로 일기를 기록하고, 이 일기의 감정을 분석하는 서비스를 만들고 있습니다. 음성을 텍스트로 변환하거나, 텍스트에서 감정을 추출할 때는 불가피하게 오랜 시간이 걸립니다🥲 따라서 전체 응답 시간을 최소화하기 위해 다른 부분에서 발생하는 시간을 최소화할 필요가 있다 느꼈습니다. 그래서 빠르다는 장점이 있는 NoSQL 형식의 DynamoDB를 사용하게 되었습니다.
대부분의 구성이 다른 데이터베이스와 동일합니다.
기본키는 두가지 방법으로 구성할 수 있는데, 이때 파티션키와 정렬키라는 개념이 사용됩니다.
DynamoDB의 파티션은 데이터를 관리하고 분배하는 논리적인 구성 단위입니다. 각 파티션은 파티션 키로 구분될 수 있습니다. 파티션키는 해시 함수에 대한 입력으로 사용되고 해시 함수의 출력 결과에 따라 파티션이 결정됩니다.
파티션 키는 DynamoDB에서 데이터를 효율적으로 분산시키고 검색하는 데 중요한 역할을 합니다. 특정 파티션 키를 사용하여 데이터를 조회하면, DynamoDB는 해당 파티션 키가 속한 파티션에서만 데이터를 검색합니다. 따라서 전체 데이터 세트가 아닌 특정 파티션 범위에서만 검색을 수행하므로, 데이터 조회 성능이 크게 향상됩니다.
파티션키로만 기본키를 구성하는 경우, 어떤 item도 동일한 파티션키를 가질 수 없습니다.
여러 항목이 동일한 파티션키를 가질 수 있습니다. 이때의 파티션키는 어떤 파티션에 저장될지를 의미하며, 파티션키만을 이용한다면 고유한 item을 구분할 수 없습니다. 정렬 키를 함께 이용해야 합니다. 같은 파티션에 있는 item들은 정렬키를 이용해 정렬됩니다.
우리 서비스에서는 하나의 사용자가 여러 날짜의 일기를 쓸 수 있으며, 사용자와 시간으로 각각의 일기를 구성할 수 있으므로 파티션 키로는 userId
, 정렬 키로는 dateTime
을 사용하기로 했습니다.
DynamoDB를 만드는 방법은 아주 간단하므로 본 글에서는 생략하고, 이미 만들어진 DynamoDB에 어떻게 접근하여 데이터를 넣을 수 있는지에 대해서 작성해보겠습니다.
람다에서 DynamoDB에 접근하기 위해서는 사용하려는 람다에 Dynamo 관련 권한을 추가해야 합니다. 저는 DynamoDBFullAccess
권한을 추가해주었습니다.
아래 코드를 통해 테이블이 잘 만들어졌는지, 정상적으로 접근 가능한지 확인할 수 있습니다. describe_table
는 DynamoDB 테이블의 내용을 설명해주는 함수입니다.
import boto3
dynamodb = boto3.resource('dynamodb') #client가 아니라 resource로 하는걸 추천
def lambda_handler(event, context):
print(dynamodb.describe_table(TableName="any-it-jana")) #테이블 명
출력 결과
'Table':{
'AttributeDefinitions':[
{
'AttributeName':'dateTime',
'AttributeType':'S'
},
{
'AttributeName':'userId',
'AttributeType':'S'
}
],
'TableName':'any-it-jana',
'KeySchema':[
{
'AttributeName':'userId',
'KeyType':'HASH'
},
{
'AttributeName':'dateTime',
'KeyType':'RANGE'
}
],
'TableStatus':'ACTIVE'
...
테이블이 잘 생성되었으며, 기본키도 제대로 설정되었음을 확인할 수 있습니다.
DynamoDB에 item을 추가하기 전, DynamoDB가 지원하는 데이터 타입에 대해 살펴봅시다.
우리 서비스에서는 [사용자 id, 시간, 일기 내용, 감정, 사용된 단어]가 저장되어야 합니다. 사용자 id와 일기 내용은 이견 없이 S
로 저장하면 될 것 같습니다. DynamoDB에서는 시간에 대한 데이터 타입을 지원하지 않으므로 시간도 S
로 저장합니다. 감정은 JSON 형태로 추출되므로 M
으로 저장합니다. 사용된 단어는 리스트 형식이므로 L
로 저장합니다.
Lambda에서는 boto의 put_item
를 사용해서 item을 추가할 수 있습니다. 이때 boto3.resource
를 사용하는 형식과 boto3.client
를 사용하는 형식이 다르기 때문에 주의해야 합니다.
둘 다 aws 서비스를 추상화할 수 있는 sdk 이지만, resource가 더 고수준의 서비스라고 할 수 있습니다. 따라서 resource를 사용한다면, low-level의 것을 신경쓰지 않아도 됩니다. 하지만 모든 aws service에서 resource를 지원하는 것은 아니기 때문에 client를 사용하는 것도 염두에 두어야 합니다.
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table("any-it-jana")
def lambda_handler(event, context):
item = {
'userId': 1,
'text': 'hi',
}
table.put_item(Item=item)
import boto3
dynamodb = boto3.client('dynamodb')
def lambda_handler(event, context):
item = {
'userId': {'N' : 1},
'text': {'S' : text}
}
dynamodb.put_item(TableName="any-it-jana", Item=item)
저는 resource를 사용하는 코드가 더 간단하고, 가독성도 좋다고 판단하여 resource를 사용하는 방법을 적용했습니다😊
import json
import boto3
from datetime import datetime
from decimal import Decimal
import time
comprehend = boto3.client('comprehend')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table("any-it-jana")
def lambda_handler(event, context):
# 측정 시작
start = time.time()
text = event['text']
lang_code = 'ko'
userId = "1" # 수정 예정
phrases = comprehend.detect_key_phrases(Text = text, LanguageCode = lang_code)
key_phrase_list = [item['Text'] for item in phrases['KeyPhrases']]
sentiment_float = comprehend.detect_sentiment(Text = text, LanguageCode = lang_code)['SentimentScore']
sentiment = json.loads(json.dumps(sentiment_float), parse_float=Decimal)
item = {
'userId': userId,
'dateTime': datetime.now().strftime("%Y/%m/%d %H:%M"),
'status': 'running',
'text': text,
'emotionScore': sentiment,
'keyPhrases': key_phrase_list
}
try :
savedData = table.put_item(Item=item)
print(f"데이터 입력 성공 : {item}")
except Exception as e:
print(f"데이터 입력 중 오류 발생: {e}")
# 측정 종료
end = time.time()
print(f"Comprehend 수행 시간: {end - start: .2f} sec")
{
'userId': '1',
'dateTime': '2023/12/09 04:41',
'status': 'running',
'text': '오늘은 정말 힘든 하루였다. 엄마가 보고싶다.',
'emotionScore': {'Positive': Decimal('0.022698456421494484'),
'Negative': Decimal('0.9068351984024048'),
'Neutral': Decimal('0.0702165737748146'),
'Mixed': Decimal('0.00024978703004308045')},
'keyPhrases': ['오늘', '힘든 하루', '엄마']
}
{
"userId": {
"S": "1"
},
"dateTime": {
"S": "2023/12/09 04:41"
},
"emotionScore": {
"M": {
"Mixed": {
"N": "0.00024978703004308045"
},
"Negative": {
"N": "0.9068351984024048"
},
"Neutral": {
"N": "0.0702165737748146"
},
"Positive": {
"N": "0.022698456421494484"
}
}
},
"keyPhrases": {
"L": [
{
"S": "오늘"
},
{
"S": "힘든 하루"
},
{
"S": "엄마"
}
]
},
"status": {
"S": "running"
},
"text": {
"S": "오늘은 정말 힘든 하루였다. 엄마가 보고싶다."
}
}
무사히 잘 저장되었음을 확인할 수 있습니다!😇
시간대에 대한 별도 설정을 하지 않으니 시간대가 서울이 아닌 상태로 저장되었습니다. 람다에 환경변수를 추가해 해결할 수 있었습니다.
Float types are not supported. Use Decimal types instead 에러가 발생했습니다. 아래 링크를 참고해 해결할 수 있었습니다.
이미지 출처 : https://aws.amazon.com/ko/blogs/database/choosing-the-right-dynamodb-partition-key/
https://www.learnaws.org/2021/02/24/boto3-resource-client/