들어가며

페이지네이션(Pagination) 이란 한정된 네트워크 자원을 효율적으로 활용하기 위해 쿼리의 결과값으로 리턴된 리소스를 분할하여 전달하는 것을 의미한다.

즉, 데이터베이스에 만 개의 투두(todo) 아이템이 있을 때, 한 번에 만 개를 돌려주는 대신 0번부터 49번까지 50개씩 돌려주는 것을 의미한다. 여기서 다음 요청이 들어오면 50번부터 99번까지, 또 다음 요청이 들어오면 100번부터 149번까지 돌려준다. 이렇게 함으로써 네트워크의 낭비를 막고, 빠른 응답을 기대할 수 있게 된다.

전통적인 페이지네이션 방식은 현재의 위치를 의미하는 offset (page) 과 한 번응답 시 돌려줄 갯수를 의미하는 limit(per_page), 두 가지의 파라미터를 이용한다. 그러므로 전통적인 페이지네이션을 통한 get요청은 아래와 같은 모습을 띈다.

https://todo.com/todos?offset=5&limit=30 // 페이지는 5번, 갯수는 30개!

SNS의 시대

전통적인 페이지네이션은 오랜 기간 잘 작동해왔다. 문제는 페이스북이나 인스타그램과 같이 잦은 수정/생성/삭제가 반복되는 SNS 서비스가 등장하면서 더 이상 효율적으로 작동하지 못하게 되었다는 것이다.

예를 들어 유저가 1페이지를 요청하여 5개의 아이템을 돌려준 상황을 가정하자. 그리고 30분쯤 후에 유저가 2페이지를 요청한다고 할 때, 만약 그 사이에 3개의 새로운 아이템이 추가된다면 2개의 아이템이 중복으로 전송된 셈이 된다.

Item 1 - 10

Read #1 page = 1, count = 5
Item 10 - 6

+ 3 Item

Read #2 page = 2, count = 5
Item 8 - 4, 3개의 중복 발생!

Cursor based pagination

이 같은 문제를 해결하기 위해 트위터는 커서 기반 페이지네이션을 활용했다. 커서 기반 페이지네이션이란 무엇일까? 쉽게 생각해보면 우리가 흔히 책을 읽을 때 사용하는 책갈피를 생각해볼 수 있다. 책갈피를 끼워두면 굳이 앞에서부터 페이지를 셀 필요 없이 곧장 읽은 데부터 다시 읽을 수 있다. 이전에 읽은 부분으로 넘어가는 것도 어렵지 않다. 책갈피가 Index의 역할을 하기 때문이다.

이쯤에서 트위터의 구현 방식을 살펴보자.
트위터는 끊임없이 변하는 타임라인을 효율적으로 읽어들이기 위해서 max_id라는 인자를 활용한다. 즉, 이전에 읽어들인 트윗의 아이디 중에서 가장 작은 아이디를 다음 요청에 count와 함께 전달하는 것이다.

이는 다음과 같다.

초기: Tweet 1 - 10

Read #1, count = 5
Tweet 10 - 6

+ 2 Tweets

Read #2, max_id = 6, count = 5
Tweet 6 - 2
-> 새로운 트윗이 추가되더라도 max_id부터 읽어들이기 때문에 상관없음.

여기서 주목할 점은 max_id를 활용함으로써 중복되는 트윗의 수를 최소한으로 유지할 수 있다는 점이다. (늘 max_id에 해당되는 트윗만 중복됨)

More optimization?

다음과 같은 경우를 생각해보자.

초기: Tweet 1-10까지 읽음. Tweet 11-18번이 추가됨.

Read #1, count = 5
Tweet 18 - 14

Read #2, count = 5, max_id = 14
Tweet 14 - 10, 두 개의 중복 발생!

위의 경우와는 달리 두 개의 중복이 발생하고 말았다. 이는 10번 트윗을 읽었다는 것을 전달하지 않았기 때문에 발생한 문제이다.

트위터는 since_id라는 인자를 활용해서 이 상황을 해결했다.
since_id를 활용하면 상황은 아래와 같이 달라진다.

초기: Tweet 1-10까지 읽음. Tweet 11-18번이 추가됨.

Read #1, count = 5, since_id = 10
Tweet 18 - 14

Read #2, count = 5, max_id = 14, since_id = 10
Tweet 14 - 11, 더 이상 10번을 읽어들이지 않음!

이미 10번 트윗을 읽었다는 정보가 전달됐기 때문에 더 이상 이미 읽은 트윗을 전달하지 않는다.

결론

전통적인 페이지네이션 방식은 오랫동안 네트워크 낭비를 줄여주는 기능을 담당해왔다. 하지만 실시간성을 띄는 SNS 서비스의 등장으로 리소스가 자주 수정/생성/삭제되는 상황이 늘어나자 중복 전송의 가능성이 커졌다. 트위터는 커서 기반 페이지네이션을 통해 실시간으로 변화하는 타임라인 상에서 리소스의 중복 전송을 효과적으로 막아냈다.

참고

트위터 API 문서: Get Tweet timelines
https://developer.twitter.com/en/docs/tweets/timelines/guides/working-with-timelines.html