본문 바로가기
개발/AWS

DynamoDB Pagination 삽질 기록

by 문둘기 2020. 3. 12.
https://k.kakaocdn.net/dn/bDwsvo/btqCDlg6QeK/Tg3VHXorsCkkDEzsNKrdnk/img.png

Pagination을 해야할 일이 생겼다.

문제는 지금 사용중인 DB가 DynamoDB 라는것.
오래전에 RDBMS를 이용해서 게시판을 구현해본적은있지만
DynamoDB같은 nosql로 구현해본적은 없었다

결론부터 말하자면, 포기했다.
애초에 DynamoDB로 게시판같은 Pagination을 하려는게 문제(?)였다

삽질 정리내용

우선 기획자님의 요구사항은 개인메모장같은 기능이었다.
페이지마다 5개씩 글을 볼수있어야했다

일단 쿼리 params에 아래의 옵션을 추가해야한다

ScanIndexForward: false 
Limit: 5

ScanIndexForward는 오름차순, 내림차순을 정하는 옵션이다
기본적으로 DDB는 정렬 순서가 오름차순인데
게시판처럼 최근글일수록 위로 오게하려면 내림차순으로 바꿔야한다.

ScanIndexForward를 false로 하면 내림차순이된다.(DDB는 정렬을 Sort key를 기준으로 한다. 참고로 Key, GSI, LSI 말고 일반 attribute로는 정렬할수없다고한다.)

Limit 5 는 말그대로 데이터를 5개만 가져오라는것이다
아래는 DynamoDB 쿼리결과의 예시다.

{ Items: Array(5), Count: 5, ScannedCount: 5, LastEvaluatedKey: {…} }

Items 배열에는 결과값이 들어있다,
ScannedCount는 5개 스캔했다는것이고
Count는 결과값이 5개라는뜻이다

여기서 LastEvaluatedKey가 핵심인데,
쿼리 결과에 LastEvaluatedKey가 없다면, 더이상 검색할항목이 없다는뜻이다

예를들어 실제 데이터가 6개 들어있는데,
Limit 6으로 데이터를 뽑으면더이상 검색할 항목이 없기때문에 아래처럼 쿼리결과에 LastEvaluatedKey가 없다

{ Items: Array(6), Count: 6, ScannedCount: 6 }

하지만 Limit 5를 하게되면 아직 더 검색할 값이 더 남아있기때문에
LastEvaluatedKey가 존재하게된다.
LastEvaluatedKey 에는 검색한값의 마지막값 partition key와 sort key 가 저장되어있다

{ Items: Array(5), Count: 5, ScannedCount: 5, LastEvaluatedKey: {…} }

이게 왜 핵심이냐면, DDB의 Pagination은 LastEvaluatedKey를 이용해서 해야하기때문이다

DynamoDB의 Pagination 방식

공식 문서에 따르면 DynamoDB는

이런식으로 Pagination을 구현한다

예시

예를들어 총 16개의 게시글이 있는데, 5개씩 게시글을 가져왔다고 가정해보자

1페이지 → { Items: Array(5), Count: 5, ScannedCount: 5, LastEvaluatedKey: {…} }
2페이지 → { Items: Array(5), Count: 5, ScannedCount: 5, LastEvaluatedKey: {…} }
3페이지 → { Items: Array(5), Count: 5, ScannedCount: 5, LastEvaluatedKey: {…} }
4페이지→ { Items: Array(1), Count: 1, ScannedCount: 1}

순서대로 설명하자면

1페이지값 5개를 뽑아온다.
1페이지 가장 마지막값(5번째값)의 key가 LastEvaluatedKey에 저장되어있다
LastEvaluatedKey를 2페이지의 시작값( ExclusiveStartKey )으로 이용해서 2페이지값을 뽑아온다
위 과정 반복하다가 4페이지에는 LastEvaluatedKey가 없으니, 더이상 뽑아올 값이없다

여기까지 이해했다면 어떤 문제가 있을지 알수있을것이다..

바로 다음 페이지의 데이터만 가져올수있다는것
1페이지를 뽑았으면, 1페이지 마지막값을 얻게되고,
그값을 기준으로 2페이지 데이터를 가져오기때문에
페이지를 건너다닐수가없다.. 1페이지에서 3페이지를 못간다.

이전 or 다음페이지만 가능하다

어떻게든 하기위해 생각한 방법

1. sort key를 auto increment 처럼 저장한다면 쿼리할때 Sort Key 를 BETWEEN쿼리로 가져올수있을거같은데
좀더 생각해봤더니, 유저가 글을 삭제하면 페이지에 구멍이 나버릴것이다 ..

2. 페이지마다 첫번째 값을 따로 저장해두는것이다, 해당페이지값을 이용해서 해당페이지를 뽑아오면된다,
문제는 글이 추가or삭제될떄마다 계속 바뀌어야한다,, 비효율적이다 굳이 이렇게까지 할필요가있을까..?

결론

결국 DDB로는 게시판처럼 페이지네이션을 하기가힘들다는게 잠정적으로 내린 결론이었다.. ( 불가능은 아님 )

그후 관련 질문을 몇군데에 했는데, 감사하게도 많은 답변을 받았다.

받은 답변들 공유 ( 오픈 카톡방 )

https://k.kakaocdn.net/dn/cleOYp/btqCIE1pFXd/f72h1bkunrfLurTIVaN3jk/img.png

이분은 DDB 페이지네이션과는 조금 다른주제를 말씀해주셨는데,
결국 RDBMS도 페이지네이션했을때 뒤쪽 페이지일수록 '이론상' 느려진다는것이다

https://k.kakaocdn.net/dn/UXwcl/btqCKBv3GdD/DmpgEnxugkHBPgMJ0NLD7K/img.png

이건 좀 신기했는데

뒤쪽 페이지일수록 '이론상' 느려진다 라고 하셨다.
위 문제때문에 오유 사이트의 경우, 301페이지로 이동하기위해 page 파라미터에 300보다 큰값을 넣게되면

"서버부하 문제로 베스트오브베스트 게시판의 300페이상의 조회를 당분간 제한합니다."

라는 메시지가 나오면서 접근할수가없다. 300페이지 이하는 전부 잘된다.

받은 답변들 공유2 ( AWSKRUG 슬랙의 Serverless 채널)

정렬 키를 auto increment 처럼 사용하시면 제한적으로 가능 할 것 같은데 일반적으로는 불가능 할 것 같습니다.


이 답변은 위에서 내가 생각했던것과 동일한것같다.
제한적으로 가능하다는게 아마삭제관련 문제를 말씀하시는거같다

저는 pagination이 핵심 비즈니스 로직이면 rdb를 사용하는것을 추천드립니다. DDB로 페이지네이션은 NG입니다. 꼭 DDB를 사용하고싶은 경우에는 ES를 도입해서 검색 쿼리는 ES에서 처리하는 아키텍쳐도 있습니다. 이때 데이터 추가/삭제/변경 및 GetItem 정도는 DDB에서 바로 수행하고, 쿼리는 ES에서 처리합니다. 데이터의 동기화는 DDB Streams로 비동로 처리합니다. 추가적으로 count(*) 같은것도 DDB에는 없는 기능이라, 쿼리 대상이되는 총 개수를 파악하는것도 DDB로는 어렵습니다.


다른분은 DDB로 페이지네이션은 "NG" 라고 하셨다
DDB를 꼭 쓰고싶다면 ES를 도입해서 처리해도된다고 제안해주셨다 ( ES는 엘라스틱서치인듯..? 아직 사용해본적이없다 )

DDB 스트림으로 ES와 데이터 동기화를하고, 쿼리를 ES에서 처리하는 방식그외에 추가수정삭제,
GetItem 정도의 단순한 명령은 DDB에서 처리하는것

어쨌든 결론은 DDB로 pagination 구현은 안하기로했다

지금까지 서비스를 DDB하나로만 운영하면서 불편함을 많이 겪었다
그래서 "일부는 RDS로 이전해서 RDB와 NOSQL의 장점만 골라쓰자" 라고 생각하고있었기때문에
굳이 DDB로 생쇼를하면서 페이지네이션을 구현하고싶지않았다

위 답변에서도 볼수있듯이, count(*) 처럼, 전체데이터가 몇갠지 알아내는 쿼리도 없기때문에 답이없다
풀스캔해버리면 아마 할수있을것이다 ㅋㅋ..

추가

글을 블로그에 정리하면서 다시 생각해보니, 모바일앱같은 경우에는 문제 없을것같다.

앱은 하단의 버튼을 통해 페이지를한번에 이동하는것보다
리사이클러뷰 계속 내리면 그다음 페이지를 계속 추가로 불러오는것아닌가..?
그래서 또 질문을 올렸고, 받은 답변이다

앱 쪽만 보면 별 문제 가 없을것 같습니다만, 주로 앱쪽에서 리스트로 불러오는 기능은 어드민쪽이나 백오피스쪽에서도 리스트로 표현해주게 되는데, 어드민에서도 무한스크롤 방식으로 괜찮다면 상관없을것같습니다. 주로 어드민에서는 페이지를 하단에 표시해주고 페이지를 클릭해서 이동하거나, 페이지나 검색 쿼리 자체가 url에 포함되어서 url을 공유해서 협업한다던지 하는 케이스도 있으므로, 어드민까지 고려하면 아무리 심플한 앱도 결국 DynamoDB만으로는 어려울 것 같다는 의견입니다.근데 사실 서버리스 앱으로 빠르게 프로토타이핑해서 사용하고 싶은경우에는 일단 DDB로 개발하고, 나중에 RDS등으로 갈아타도 크게 문제는 없을것같습니다. 실제로 개발은 쿼리단위로 코드를 짜는게아니라 주로 ORM 활용해서 많이 개발하니까 코드상에서 DB이전에 대한 이슈도 크게 부담스러운 수준이 되기전에 얼른 갈아타면 괜찮을것같습니다. 물론 이런경우에 SLA가 많이 높다면, 마이그레이션이라고하는 큰 산을 넘는게 또 다른 도전과제가 되겠지만요…;;

서비스마다 다 요구사항이나 제약사항 등이 달라서 일반화 시키는건 무리겠지만, 그래도 제 나름대로 일반화 시켜서 갖고있는 DDB에 대한 생각은,

- 코어 비즈니스 로직에 사용하기에는 어렵다.
- SQL 조인을 사용하지않는 마이크로서비스에는(대략적으로)적합할 가능성이 높다 –> 이때는 DB의 한계를 마이크로서비스 설계 능력에 의존하여 풀게 되므로 난이도는 더 상승하겠지만요.
- 일단 DDB로 개발하고, 검색등의 한계에 부딪히면 ES를 도입하는식의 어프로치가 허용된다면 적극 도입해서 사용해도 될것 같다. –> 이경우에는 Strong consistency 보다는 eventual consistency가 허용되는 서비스에만 해당될것같습니다.
- DDB 장점만 알겠고 단점은 잘모르겠으면 일단 잘 알고있는 RDB를 쓰자
정도입니다..ㅎㅎ 혹시 다른분들 의견있으시면 저도 궁금하네요..

 

좋은답변을받았다 내용이 정말좋으니 꼭 읽어보시길 !

https://k.kakaocdn.net/dn/uOjhn/btqCKzSBXAd/jKS4u6t0beIjySdTqfIink/img.png

그외 , 참고한 글들

https://medium.com/@sylwit/how-to-work-with-aws-dynamodb-78fa223b3bf2

http://docs.aws.amazon.com/aws-sdk-php/v2/api/class-Aws.DynamoDb.DynamoDbClient.html#_query

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.Pagination.html

https://stackoverflow.com/questions/54250864/how-to-use-pagination-on-dynamodb

https://www.talentica.com/blogs/dynamo-db-pagination/