본문 바로가기
제가 왜 DB를 만지고 있을까요?/DynamoDB

DynamoDB로 panaging 기능을 구현해보자.

by asj8000 2022. 12. 26.
반응형

만들면서 느낀 결론부터 적어보겠다.

 

페이지네이션(pagenation) 기능이 필요하다면, 그냥 관계형 db를 쓰자...ㅎ

DDB로 페이지네이션을 구현한다는 것 자체가 이미 오류사항이다.

아직 설계 전이고, 페이징 기능이 메인으로 필요하다면 RDS를 사용하자.

 

 

이 글에선 특정 n번째 페이지로 페이징 하는 방식이 아닌,

무한 스크롤 방식의 페이징을 구현하는 내용을 다루고 있으며.

 

이런 페이징 방식을 구현해야 하는 사람들에게 나의 케이스가 참고가 될 수 있을까 하여,

내가 사용했던 방법을 간단하게 공유해볼까 한다.

 

빠르고 간단하게 내용을 다뤄보자.

 


들어가기 앞서

 

우선 DynamoDB엔 페이지네이션 기능을 공식적으로 지원하지 않는다.

 

공식적으로 지원해주는 기능으론 단순 갯수를 제한걸어, 데이터의 호출 개수를 설정해주는 것 뿐이다.

그렇기에 단순 리밋 기능만 사용하여 3페이지의 값을 가져오려면 필수적으로 3번의 쿼리를 돌려야한다.

뭐 이런 방법으로 구현해도 되지만, 이 DB를 사용하는 주 목적중 하나가 성능이지 않겠는가,

1000페이지의 데이터를 불러오면 1000번의 쿼리를 돌려하는 이런 비효율적인 방법은 쓸 수 없다.

 

그렇기에, n번째 페이지로 바로 이동하는 방식이 필요하다면 DynamoDB가 아닌 다른 DB를 알아보는게 맞다.

뭐 굳이 그렇게 구현하라고 하면 할 수는 있겠지만.... 이 DB의 이점을 다 갖다 버리는 그런 짓을 왜 사서 하는지 싶다.

위에도 언급했다시피, 차라리 RDS를 쓰자

 

이 글에서 다룰 주제는 무한 스크롤 페이징 기능을 성능 저하 없이 효율적으로 구현하는 것이다.

 


구현 내용

aws 공식 docs에서 권장해주고 있는 내용을 토대로 기능을 구현하였습니다.

참고 aws docs - Paginating table query results

 

 

나는 페이지네이션 기능을 위해 primary key의 Sort Key를 사용하였다.

Primary Key에 Partition Key와 Sort Key 두가지의 키가 사용 가능하다.

 

 

나는 이 DB를 사용해 사용자가 받은 모든 푸시들을 모아볼 수 있는 푸시 알림함의 기능을 구현을 하였고,

('쿠팡에 있는 알림센터와 동일한 기능')

 

테이블 설계에선

Partition Key 에는 유저의 Id를 넣고,

Sort Key에는 DB에 값이 등록된 시점을 Y-m-d H:i:s에 millisecond 3자리를 포함한 타임스탬프 값 을 넣었다.

 

 

1페이지에는 그냥 userId 기준으로 10건을 가져오고 (페이징이 필요 없으니)

 

2페이지부터는, 직전 페이지 마지막 값의 Sort Key 값을 가져와서,

Sort Key 값이 마지막 값보다 작은 데이터를 서치 조건으로 걸었다.

(마지막 값을 검색 결과에서 LastEvaluatedKey 필드로 따로 내려줘서, 그냥 이 값만 가져오면 된다.)

 

이런 방식으로 구현을 할 경우,

마지막 키 값과 단순 조건문만을 가지고 성능 이슈 없이 간단하게 페이징 기능을 구현할 수 있다.

class queryData extends Base
{
     /**
     * 피드 검색용
     * 요청사항에 따른, 검색에 사용할 값 포맷하여 리턴
     * @param array $message   검색 요청사항 ['lastId']
     * @param int $Id          검색 대상 유저 Id
     * @return array           검색에 사용할 값 리턴
     */
    function dataFormat(array $message, int $Id) :array
    {
        $this->sendCase = 'query';

        $itemDetailUserId = '":userId": ' . $Id;
        $itemDetailSubKey = '';

        $addKeyConditionExpression = '';

        //페이징 처리
        if ($message['lastId'] != '') {
            //datetime 키 기준 해당 값보다 낮은 20개 출력.
            $itemDetailSub = ', ":sub": ' . $message['lastId'];
            $addKeyConditionExpression = ' and #dt < :sub';
            $params['ExpressionAttributeNames']['#dt'] = 'datetime';
        }

        $itemDetail = '
            {
                ' . $itemDetailUserId . '
                ' . $itemDetailSubKey . '
            }
        ';

        $item = $this->JsonEncoder()->marshalJson($itemDetail);

        $params['TableName'] = 'alarmCenterFeed';
        $params['KeyConditionExpression'] = 'userId = :userId ' . $addKeyConditionExpression;
        $params['sortKeyName'] = 'datetime';
        $params['ScanIndexForward'] = false;
        $params['Limit'] = 20;
        $params['ExpressionAttributeValues'] = $item;

        return $params;
    }
}

 


짧글을 마치며

 

위 케이스의 경우엔, Secondary key를 타임스탬프로 사용하고 있기에 가능한 방법이고,

이미 Secondary key를 다른 용도로 사용하고 있다면,,, 사실 이 방법은 적용이 불가능하다.

 

 

또한, 페이징 기능을 구현하는 방법은 분명 다양하게 있을 것이다.

아니면 Elasticsearch를 동기화시켜 검색용으로 사용해도 된다. 

 

근데 사실 이미 구현되어 있는 nosql 에 추후 기능 도입이 필요할때나 고민할 문제이지,

설계단계라면 그냥 처음부터 요구사항에 맞는 DB를 선택하는게 옳지 싶다.

 

반응형

'제가 왜 DB를 만지고 있을까요? > DynamoDB' 카테고리의 다른 글

Aws DynamoDB 사용하기  (0) 2021.08.13

댓글