Antipadrões populares: paginação

Olá, meu nome é Dmitry Karlovsky e eu ... não gosto de ler livros, porque enquanto você vira a página, você começa uma história fascinante. E vale a pena hesitar um pouco quando você esquecer o que terminou a última frase da página anterior e precisar voltar para lê-la novamente. E se não é tão assustador com livros físicos, com a emissão de um servidor de descanso tudo fica muito mais triste - afinal, agora existem alguns dados na página e, depois de um segundo, é completamente diferente. Vamos pensar em como aconteceu, quem é o culpado e o mais importante - o que fazer.


Puginadores diversos


O problema


Portanto, precisamos emitir todas as mensagens para a consulta "paginação", começando pelas mais recentes (as últimas foram alteradas a partir do topo ) ou em uma ordem complicada. Está tudo bem, desde que tenhamos menos de cem dessas mensagens - basta fazer uma seleção no banco de dados e retornar os dados:


Pedido do cliente:


GET /message/text=/ 

Solicitação do banco de dados:


 SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC 

Esquema de resposta JSON para o cliente:


 Array<{ id : number , text : string }> 

Mas o número de mensagens está crescendo e temos os seguintes problemas:


  1. As consultas ao banco de dados estão ficando mais lentas à medida que mais dados precisam ser coletados.
  2. O envio de dados pela rede está demorando cada vez mais.
  3. A renderização desses dados no cliente está ficando cada vez mais longa.

A partir de um certo limite, os atrasos se tornam tão significativos que tornam impossível o uso do nosso site. Se, é claro, ele ainda não havia se deitado, cansado de um grande número de pedidos pesados ​​paralelos.


A solução mais simples, que talvez venha à mente primeiro, e você pode encontrá-la agora em qualquer torradeira - para fornecer dados não todos em massa, mas divididos em páginas. Tudo o que precisamos fazer é lançar um parâmetro adicional do cliente na solicitação do banco de dados:


 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 } 

Bem, sim, ainda tivemos que recontar todas as mensagens para que o cliente pudesse desenhar uma lista de páginas ou calcular a altura do pergaminho virtual, mas pelo menos não precisamos obter todas essas 100500 mensagens do banco de dados.


E tudo ficaria bem se tivéssemos algum tipo de fórum não popular por muito tempo e não mais tópicos relevantes. Mas eles escrevem e escrevem para nós, escrevem e escrevem, e enquanto o usuário lê a quinta página, a lista de mensagens muda além do reconhecimento: novas são adicionadas e antigas são excluídas. Assim, temos dois tipos de problemas do ponto de vista do usuário:


  1. Na página seguinte, podem aparecer novamente mensagens que já estavam na anterior.
  2. O usuário não verá nenhuma mensagem, pois conseguiu passar da página 6 para a 5 exatamente entre a transição do usuário da 5 para a 6.

Além disso, ainda temos problemas de desempenho. Cada transição para a próxima página leva ao fato de que precisamos fazer até duas consultas de pesquisa no banco de dados com um número crescente de elementos ignorados das páginas anteriores.


Sim, e uma implementação competente no lado do cliente não é tão simples - você sempre deve estar preparado para o fato de que qualquer resposta do servidor pode retornar um novo número total de mensagens, o que significa que precisaremos redesenhar o paginador e redirecionar para outra página se a atual de repente está vazio. E é claro que você não pode cair no caso de duplicatas.


Além disso, às vezes o cliente precisa atualizar os resultados da pesquisa, mas a carga ainda receberá dados que já podem ter de solicitações anteriores.


Como você pode ver, a paginação tem muitos problemas. Realmente não há solução melhor?


Solução


Primeiro, vamos prestar atenção que, ao trabalhar com o banco de dados, existem 2 operações que são essencialmente diferentes:


  1. Pesquisar. Operação relativamente pesada de localização de ponteiros para dados para alguma consulta.
  2. Amostragem. Uma operação relativamente simples de realmente obter dados.

Seria ideal:


  1. Uma vez procure e em algum lugar para lembrar seus resultados na forma de um instantâneo em um determinado momento.
  2. Selecione rapidamente os dados em pequenas porções, conforme necessário.

Onde armazenar instantâneos? existem 2 opções:


  1. No servidor Mas então o entupimos com um monte de lixo com resultados de pesquisa que precisam ser limpos com o tempo.
  2. Para o cliente. Mas você deve transferir imediatamente todo o instantâneo para o cliente.

Vamos estimar o tamanho do instantâneo, que é apenas uma lista de identificadores. É duvidoso que o usuário tenha paciência para rolar pelo menos 100 páginas sem usar filtragem e classificação. Digamos que temos 20 elementos por página. Cada identificador não ocupará mais que 10 bytes na representação json. Multiplique e obtenha não mais que 20kb. E provavelmente muito menos. Seria razoável definir um limite rígido para o tamanho da saída em, digamos, 1000 elementos.


 GET /message/text=/ 

 SELECT id FROM Message WHERE text LICENE "" ORDER BY changed DESC LIMIT 1000 

 Array<number> 

Agora, o cliente pode desenhar pelo menos um paginador, pelo menos um pergaminho virtual, solicitando dados apenas para identificadores de seu interesse.


 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 } > 

O que finalmente obtemos:


  1. API normalizada: pesquise separadamente, selecione os dados separadamente.
  2. Minimize o número de consultas de pesquisa.
  3. Você não pode solicitar dados que já foram baixados ou atualizá-los em segundo plano.
  4. Código relativamente simples e universal no lado do cliente.

Das deficiências, podemos observar apenas:


  1. Para mostrar algo que o usuário precisa fazer pelo menos 2 solicitações consecutivas.
  2. É necessário lidar com o caso quando o identificador estiver e os dados nele não estiverem mais disponíveis.

Source: https://habr.com/ru/post/pt413133/


All Articles