Video thumbnail

    Seu back-end tem paginação? Então veja isso! (cursor-based vs offset-based pagination)

    Valuable insights

    1.Paginação Offset-Based: Limitações em Escala: A paginação Offset-Based é funcional para tabelas pequenas, mas seu desempenho degrada drasticamente em grandes volumes de dados ou ao pular muitas páginas, consumindo recursos excessivos do banco de dados e lentidão na aplicação.

    2.Impacto do Offset Alto na Performance: Valores elevados de OFFSET forçam o banco de dados a processar e descartar registros desnecessariamente, resultando em queries demoradas e sobrecarga significativa de RAM e CPU, tornando a abordagem insustentável para grandes conjuntos de dados.

    3.Paginação Cursor-Based como Solução Eficiente: A paginação Cursor-Based, utilizando cláusulas WHERE em campos ordenáveis como ID ou data de publicação, oferece uma alternativa superior para tabelas volumosas e sistemas de "scroll infinito", otimizando o acesso aos dados ao evitar o descarte de registros já processados.

    4.Diferença Crucial entre WHERE e OFFSET: A cláusula WHERE filtra e exclui dados antes do processamento principal da query, enquanto OFFSET atua como um filtro pós-consulta, descartando registros somente após eles serem buscados. Essa distinção fundamental explica a ineficiência do OFFSET em larga escala.

    5.Ordenabilidade dos Cursos para Eficácia: A implementação eficaz da paginação baseada em cursor exige que o campo usado como cursor seja ordenável. Enquanto UUID v4 é inadequado, o UUID v7 é time-sortable, permitindo o uso de IDs como cursores de forma eficiente, baseando-se no tempo de criação.

    6.Otimização e Implementação de Cursor-Based: A implementação de Cursor-Based envolve buscar N+1 registros para determinar a existência de uma próxima página (`hasNextPage`) e extrair o `nextCursor` do último item. Essa técnica otimiza o carregamento progressivo e a experiência de usuário em interfaces de "scroll infinito".

    O que é Paginação Offset e Limit?

    A paginação utilizando os parâmetros `OFFSET` e `LIMIT` é uma abordagem comum em desenvolvimento back-end, conhecida como paginação "Offset-Based". Embora amplamente utilizada, essa técnica pode gerar sérios problemas de performance quando aplicada a tabelas com um grande volume de registros, especialmente ao tentar acessar páginas que estão muito além da primeira. Nesses cenários, a consulta se torna exponencialmente mais lenta, impactando diretamente a experiência do usuário e a saúde do banco de dados.

    Por que Offset/Limit falha em tabelas grandes?

    Quando se trabalha com tabelas que contêm milhões de registros, a paginação "Offset-Based" se mostra ineficiente. A falha reside no fato de que, para retornar um conjunto específico de dados após pular uma grande quantidade de registros, o banco de dados precisa processar todos os itens anteriores ao `OFFSET` definido. Isso leva a um problema de performance considerável, principalmente quando se busca por páginas avançadas na listagem de dados, como a página 100 ou 1000, onde a sobrecarga de processamento é drástica.

    Como Offset/Limit afeta a performance?

    O impacto da paginação "Offset-Based" na performance é notável. Uma query simples que busca dez primeiros registros pode levar 1 segundo. No entanto, ao tentar pular 100.000 registros, o tempo de execução pode saltar para 4 segundos. Com um `OFFSET` de 1 milhão de registros, a query pode demorar até 40 segundos ou mais. Além da lentidão, essa abordagem consome significativamente mais RAM e processamento do banco de dados, podendo levar a custos adicionais ou até mesmo à queda da aplicação se não for monitorada.

    Offset
    Tempo de Execução (aproximado)
    10 registros
    1 segundo
    100.000 registros
    4 segundos
    500.000 registros
    24 segundos
    1.000.000 registros
    40 segundos

    Qual o impacto de Offset alto?

    Um `OFFSET` elevado não apenas aumenta exponencialmente o tempo de resposta das consultas, tornando a experiência do usuário péssima, mas também sobrecarrega os recursos do banco de dados de maneira crítica. O processamento de milhões de registros para descartar a maioria deles resulta em alto consumo de RAM e CPU. Essa ineficiência pode levar a um aumento nos custos de infraestrutura e, em casos extremos, causar instabilidade ou até mesmo a indisponibilidade da aplicação devido à exaustão dos recursos do servidor de banco de dados.

    Com certeza, se é uma query que você vai estar executando muitas vezes na sua aplicação, além de ter uma experiência péssima para o seu usuário, porque vai demorar para caramba, você também acaba consumindo recursos demais do banco de dados, fazendo quem sabe até tua aplicação gastar a mais ou até cair.

    Por que Offset é lento no SQL?

    A lentidão do `OFFSET` no SQL reside na ordem de execução das cláusulas em uma query. Diferentemente da cláusula `WHERE`, que é processada em primeiro lugar e tem a função de diminuir a quantidade de dados a serem atingidos e buscados do banco, o `OFFSET` atua em uma etapa posterior. Isso significa que, antes mesmo de pular os registros, o banco de dados precisa buscar e carregar uma quantidade muito maior de dados, para só então descartá-los.

    Execução de Cláusulas SQL

    A ordem de execução no SQL determina a eficiência da query. A cláusula `WHERE` é executada primeiro porque ela otimiza o processo ao reduzir o conjunto de dados desde o início. Por exemplo, ao usar `WHERE author_ID = 1`, apenas os posts daquele autor são considerados. Em contrapartida, o `OFFSET` não é uma condição de exclusão inicial; ele não filtra os dados na fonte, o que o torna menos eficiente para tabelas grandes, pois os dados já foram buscados antes de serem desconsiderados.

    Qual a diferença entre WHERE e OFFSET?

    A diferença fundamental entre `WHERE` e `OFFSET` reside na forma como eles interagem com os dados. A cláusula `WHERE` tem a função de diminuir a quantidade de dados efetivamente buscados de dentro da tabela, atuando como um filtro pré-query que reduz o conjunto de resultados. Já o `OFFSET` não diminui os dados buscados, mas sim a quantidade de dados que são retornados ao usuário, agindo como um filtro pós-query. O banco de dados não mantém uma referência para saber quais registros pular, ele processa e então os descarta.

    • `WHERE` diminui a quantidade de dados buscados da tabela.
    • `OFFSET` diminui a quantidade de dados retornados pela query.
    • `WHERE` atua como um filtro pré-query, otimizando a busca.
    • `OFFSET` atua como um filtro pós-query, processando dados desnecessários antes de descartá-los.

    Offset é um filtro pós-query?

    Sim, o `OFFSET` é tratado como um filtro pós-query no banco de dados. Isso significa que, se você tem uma tabela com, por exemplo, 500 registros e aplica um `OFFSET 400`, o banco de dados não sabe quais registros pular previamente. Ele irá buscar os 500 registros e, somente depois de tê-los em memória ou em algum estado intermediário, irá descartar os primeiros 400, retornando apenas os 100 restantes. A cláusula `LIMIT`, por sua vez, age como uma exclusão, definindo o número máximo de registros a serem buscados, o que a torna eficiente e geralmente utilizada em qualquer tipo de paginação.

    Quando usar Paginação Offset-Based?

    A paginação "Offset-Based" não é inerentemente ruim e tem seus casos de uso apropriados. É perfeitamente utilizável em grande parte das aplicações, especialmente quando as tabelas não são extremamente grandes, as consultas já possuem filtros `WHERE` abrangentes, ou o número de registros por página é relativamente baixo (por exemplo, 100, 200 ou 1000). Nesses cenários, o impacto na performance da aplicação tende a ser mínimo e a simplicidade de implementação justifica seu uso.

    Quando evitar Offset-Based e usar Cursor?

    Deve-se evitar a paginação "Offset-Based" em dois cenários principais: quando se lida com uma quantidade "anormal" de dados em uma tabela (milhões ou bilhões de registros) ou ao implementar uma paginação via "scroll infinito". Em ambos os casos, a ineficiência do `OFFSET` em pular grandes quantidades de dados e a perda de estado ao recarregar a página (como no `Twitter` ou `Instagram`) tornam a experiência do usuário e a performance do sistema insustentáveis. Nestas situações, a paginação "Cursor-Based" é a alternativa superior.

    Paginação em Scroll Infinito

    No "scroll infinito", como visto em plataformas como Twitter ou Instagram, não há uma noção clara de qual página o usuário está. Conforme o usuário rola, mais conteúdo é carregado dinamicamente. A paginação "Offset-Based" não se adequa a este modelo, pois não mantém o estado da página na URL (perdendo o progresso ao atualizar a página) e não se beneficia do conceito de pular um número fixo de registros, que seria ineficiente e complexo de gerenciar neste contexto.

    O que é Paginação Cursor-Based?

    A paginação "Cursor-Based" representa uma alternativa robusta à abordagem "Offset-Based". Em vez de pular uma quantidade de registros com `OFFSET`, ela utiliza uma cláusula `WHERE` em um campo ordenável da tabela para definir a partir de qual ponto os próximos registros devem ser buscados. Como a cláusula `WHERE` exclui dados antes do processamento, essa técnica é intrinsecamente mais eficiente, pois evita a busca desnecessária de um grande volume de informações, otimizando o acesso ao banco de dados.

    Como usar ID como cursor?

    Uma forma eficaz de implementar a paginação "Cursor-Based" é utilizando o `ID` dos registros como cursor. Em tabelas onde o `ID` é auto incremental (como em `PostgreSQL`, `SQLite` ou `MySQL`), é possível buscar os próximos 20 posts, por exemplo, usando uma condição `WHERE ID > [último_ID_exibido]`. Essa abordagem garante que o banco de dados procure apenas os registros com IDs maiores que o último visto, o que significa que ele não precisa processar os registros anteriores, resultando em uma consulta muito mais performática e eficiente.

    Usar data de publicação como cursor?

    Além do `ID`, outros campos ordenáveis podem servir como cursores, como a data de publicação (`published_at`). Se a ordem de exibição dos posts é determinada por este campo, ele se torna um excelente candidato a cursor. Ao buscar posts, é possível solicitar ao banco de dados que retorne apenas aqueles com `published_at` maior que a data de publicação do último post exibido. O campo que determina o ponto de partida da exibição é chamado de cursor, e o uso de datas ou outros campos adequados oferece flexibilidade e eficiência na paginação.

    Cursor-Based permite pular páginas?

    Uma das principais limitações da paginação "Cursor-Based" é a impossibilidade de navegar diretamente para uma página específica. Ao contrário da paginação "Offset-Based", onde é possível calcular o `OFFSET` para saltar para, digamos, a página 21 (pulando 400 itens se houver 20 por página), o modelo "Cursor-Based" opera sequencialmente. Ele busca o próximo conjunto de dados a partir de um ponto de referência (o cursor), não de um número de página arbitrário. É por essa razão que é particularmente adequado para interfaces de "scroll infinito", onde a navegação sequencial é o comportamento esperado.

    Como funciona o scroll infinito?

    O "scroll infinito" funciona de forma otimizada com a paginação "Cursor-Based". Inicialmente, uma quantidade limitada de dados é carregada. Quando o usuário rola a tela até o final, a aplicação obtém o cursor (por exemplo, o `published_at` do último item exibido) e o envia para a API. A API, por sua vez, realiza uma nova consulta ao banco de dados, buscando apenas os registros que possuem um valor de cursor maior que o recebido, limitando a quantidade de novos itens. Esse processo se repete, carregando dados progressivamente sem a necessidade de números de página, proporcionando uma experiência de usuário fluida e contínua.

    ID como cursor: qual o problema?

    Embora o `ID` seja um candidato natural para cursor, a utilização de IDs que não são naturalmente ordenáveis, como o `UUID v4`, pode ser um problema. Para que a paginação por cursor funcione, o campo utilizado como cursor deve ser capaz de estabelecer uma ordem clara entre os registros. Datas são ordenáveis, e IDs numéricos auto incrementais também. Contudo, `UUID v4` gera identificadores que não contêm informações de tempo ou sequência, tornando impossível determinar qual `UUID` veio antes do outro, invalidando seu uso direto como cursor para ordenação.

    UUID v7 resolve IDs não ordenáveis?

    Sim, o `UUID v7` emerge como uma solução para o problema de IDs não ordenáveis em paginação por cursor. Diferente do `UUID v4`, que gera identificadores aleatórios, o `UUID v7` é projetado para ser "time-sortable". Isso significa que os primeiros caracteres de um `UUID v7` incorporam informações de data e tempo em que o identificador foi gerado. Consequentemente, ao ordenar uma lista de `UUID v7`, os IDs criados mais recentemente aparecerão depois dos mais antigos, permitindo seu uso eficaz como cursores para paginação, combinando unicidade com ordenabilidade temporal.

    Como refatorar para Cursor-Based?

    A refatoração de uma paginação "Offset-Based" para "Cursor-Based" envolve a substituição da cláusula `OFFSET` por uma condição `WHERE` que utilize um campo ordenável como cursor. Por exemplo, em vez de pular um número fixo de registros, a query passará a buscar itens cuja data de publicação (`published_at`) seja maior que a data de publicação do último item da página anterior. Esta mudança fundamental garante que o banco de dados não precise processar e descartar dados desnecessariamente, otimizando o desempenho da consulta, especialmente em cenários de alta volumetria de dados.

    Cursor-Based: como implementar e otimizar?

    A implementação da paginação "Cursor-Based" se dá através de uma cláusula `WHERE` que filtra os registros com base no valor do cursor (e.g., `published_at > '[valor_do_cursor]'`). Essa abordagem elimina a necessidade de um `OFFSET`, resultando em um tempo de execução significativamente menor e menor consumo de recursos do banco de dados, pois o banco não precisa carregar e descartar milhões de registros. Para otimizar a experiência do usuário e a funcionalidade da API, é uma boa prática buscar `N+1` registros em vez de apenas `N`.

    Otimização com N+1 Registros

    A técnica de buscar `N+1` registros para `N` itens por página é uma otimização chave. Isso permite que a aplicação determine facilmente se existe uma próxima página de resultados sem a necessidade de uma consulta adicional. Se a quantidade de posts retornada for maior do que a solicitada pelo usuário (N), significa que há mais dados disponíveis, indicando a existência de uma próxima página. Caso contrário, não há mais registros a serem carregados, otificando a interface do usuário e o consumo de recursos da API.

    Como saber se há próxima página?

    Para determinar se existe uma próxima página de resultados na paginação "Cursor-Based", a estratégia de buscar `N+1` registros é crucial. Por exemplo, se o usuário solicitou 10 itens por página, a API deve tentar buscar 11. Se 11 posts forem retornados, significa que há mais dados além da página atual, e a aplicação pode informar ao front-end que uma próxima página existe. Essa abordagem é eficiente porque evita a necessidade de uma segunda consulta para verificar a existência de mais dados, otimizando a comunicação entre back-end e front-end.

    Como implementar hasNextPage e nextCursor?

    A implementação de `hasNextPage` e `nextCursor` é feita da seguinte forma: uma variável `hasNextPage` é inicializada como `false`. Se o número de posts retornados pela consulta for maior do que o número de posts solicitados (N), então `hasNextPage` é definida como `true`. O `nextCursor` é extraído do campo cursor (por exemplo, `published_at`) do último elemento do array de posts. Isso permite que o front-end saiba se deve continuar carregando mais itens (no caso de "scroll infinito") e qual cursor usar para a próxima requisição, evitando buscas desnecessárias.

    • Inicialize `hasNextPage` como `false`.
    • Compare a contagem de posts retornados com o limite solicitado (`items_per_page`). Se a contagem for maior, defina `hasNextPage` como `true`.
    • O `nextCursor` será o valor do campo cursor (ex: `published_at`) do último post retornado na lista.

    Quando usar Cursor ou Offset?

    A escolha entre paginação "Cursor-Based" e "Offset-Based" depende das necessidades da aplicação. A paginação "Offset-Based" é adequada para painéis administrativos ou interfaces onde o usuário precisa navegar para páginas específicas e os datasets são de tamanho moderado. Já a "Cursor-Based" é ideal para aplicações com grandes volumes de dados, "scroll infinito", ou APIs onde a performance é crítica. Ferramentas como TanStack Query no React, por exemplo, já utilizam a lógica de cursor em seu `useInfinityQuery`, demonstrando sua eficácia e relevância para o desenvolvimento front-end moderno.

    Useful links

    These links were generated based on the content of the video to help you deepen your knowledge about the topics discussed.

    Drizzle ORM
    PostgreSQL Official Website
    SQLite Official Website
    MySQL Official Website
    TanStack Query Official Documentation
    RFC 4122 (UUID v4)
    RFC 9562 (UUID v7)
    This article was AI generated. It may contain errors and should be verified with the original source.
    VideoToWordsClarifyTube

    © 2025 ClarifyTube. All rights reserved.