Comment implémenter une pagination "cacheable" de contenu changeant fréquemment

Chaque fois que nous devons afficher une grande quantité de contenu stocké sur le backend, nous le scindons en morceaux, puis nous les chargeons l'un après l'autre. C'est une approche commune, et il y a de nombreuses raisons à son succès:

  1. Cela améliore l'expérience utilisateur. Le chargement d'une petite page prend moins de temps, de sorte que l'utilisateur peut commencer à consommer du contenu plus rapidement.
  2. Cela réduit la charge sur le réseau. Une seule page est petite et légère en termes de bande passante. De plus, nous pouvons optimiser la consommation de batterie et de réseau pour les appareils mobiles en ajustant la taille d'une seule page.
  3. Il réduit la charge sur l'arrière. Le traitement de petites pièces est plus rapide que les grandes. Les utilisateurs n’ont généralement pas besoin de tout le contenu en même temps. La charge moyenne par utilisateur est donc inférieure.

Tout le monde gagne. Dans la plupart des cas. Mais pas tous. Comme cela arrive souvent avec les solutions génériques, plus la solution est spécifique à un domaine, meilleure est la solution à trouver. Dans cet article, je souhaite partager une solution intéressante pour un tel cas.

Définir la tâche

Imaginons une collection qui change fréquemment au fil du temps. Par exemple, nous pourrions examiner une liste d’articles auxquels un utilisateur a appliqué sur Medium, une liste de souhaits dans une application de shopping ou un historique des actions d’autres utilisateurs en général. Les utilisateurs peuvent «ajouter» autant d'éléments à cette collection qu'ils le souhaitent.

Notre tâche consiste à afficher cette collection de manière pratique, tout en évitant d’abuser du réseau, et donc de la batterie et de la bande passante.

Problèmes de pagination

L'un des moyens de minimiser l'utilisation du réseau consiste à mettre en cache les réponses. La plupart des applications mobiles et Web s'appuient fortement sur le cache HTTP. Il enregistre les réponses pendant une période spécifiée dans l’en-tête de la réponse. Chaque fois qu'une application fait une demande, le client HTTP tente d'obtenir une réponse correspondante du cache. Ce n'est que s'il n'est pas disponible qu'il appelle réellement le serveur.

Parfois, ce type de mise en cache ne fonctionne pas bien pour le contenu paginé. Si la collection est modifiée fréquemment et que le contenu doit être à jour, il n’a aucun sens de mettre en cache la réponse. Par exemple, imaginons le scénario suivant:

  1. L'utilisateur ouvre la liste des articles qu'il a applaudis ici, sur support. La première page est extraite du backend.
  2. Après cela, l'utilisateur a recherché quelque chose de nouveau, a trouvé un autre article intéressant et a décidé de le recommander.
  3. Maintenant, ils veulent vérifier la liste des articles qu'ils ont recommandés à nouveau.

L'application doit effectuer la même demande pour la première page, mais le résultat est différent. La réponse ne peut pas être mise en cache.

Si votre tâche spécifique à un domaine vous permet de réorganiser les éléments de cette collection, votre problème est encore pire. Pour la même raison: la réponse change constamment.

Une autre approche

Examinons de plus près la première page de données extraites du backend. La réponse elle-même est une liste d'éléments dans un ordre particulier. Chaque élément est peu susceptible de changer. Qu'est-ce qui change est l'ordre des éléments et quels éléments sont sur cette liste.

Cela signifie que si nous pouvons extraire séparément l'ordre des ID d'élément et les détails de l'élément, le second appel peut potentiellement être mis en cache. En fait, même la mise en cache du deuxième appel n’est pas simple, mais nous y arriverons. Pour le moment, faisons une demande distincte pour chaque article:

Comme vous pouvez le constater sur le diagramme ci-dessus, il est peu probable que les éléments changent. Nous pouvons donc mettre en cache les appels de détail d'élément. Malheureusement, un tel partage multipliera par un ordre de grandeur la quantité d'appels réseau. Mais il y a quelque chose que nous pouvons faire à ce sujet.

Que voulons-nous réellement?

Si nous demandons simplement un tas d’articles, nous rencontrerons le même problème que l’approche de pagination générique. Le cache HTTP n’agira pas comme nous le voudrions, alors écrivons le nôtre en utilisant une logique similaire mais plus délibérée.

Ce cache ne va pas stocker des lots, mais des éléments uniques pendant un laps de temps donné. Nous allons prendre la réponse, accéder à ses en-têtes HTTP et récupérer des informations sur le temps de mise en cache. Ensuite, nous placerons chaque élément individuellement dans le cache. La prochaine fois que nous aurons besoin d'afficher des éléments, nous pourrons facilement accéder aux éléments en cache et demander le reste. Dans le code, cela semble plus facile que ça en a l'air:

Voyons rapidement le code. La méthode getOrderedItemsIds () renvoie l'ordre des éléments et est paginée. La partie la plus importante est la méthode getItemsByIds (). C’est un endroit où nous vérifions d’abord quels éléments se trouvent dans le cache, puis demandons le reste au serveur. Veuillez noter que par souci de simplicité, le code ci-dessus est synchrone et ne sera probablement pas compilé.

Après avoir implémenté cette approche, l’ajout d’un nouvel article à l’en-tête de la liste entraînera une demande de commande d’un des ID d’article et des détails pour le nouvel article. Le reste vient de la cache.

Une paire d'appels similaire se produira pour chaque page consécutive. L'idée principale est que nous pouvons récupérer la plupart des détails des éléments du cache. Mais malheureusement, nous devons demander la commande des ID d’article pour chaque page.

Fais le mieux

Les ID d'élément sont généralement un petit objet, comme une chaîne ou un identificateur unique universel (UUID). Par conséquent, nous pouvons envoyer de plus grandes pages. L'augmentation du nombre d'identifiants d'articles renvoyés par un appel d'ordre réduit le nombre d'appels sans abus de la bande passante du réseau.

Par exemple, au lieu de demander 20 à 40 ID d’article, nous pouvons demander 100-200. Plus tard, la couche d'interface utilisateur peut modérer le nombre de détails d'éléments à afficher et les demander en conséquence. Ensuite, une séquence d'appels ressemblera à ceci:

  1. Demander les 100 premiers ID d’articles et les conserver en mémoire.
  2. Demandez des détails pour les 20 premiers éléments (cachez-les bien sûr) et affichez-les à l'utilisateur.
  3. Une fois que l'utilisateur a parcouru les 20 premiers articles, demandez le deuxième lot de 20 détails d'article.
  4. Répétez l'étape précédente trois fois de plus et procédez de la même manière pour la page d'ID d'éléments suivante.

Maintenant, ajouter un nouvel élément au sommet entraîne toujours deux demandes (une pour les ID et l'autre pour les détails de ce nouvel élément). Mais nous n’aurons pas besoin de demander la page suivante pendant un moment, car les pages sont plus grandes. Nous n’aurons également pas besoin de demander les détails des éléments car ils sont mis en cache!

Petit disclaimer: la manière dont l'interface utilisateur modère la demande de détails d'élément peut être plus intéressante. Par exemple, il peut ignorer les demandes de certains éléments si l'utilisateur fait défiler trop rapidement, car ils ne les intéressent pas. Mais cela mérite un tout autre article.

Conclusion

Les solutions générales ne sont généralement pas optimisées pour des cas particuliers. Connaître les spécificités peut nous aider à écrire des applications plus rapides et plus optimisées. Dans ce cas, il était crucial de savoir que le contenu change fréquemment et qu'il s'agit d'un ensemble d'éléments avec des identifiants. Passons en revue toutes les améliorations apportées par la nouvelle approche:

  1. Demander l'ordre des éléments séparément nous permet de mettre en cache les détails, même si nous avons dû écrire un cache HTTP modifié.
  2. La mise en cache des détails d’éléments entraîne une utilisation réduite de la bande passante.
  3. L'augmentation de la taille des pages pour la demande de commande réduit le nombre d'appels.

Une dernière chose: les optimisations sont géniales et j’ai trouvé passionnant d’écrire du code efficace, mais n’oubliez pas de le profiler au préalable. Une optimisation prématurée pourrait poser problème et nous devrions tous l’éviter.

Merci pour le temps que vous avez lu cet article. Si vous l’aimez, n’oubliez pas de cliquer sur le ci-dessous.