[AWS] Lambda에서 aws DynamoDB 사용하기

nayonso·2023년 12월 8일
0

aws rookie championship

목록 보기
3/4
post-thumbnail

📑 학습 배경


음성으로 일기를 기록하고, 이 일기의 감정을 분석하는 서비스를 만들고 있습니다. 음성을 텍스트로 변환하거나, 텍스트에서 감정을 추출할 때는 불가피하게 오랜 시간이 걸립니다🥲 따라서 전체 응답 시간을 최소화하기 위해 다른 부분에서 발생하는 시간을 최소화할 필요가 있다 느꼈습니다. 그래서 빠르다는 장점이 있는 NoSQL 형식의 DynamoDB를 사용하게 되었습니다.


📎 DynamoDB의 구성 요소


대부분의 구성이 다른 데이터베이스와 동일합니다.

  • Table : 다른 데이터베이스에서의 테이블과 동일한 개념입니다.
  • Item (항목) : 다른 데이터베이스에서의 행, 레코드, 튜플과 대응되는 개념입니다.
  • Attribute (속성) : 데이터베이스에서의 열, 필드와 대응되는 개념입니다.
  • Primary Key (기본키) : 각 item을 구분해주는 공유 식별자로, 다른 데이터베이스에서의 기본키와 대응되는 개념입니다.

기본키는 두가지 방법으로 구성할 수 있는데, 이때 파티션키와 정렬키라는 개념이 사용됩니다.


📎 DynamoDB에서 기본키를 구성하는 방법


📍 파티션과 파티션 키

DynamoDB의 파티션은 데이터를 관리하고 분배하는 논리적인 구성 단위입니다. 각 파티션은 파티션 키로 구분될 수 있습니다. 파티션키는 해시 함수에 대한 입력으로 사용되고 해시 함수의 출력 결과에 따라 파티션이 결정됩니다.

파티션 키는 DynamoDB에서 데이터를 효율적으로 분산시키고 검색하는 데 중요한 역할을 합니다. 특정 파티션 키를 사용하여 데이터를 조회하면, DynamoDB는 해당 파티션 키가 속한 파티션에서만 데이터를 검색합니다. 따라서 전체 데이터 세트가 아닌 특정 파티션 범위에서만 검색을 수행하므로, 데이터 조회 성능이 크게 향상됩니다.

📍 1) 파티션 키로만 기본 키를 구성

파티션키로만 기본키를 구성하는 경우, 어떤 item도 동일한 파티션키를 가질 수 없습니다.

📍 2) 파티션 키와 정렬 키로 기본 키를 구성 (복합키)

여러 항목이 동일한 파티션키를 가질 수 있습니다. 이때의 파티션키는 어떤 파티션에 저장될지를 의미하며, 파티션키만을 이용한다면 고유한 item을 구분할 수 없습니다. 정렬 키를 함께 이용해야 합니다. 같은 파티션에 있는 item들은 정렬키를 이용해 정렬됩니다.

🤔 우리 서비스는 어떤 기본키를 따라야 하나?

우리 서비스에서는 하나의 사용자가 여러 날짜의 일기를 쓸 수 있으며, 사용자와 시간으로 각각의 일기를 구성할 수 있으므로 파티션 키로는 userId, 정렬 키로는 dateTime을 사용하기로 했습니다.

📎 Lambda에서 DynamoDB 사용하기


DynamoDB를 만드는 방법은 아주 간단하므로 본 글에서는 생략하고, 이미 만들어진 DynamoDB에 어떻게 접근하여 데이터를 넣을 수 있는지에 대해서 작성해보겠습니다.

📍 람다에 DynamoDB 접근 권한 주기

람다에서 DynamoDB에 접근하기 위해서는 사용하려는 람다에 Dynamo 관련 권한을 추가해야 합니다. 저는 DynamoDBFullAccess 권한을 추가해주었습니다.

📍 람다에서 DynamoDB 접근 되는지 확인

아래 코드를 통해 테이블이 잘 만들어졌는지, 정상적으로 접근 가능한지 확인할 수 있습니다. 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가 지원하는 데이터 타입

DynamoDB에 item을 추가하기 전, DynamoDB가 지원하는 데이터 타입에 대해 살펴봅시다.

우리 서비스에서는 [사용자 id, 시간, 일기 내용, 감정, 사용된 단어]가 저장되어야 합니다. 사용자 id와 일기 내용은 이견 없이 S로 저장하면 될 것 같습니다. DynamoDB에서는 시간에 대한 데이터 타입을 지원하지 않으므로 시간도 S로 저장합니다. 감정은 JSON 형태로 추출되므로 M으로 저장합니다. 사용된 단어는 리스트 형식이므로 L로 저장합니다.

📍 Lambda에서 DynamoDB에 item 추가하기

Lambda에서는 boto의 put_item 를 사용해서 item을 추가할 수 있습니다. 이때 boto3.resource 를 사용하는 형식과 boto3.client를 사용하는 형식이 다르기 때문에 주의해야 합니다.

🤔 resource vs. client

둘 다 aws 서비스를 추상화할 수 있는 sdk 이지만, resource가 더 고수준의 서비스라고 할 수 있습니다. 따라서 resource를 사용한다면, low-level의 것을 신경쓰지 않아도 됩니다. 하지만 모든 aws service에서 resource를 지원하는 것은 아니기 때문에 client를 사용하는 것도 염두에 두어야 합니다.

📍 resource를 사용할 때 put_item() 형식

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)

📍 client를 사용할 때 put_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': ['오늘', '힘든 하루', '엄마']
}

📎 DynamoDB에 저장되는 형식

{
  "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": "오늘은 정말 힘든 하루였다. 엄마가 보고싶다."
  }
}

무사히 잘 저장되었음을 확인할 수 있습니다!😇

🛠️ Trouble Shooting

📍 Time zone

시간대에 대한 별도 설정을 하지 않으니 시간대가 서울이 아닌 상태로 저장되었습니다. 람다에 환경변수를 추가해 해결할 수 있었습니다.

📍 Float types are not supported

Float types are not supported. Use Decimal types instead 에러가 발생했습니다. 아래 링크를 참고해 해결할 수 있었습니다.

https://stackoverflow.com/questions/70343666/python-boto3-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/

0개의 댓글