Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pagination Demystified

Pagination Demystified

В множестве клиентских приложений реализована постраничная загрузка данных. Эта, достаточно простая в теории тема, может во много раз усложниться в зависимости от специфики проекта и функциональности серверной части.

Egor Tolstoy

March 30, 2016
Tweet

More Decks by Egor Tolstoy

Other Decks in Technology

Transcript

  1. PAGINATION
    DEMYSTIFIED
    Egor Tolstoy @igrekde

    View full-size slide

  2. DISCLAIMER
    ВЫСТУПЛЕНИЕ ОСНОВАНО НА ЛИЧНОМ
    СТРАДАНИЯХ ОПЫТЕ.
    ДОКЛАД СОДЕРЖИТ ДЕТАЛЬНОЕ ОПИСАНИЕ
    ОТВРАТИТЕЛЬНЫХ КОСТЫЛЕЙ
    И ПОЭТОМУ НЕ РЕКОМЕНДУЕТСЯ К ПРОСМОТРУ
    НИКОМУ.

    View full-size slide

  3. Локальная фильтрация
    Динамический размер страницы
    Пересобирается раз в час
    Выдача лежит на CDN
    Все еще limit/offset
    authorId = postId >> 32

    View full-size slide

  4. Техники пагинации
    Как подступиться
    Загрузка вниз
    Обновление ленты

    View full-size slide

  5. limit/offset
    httр://api.соm/entries?offset=100&limit=20

    View full-size slide

  6. 0
    1
    2
    cache server
    0
    1
    2
    3
    4
    5
    6
    7

    View full-size slide

  7. 0
    1
    2
    cache server
    0
    1
    2
    3
    4
    5
    6
    7
    offset: 3
    limit: 5
    3
    4
    5
    6
    7

    View full-size slide

  8. 0
    1
    2
    cache server
    0
    1
    2
    3
    4
    5
    6
    7
    offset: 3
    limit: 5
    3
    4
    5
    6
    7
    3
    4
    5
    6
    7
    3
    4
    5
    6
    7

    View full-size slide

  9. Номера страниц
    httр://api.соm/entries?page_number=3

    View full-size slide

  10. cache server
    0
    1
    2
    0

    View full-size slide

  11. cache server
    0
    1
    2
    0
    page: 1
    1

    View full-size slide

  12. cache server
    0
    1
    2
    0
    page: 1
    1
    1
    2
    1

    View full-size slide

  13. Позиции элементов не меняются
    Условия

    View full-size slide

  14. Курсоры
    httр://api.соm/entries?since=752284800&limit=10
    httр://api.соm/entries? max_id=228&limit=10
    httр://api.соm/entries? after=MTAxNTE&limit=10

    View full-size slide

  15. cache server
    0
    1
    2
    21:04-
    21:02-
    19:35-
    19:18-
    17:44-
    16:02-
    15:58-
    15:49-
    15:40-
    3
    4
    5
    6
    7
    8
    0
    1
    2
    19:35-
    19:18-
    17:44-
    15:21- 9

    View full-size slide

  16. cache server
    0
    1
    2
    21:04-
    21:02-
    19:35-
    19:18-
    17:44-
    16:02-
    15:58-
    15:49-
    15:40-
    3
    4
    5
    6
    7
    8
    0
    1
    2
    19:35-
    19:18-
    17:44-
    since: 17:44
    limit: 5
    15:21- 9
    3
    4
    5
    6
    7
    16:02-
    15:58-
    15:49-
    15:40-
    15:21-

    View full-size slide

  17. cache server
    0
    1
    2
    21:04-
    21:02-
    19:35-
    19:18-
    17:44-
    16:02-
    15:58-
    15:49-
    15:40-
    3
    4
    5
    6
    7
    8
    0
    1
    2
    19:35-
    19:18-
    17:44-
    since: 17:44
    limit: 5
    15:21- 9
    3
    4
    5
    6
    7
    16:02-
    15:58-
    15:49-
    15:40-
    15:21-
    16:02-
    15:58-
    15:49-
    15:40-
    5
    6
    7
    8
    15:21- 9

    View full-size slide

  18. Уникальный параметр каждого элемента
    Сортировка выдачи не меняется
    Условия

    View full-size slide

  19. Изменение количества
    элементов
    Актуальность
    выдачи
    Обновление
    контента
    • Лента статична
    • Новые элементы
    добавляются
    сверху
    • Любая часть
    выдачи может быть
    изменена
    • Выдача всегда
    актуальна
    • Выдача может быть
    переформирована
    • Отображаемые
    данные не
    обновляются
    • Отображаемые
    данные могут быть
    изменены

    View full-size slide

  20. 1. Количество элементов не
    меняется
    2. Выдача всегда актуальна
    3. Данные могут быть
    изменены
    Афиша.Рестораны

    View full-size slide

  21. 1. Новые элементы
    добавляются только сверху
    2. Выдача всегда актуальна
    3. Данные могут быть
    изменены
    Афиша

    View full-size slide

  22. 1. Количество элементов не
    меняется
    2. Выдача может быть
    переформирована
    3. Данные не могут быть
    изменены
    Рамблер.Новости

    View full-size slide

  23. 1. Любая часть выдачи может
    быть изменена
    2. Выдача всегда актуальна
    3. Данные могут быть
    изменены
    Рамблер.Почта

    View full-size slide

  24. тем временем в офисе бэкенда

    View full-size slide

  25. Решения проблем
    пагинации на
    клиенте

    View full-size slide

  26. item 0
    item 1
    item 2
    item 3
    item 4
    item 5
    item 6
    item 7
    item 8
    item 9
    Загрузка вниз
    Случай 1:
    Лента статична
    1/5

    View full-size slide

  27. item 0
    item 1
    item 2
    item 3
    item 4
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    Загрузка вниз
    Случай 1:
    Лента статична
    2/5

    View full-size slide

  28. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    Загрузка вниз
    Случай 1:
    Лента статична
    3/5

    View full-size slide

  29. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    offset: 5
    limit: 5
    Загрузка вниз
    Случай 1:
    Лента статична
    4/5

    View full-size slide

  30. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5 (cached)
    item 6 (cached)
    item 7 (cached)
    item 8 (cached)
    item 9 (cached)
    offset: 0
    limit: 5
    offset: 5
    limit: 5
    Загрузка вниз
    Случай 1:
    Лента статична
    5/5

    View full-size slide

  31. paging.startIndex = cachedPosts.count;
    paging.count = 5;

    View full-size slide

  32. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    Загрузка вниз
    Случай 2:
    Добавились новые
    элементы
    1/5

    View full-size slide

  33. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    item 0' (new)
    item 2' (new)
    Загрузка вниз
    Случай 2:
    Добавились новые
    элементы
    2/5

    View full-size slide

  34. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    offset: 5
    limit: 5
    item 0' (new)
    item 2' (new)
    Загрузка вниз
    Случай 2:
    Добавились новые
    элементы
    3/5

    View full-size slide

  35. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5 (cached)
    item 6 (cached)
    item 7 (cached)
    item 8
    item 9
    offset: 0
    limit: 5
    offset: 5
    limit: 5
    item 0' (new)
    item 2' (new)
    Загрузка вниз
    Случай 2:
    Добавились новые
    элементы
    4/5

    View full-size slide

  36. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5 (cached)
    item 6 (cached)
    item 7 (cached)
    item 8
    item 9
    offset: 0
    limit: 5
    offset: 5
    limit: 5
    offset: 8 + 2
    item 0' (new)
    item 2' (new)
    Загрузка вниз
    Случай 2:
    Добавились новые
    элементы
    5/5

    View full-size slide

  37. paging.startIndex = cachedPosts.count
    + intersections;
    paging.count = 5;

    View full-size slide

  38. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    1/7

    View full-size slide

  39. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    item 10
    item 11
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    2/7

    View full-size slide

  40. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    item 10
    item 11
    offset: 5
    limit: 5
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    3/7

    View full-size slide

  41. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7 (cached)
    item 8 (cached)
    item 9 (cached)
    offset: 0
    limit: 5
    item 10 (cached)
    item 11 (cached)
    offset: 5
    limit: 5
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    4/7

    View full-size slide

  42. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    ...
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    5/7

    View full-size slide

  43. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    offset: 0
    limit: 5
    ...
    offset: 4
    limit: 5
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    6/7

    View full-size slide

  44. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5 (cached)
    item 6 (cached)
    item 7 (cached)
    item 8 (cached)
    item 9
    offset: 0
    limit: 5
    ...
    offset: 4
    limit: 5
    Загрузка вниз
    Случай 3:
    Удалены старые
    элементы
    7/7

    View full-size slide

  45. paging.startIndex = startIndex - 1;
    paging.count = 5;

    View full-size slide

  46. еще одна шутка про Facebook

    View full-size slide

  47. item 0
    item 1
    item 2
    item 3
    item 4
    item 5
    item 6
    item 7
    item 8
    item 9
    Загрузка вниз
    Случай 4:
    Порядок выдачи
    пересобирается
    1/5

    View full-size slide

  48. item 0
    item 1
    item 2
    item 3
    item 4
    item 5
    item 6
    item 7
    item 8
    item 9
    items:
    0,1,2,3,4
    Загрузка вниз
    Случай 4:
    Порядок выдачи
    пересобирается
    2/5

    View full-size slide

  49. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5
    item 6
    item 7
    item 8
    item 9
    items:
    0,1,2,3,4
    Загрузка вниз
    Случай 4:
    Порядок выдачи
    пересобирается
    3/5

    View full-size slide

  50. item 6
    item 1 (cached)
    item 4 (cached)
    item 5
    item 8
    item 9
    item 2 (cached)
    item 0 (cached)
    item 7
    item 3 (cached)
    Загрузка вниз
    Случай 4:
    Порядок выдачи
    пересобирается
    4/5

    View full-size slide

  51. item 6 (cached)
    item 1 (cached)
    item 4 (cached)
    item 5 (cached)
    item 8 (cached)
    item 9 (cached)
    item 2 (cached)
    item 0 (cached)
    item 7 (cached)
    item 3 (cached)
    items:
    5,6,7,8,9
    Загрузка вниз
    Случай 4:
    Порядок выдачи
    пересобирается
    5/5

    View full-size slide

  52. NSRange pageRange =
    NSMakeRange(startIndex, 5);
    NSArray *postIds = [snapshot
    subarrayWithRange:pageRange];

    View full-size slide

  53. item 0 (new)
    item 1 (new)
    item 2 (new)
    item 3 (new)
    item 4 (new)
    item 5 (new)
    item 7 (cached)
    item 8 (cached)
    item 9 (cached)
    item 6 (new)
    Обновление
    Случай 1:
    Появилось много
    новых элементов
    1/3

    View full-size slide

  54. item 7 (cached)
    item 8 (cached)
    item 9 (cached)
    offset: 0
    limit: 5
    item 0 (new)
    item 1 (new)
    item 2 (new)
    item 3 (new)
    item 4 (new)
    item 5 (new)
    item 6 (new)
    Обновление
    Случай 1:
    Появилось много
    новых элементов
    2/3

    View full-size slide

  55. item 0 (cached)
    item 1 (cached)
    item 2 (cached)
    item 3 (cached)
    item 4 (cached)
    item 5 (new)
    item 7 (cached)
    item 8 (cached)
    item 9 (cached)
    offset: 0
    limit: 5
    item 6 (new)
    Обновление
    Случай 1:
    Появилось много
    новых элементов
    3/3

    View full-size slide

  56. if (intersections == 0) {
    [self dropCache];
    }

    View full-size slide

  57. item 0 (cached)
    item 1 (cached)
    item 3 (cached)
    item 4 (cached)
    item 6 (cached)
    item 7 (cached)
    item 8 (deleted)
    item 9 (cached)
    item 5 (new)
    item 2 (new)
    Обновление
    Случай 2:
    Свободно
    изменяемая лента
    1/1

    View full-size slide

  58. for (ShortPost *post in diff) {
    [self updateCacheWith:post];
    }

    View full-size slide

  59. for (ShortPost *post in snapshot) {
    if (![cachedPosts containsObject:post]) {
    [self downloadPost:post];
    }
    }
    for (Post *post in cachedPosts) {
    if (![snapshot containsObject:post]) {
    [self deletePost:post];
    }
    }

    View full-size slide

  60. item 6
    item 1 (cached)
    item 4 (cached)
    item 5
    item 8
    item 9
    item 2 (cached)
    item 0 (cached)
    item 7
    item 3 (cached)
    Обновление
    Случай 3:
    Порядок выдачи
    пересобирается
    1/1

    View full-size slide

  61. NSString *lastModified = [self makeFeedHeadRequest];
    if (![lastModified isEqual:cachedLastModified]) {
    [self dropCache];
    [self obtainPostSnapshot];
    [self obtainFirstPage];
    }

    View full-size slide

  62. item 0 (cached)
    item 1 (updated)
    item 2 (cached)
    item 3 (updated)
    item 4 (updated)
    item 5
    item 6
    item 7
    item 8
    item 9
    Обновление
    Случай 4:
    Данные элементов
    меняются
    1/1

    View full-size slide

  63. for (NSUInteger i = 0; i < cachedPosts.count; i++) {
    Post *cachedPost = cachedPosts[i];
    ShortPost *post = snapshot[i];
    if (![cachedPost isEqual:post]) {
    [cachedPost updatePostWithShortPost:post];
    }
    }

    View full-size slide

  64. posts: NSArray
    snapshot: NSArray
    offset: NSUInteger
    maxCount: NSUInteger
    lastModified: NSDate
    Feed

    View full-size slide

  65. PostService
    loadPosts
    didLoad
    Cache

    View full-size slide

  66. PostService
    loadPosts
    didLoad
    Cache

    View full-size slide

  67. CacheTracker
    PostService
    loadPosts
    didLoad
    Cache

    View full-size slide

  68. PagingFacade
    PostService
    ViewController
    FeedService CacheUpdater
    SnapshotProcessor
    PagingCalculator

    View full-size slide

  69. @protocol PostPagingFacade
    - (void)loadNextPageWithBlock:(ErrorBlock)block;
    - (void)refreshCategoryWithBlock:(ErrorBlock)block;
    - (void)reloadCategoryWithBlock:(ErrorBlock)block;
    @end

    View full-size slide

  70. Клиент должен быть
    простым

    View full-size slide

  71. Техники пагинации
    Как подступиться
    Загрузка вниз
    Обновление ленты

    View full-size slide

  72. Egor Tolstoy @igrekde
    не смог придумать
    заключительную фразу

    View full-size slide