API REST
Quando processos (programas em execução) precisam trocar informações em uma rede, existem diversas arquiteturas para organizar essa comunicação. O modelo mais comum envolve dois papeis: clientes e servidores.
Os servidores disponibilizam uma Interface de Programação de Aplicações (API), que pode ser acessada pela rede pelos clientes.
Info!
Essencialmente, a API que o servidor expõe é o que se define como o serviço em questão.
O FastAPI é um framework muito utilizado para construção de APIs web utilizando Python. Você já trabalhou com FastAPI na disciplina de Megadados e agora iremos considerar mais detalhes sobre esse framework.
Dica
O FastAPI facilita a criação desses endpoints RESTful.
REST é um estilo de arquitetura que define um conjunto de restrições para a criação de serviços web, sendo as mais notáveis o uso de métodos HTTP (como GET, POST, PUT, DELETE) e a manipulação de recursos (dados) por meio de URLs.
Configurar Ambiente
Vamos configurar o ambiente necessário para a aula. Utilizaremos a mesma pasta da aula anterior.
Exercício
Configurar Variáveis de Ambiente
Vamos configurar as variáveis de ambiente para o PostgreSQL.
Exercício
Iniciar Serviços
Exercício
Testar API
Após iniciar os serviços, a API estará disponível no endereço http://localhost:8000/.
Exercício
Exercício
Answer
Os parâmetros skip e limit são utilizados para paginação dos resultados retornados pela API.
-
skip: Indica o número de registros a serem ignorados no início da lista. Isso é útil para pular um certo número de resultados, permitindo que você acesse páginas subsequentes de dados. -
limit: Define o número máximo de registros a serem retornados na resposta. Isso ajuda a controlar a quantidade de dados recebidos em uma única solicitação, evitando sobrecarga e melhorando o desempenho.
Juntos, esses parâmetros permitem que os clientes da API naveguem eficientemente através de grandes conjuntos de dados, solicitando apenas as partes necessárias em cada chamada.
Exercício
Answer
Para listar os status da quarta página, considerando que cada página contém 100 status, você deve calcular o valor do parâmetro skip como (página - 1) * tamanho_da_página.
Portanto, para a quarta página:
skip = (4 - 1) * 100 = 300limit = 100
A URL completa seria:
Testes com ab
O Apache Benchmark (ab) é uma ferramenta útil para realizar testes de carga em servidores web.
Vamos utilizá-la para testar o desempenho do nosso endpoint GET /api/v1/status/.
Exercício
Documentação
Para mais informações sobre o Apache Benchmark, consulte a documentação oficial.
Exercício
Vamos tentar entender o que aconteceu.
Exercício
Exercício
Exercício
Answer
A consulta SQL está utilizando o comando LIMIT para limitar o número de registros retornados a 100, e o comando OFFSET para pular os primeiros 50.000.000 registros.
O problema com essa abordagem é que o banco de dados ainda precisa processar todos os registros até o ponto do OFFSET, o que pode ser muito ineficiente, especialmente quando o valor do OFFSET é grande. Isso pode resultar em tempos de resposta lentos, pois o banco de dados tem que percorrer uma grande quantidade de dados antes de retornar os resultados desejados.
Alternativa ao uso de OFFSET
Uma alternativa mais eficiente seria utilizar um critério de ordenação e um marcador (como um ID ou timestamp) para buscar os registros a partir de um ponto específico, evitando assim a necessidade de pular um grande número de registros.
Este conceito é conhecido como paginação baseada em cursor (cursor-based pagination ou cursor-based sorting) e pode melhorar significativamente o desempenho das consultas em grandes conjuntos de dados.
Suponha que queremos paginar os status com base no campo time. Ao invés de buscarmos a décima página utilizando OFFSET, poderíamos buscar os status onde o campo time é maior que o último valor de time da página anterior (nona página).
Considere o seguinte exemplo:
EXPLAIN (ANALYZE, BUFFERS)
SELECT
status.station_id AS status_station_id,
status.bikes_available AS status_bikes_available,
status.docks_available AS status_docks_available,
status.time AS status_time,
status.category1 AS status_category1,
status.category2 AS status_category2
FROM
status
LIMIT 100 OFFSET 50000000;
EXPLAIN (ANALYZE, BUFFERS)
SELECT
status.station_id AS status_station_id,
status.bikes_available AS status_bikes_available,
status.docks_available AS status_docks_available,
status.time AS status_time,
status.category1 AS status_category1,
status.category2 AS status_category2
FROM
status
WHERE time > '2015-01-01 10:00:00.000'
ORDER BY time
LIMIT 100
Exercício
Answer
A consulta utilizando o cursor geralmente apresenta um plano de execução mais eficiente e um tempo de resposta significativamente menor em comparação com a consulta que utiliza OFFSET.
Isso ocorre porque a consulta com cursor evita a necessidade de percorrer todos os registros até o ponto do OFFSET, resultando em menos leituras de dados e melhor desempenho geral.
Vantagens da paginação baseada em cursor:
- ✅ Performance constante
- ✅ Escalável para milhões de registros
- ✅ Menor uso de recursos do banco
Desvantagens da paginação baseada em cursor:
- ❌ Não permite pular páginas diretamente
- ❌ Cliente precisa manter o último cursor
Exercício
Answer
Sim, a performance permanece constante independentemente do valor do cursor utilizado.
Isso é possível porque a consulta com cursor utiliza um critério de ordenação e um marcador para buscar os registros a partir de um ponto específico, evitando a necessidade de pular um grande número de registros.
Como resultado, o banco de dados não precisa processar todos os registros anteriores ao cursor, o que mantém o tempo de resposta estável mesmo quando navegamos por grandes conjuntos de dados.
Info!
A performance constante é alcançada porque a consulta com cursor se beneficia do índice BTree no atributo time, permitindo acesso direto aos registros relevantes sem a sobrecarga de percorrer registros desnecessários.
WooooW
Árvores BTree são naturalmente ordenadas, o que as torna ideais para operações de busca e ordenação em bancos de dados relacionais.
Paginação por Cursor na API
Exercício
Answer
@router.get("/", response_model=List[Status], summary="Get all status records")
def get_status_records(
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
cursor: datetime = Query(None, description="Cursor for pagination (time field)"),
station_id: int = Query(None, description="Filter by station ID"),
db: Session = Depends(get_db)
):
"""
Retrieve all status records with cursor-based pagination and optional filtering.
- **limit**: Maximum number of records to return (max 1000)
- **cursor**: Cursor for pagination based on time field (optional, returns records after this timestamp)
- **station_id**: Filter by specific station ID (optional)
"""
query = db.query(StatusModel)
if station_id is not None:
query = query.filter(StatusModel.station_id == station_id)
if cursor is not None:
query = query.filter(StatusModel.time > cursor)
status_records = query.order_by(StatusModel.time).limit(limit).all()
return status_records
Exercício
Answer
Após implementar a paginação por cursor, ao testar o endpoint GET /api/v1/status/, os resultados retornados estão corretos e a performance é significativamente melhorada em comparação com a abordagem anterior utilizando OFFSET.
A utilização do campo time como cursor permite que as requisições subsequentes sejam eficientes, mantendo um tempo de resposta constante mesmo ao navegar por grandes conjuntos de dados.
Onde se aplica?
Este tipo de paginação é frequentemente utilizado em páginas com scroll infinito (infinite scroll), onde novos dados são carregados conforme o usuário rola a página para baixo, proporcionando uma experiência de usuário fluida e eficiente.