Bonjour, je m'appelle Dmitry Karlovsky et je ... n'aime pas lire des livres, car pendant que vous tournez la page, vous sortez d'une histoire fascinante. Et cela mérite une petite hésitation lorsque vous oubliez la fin de la dernière phrase de la page précédente, et vous devez revenir en arrière pour la relire. Et si ce n'est pas si effrayant avec des livres physiques, alors avec la publication d'un serveur de repos, tout est beaucoup plus triste - après tout, il y a maintenant des données sur la page, et après une seconde, c'est complètement différent. Réfléchissons à la façon dont cela s'est produit, qui est à blâmer et surtout - que faire.

Le problème
Nous devons donc émettre tous les messages de la requête "pagination", en commençant par les plus récents (les derniers changés par le haut ) ou dans un ordre délicat. Tout va bien, tant que nous avons moins d'une centaine de ces messages - nous faisons simplement une sélection dans la base de données et renvoyons les données:
Demande du client:
GET /message/text=/
Demande de base de données:
SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC
Schéma de réponse JSON pour le client:
Array<{ id : number , text : string }>
Mais le nombre de messages augmente et nous avons les problèmes suivants:
- Les requêtes de base de données deviennent plus lentes à mesure que davantage de données doivent être récupérées.
- L'envoi de données sur le réseau prend de plus en plus de temps.
- Le rendu de ces données sur le client s'allonge de plus en plus.
A partir d'un certain seuil, les retards deviennent si importants qu'il devient impossible d'utiliser notre site. Si, bien sûr, il ne s'était pas encore couché, fatigué d'un grand nombre de lourdes demandes parallèles.
La solution la plus simple, qui vient peut-être d'abord à l'esprit, et vous pouvez la trouver maintenant dans n'importe quel grille-pain - pour diffuser des données non pas en masse, mais divisées en pages. Tout ce que nous devons faire est de simplement jeter un paramètre supplémentaire du client dans la demande de base de données:
GET /message/text=/page=5/
SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC SKIP 5 * 10 LIMIT 10
SELECT count(*) FROM Message WHERE text LICENE ""
{ pageItems : Array<{ id : number , text : string }> totalCount : number }
Eh bien, oui, nous devions toujours recompter tous les messages afin que le client puisse dessiner une liste de pages ou calculer la hauteur du défilement virtuel, mais au moins nous n'avions pas besoin d'obtenir tous ces 100500 messages de la base de données.
Et tout irait bien si nous avions une sorte de forum non populaire pendant longtemps, des sujets plus pertinents. Mais ils nous écrivent et nous écrivent, écrivent et écrivent, et pendant que l'utilisateur lit la cinquième page, la liste des messages change au-delà de la reconnaissance: de nouveaux sont ajoutés et les anciens sont supprimés. Ainsi, nous obtenons deux types de problèmes du point de vue de l'utilisateur:
- Sur la page suivante, des messages peuvent apparaître à nouveau qui étaient déjà sur le précédent.
- L'utilisateur ne verra pas du tout certains messages, car il a réussi à passer de la page 6 à 5 exactement entre la transition de l'utilisateur de 5 à 6.
De plus, nous avons toujours des problèmes de performances. Chaque transition vers la page suivante conduit au fait que nous devons effectuer jusqu'à deux requêtes de recherche dans la base de données avec un nombre croissant d'éléments ignorés des pages précédentes.
Oui, et une implémentation compétente du côté client n'est pas si simple - vous devez toujours être préparé au fait que toute réponse du serveur peut renvoyer un nouveau nombre total de messages, ce qui signifie que nous devrons redessiner le paginateur et rediriger vers une autre page si la page actuelle est soudainement est vide. Et bien sûr, vous ne pouvez pas tomber en cas de doublons.
En outre, le client doit parfois mettre à jour les résultats de la recherche, mais la charge recevra toujours les données qu'elle peut déjà avoir des demandes précédentes.
Comme vous pouvez le voir, la pagination pose de nombreux problèmes. N'y a-t-il vraiment pas de meilleure solution?
Solution
Tout d'abord, notons que lorsque vous travaillez avec la base de données, il y a 2 opérations qui sont essentiellement différentes:
- Cherchez. Opération relativement lourde de recherche de pointeurs vers des données pour une requête.
- Échantillonnage. Une opération relativement simple d'obtention de données.
Ce serait idéal:
- Une fois la recherche et un endroit pour se souvenir de ses résultats sous la forme d'un instantané à un certain moment.
- Sélectionnez rapidement les données en petites portions selon les besoins.
Où stocker les instantanés? il y a 2 options:
- Sur le serveur. Mais ensuite, nous les obstruons avec un tas d'ordures avec des résultats de recherche qui doivent être nettoyés au fil du temps.
- Au client. Mais vous devez ensuite transférer immédiatement tous les instantanés au client.
Estimons la taille de l'instantané, qui n'est qu'une liste d'identifiants. Il est peu probable que l'utilisateur ait eu la patience de rouler au moins 100 pages sans utiliser de filtrage et de tri. Disons que nous avons 20 éléments par page. Chaque identifiant n'occupera pas plus de 10 octets dans la représentation json. Multipliez et obtenez pas plus de 20 Ko. Et probablement beaucoup moins. Il serait raisonnable de fixer une limite stricte sur la taille de la sortie en, disons, 1000 éléments.
GET /message/text=/
SELECT id FROM Message WHERE text LICENE "" ORDER BY changed DESC LIMIT 1000
Array<number>
Désormais, le client peut dessiner au moins un paginateur, au moins un parchemin virtuel, ne demandant des données que pour les identifiants qui l'intéressent.
GET /message=49,48,47,46,45,42,41,40,39,37/
SELECT FROM Message WHERE id IN [49,48,47,46,45,42,41,40,39,37]
Array< { id : number , text : string } | { id : number , error : string } >
Ce que nous obtenons finalement:
- API normalisée: recherchez séparément, sélectionnez les données séparément.
- Réduisez le nombre de requêtes de recherche.
- Vous ne pouvez pas demander des données qui ont déjà été téléchargées ou les mettre à jour en arrière-plan.
- Code relativement simple et universel côté client.
Parmi les lacunes, on peut noter que:
- Pour afficher quelque chose, l'utilisateur doit effectuer au moins 2 demandes consécutives.
- Il est nécessaire de traiter le cas lorsque l'identifiant est, et les données sur celui-ci ne sont plus disponibles.