Tuesday, March 5, 2019

流式分页 Pagination

传统分页和流式分页

流式分页应用场景:

通过滚动/上拉/点击等方式加载新一页
无页码
无上/下页按钮
不可跳转至指定页面
pc端和移动端均有使用

实现:

前端实现:后端返回全部结果,前端做分页

后端实现:

用offset和limit来实现(mysql)

优化:

当前页数据(及其之前)若有删除,由于计算问题total/limit*offset,数据整体上移(变少),指针相对下移,会出现数据缺失。
当前页数据(及其之前)若有增加,由于计算问题total/limit*offset,数据整体下移(变多),指针相对上移,会出现数据重复。

方案:

1. 后端方案,缓存数据
2. 后端方案,用cursor记录最后一个ID。这叫游标式分页。DynamoDB采用这个方案。pagination token带有过期时间戳。
3. 前端方案,一次性发所有Id,缓存到前端。
4. 前端方案,客户端保留已浏览数据,手动去重。不能避免数据缺失。

游标式分页

这个方案避免了数据缺失和重复,但有适用范围。它仅能用于追加式的单一排序。这要求
1. 单一排序:这个顺序是固定的,而且可以快速定位到这个ID。
2. 追加式:只能添加和删除一行,不能更新某些域。更新它们若影响排序的话,就涉及数据快照概念。若第一页分页请求产生时(方案一)生成时间戳,这个时间戳对应整套数据的快照,若某些域在这个时间戳之后更新(可以按行来添加lastUpdateTime),按照设计,应该显示旧值。这要求每个域所有更新都要有历史记录。此解决方案有利有弊:可以避免数据重复出现,但是新数据会不显示。

* 快照模式:用lastUpdatedTimestamp
* 若要实现scroll down时候,新加入的entries也显示,这不属于快照模式,可以用timestamp的UUID, uuid > last-entry-uuid就可以,缺点是符号条件的entry只能按插入时间顺序排序而不能按其他条件排序

MongoDB的游标式实现

Request:
// Query the first page.
  let result = await MongoPaging.find(db.collection('myobjects'), {
    limit: 2
  });
  console.log(result);

  // Query next page.
  result = MongoPaging.find(db.collection('myobjects'), {
    limit: 2,
    next: result.next // This queries the next page
  });
  console.log(result);
}

Response:
page 1 { results:
   [ { _id: 580fd16aca2a6b271562d8bb, counter: 4 },
     { _id: 580fd16aca2a6b271562d8ba, counter: 3 } ],
  next: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGJhIn0',
  hasNext: true }
page 2 { results:
   [ { _id: 580fd16aca2a6b271562d8b9, counter: 2 },
     { _id: 580fd16aca2a6b271562d8b8, counter: 1 } ],
  previous: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGI5In0',
  next: 'eyIkb2lkIjoiNTgwZmQxNmFjYTJhNmIyNzE1NjJkOGI4In0',
  hasNext: false }

Request中的limit的返回结果的大小,next上改页结果的最后一项的id,也就是下一页结果需要从这个项目后开始查找。
Response中的token是 {_id: 123} 的base64 encode. 比如page 1的next就是第二个(最后一个)id的encode。hasNext的设计可以让客户更清晰地知道是否有下一页,当然也可以用next=''来替代。

在有些设计中,token加入timestamp, {_id: 123, timestamp: 2019-06-21 12:23:11},设一个SLA为24小时,也就是下一个翻页请求在SLA内完成,这样可以减少不必要的请求。


Ref
[1] https://aotu.io/notes/2017/06/27/infinite-scrolling/index.html
[2] MongoDB实现
[3] 游标式分页的优势

No comments:

Post a Comment