O cache do lado do servidor já funcionava há muito tempo - o cálculo determinístico de um mapa astral para a mesma date/time/lat/lon era devolvido do cache, em vez de ser recalculado. Mas o cliente não via isso. No dashboard, a coluna “cache hit %” mostrava – porque o backend registava o resultado do cache numa métrica interna, e não na resposta.
Agora, cada response inclui um dos três cabeçalhos:
X-Cache: HIT # обслужено з кешуX-Cache: MISS # обчислено з нуля, результат збереженоX-Cache: BYPASS # не кешується за дизайномEsta é uma pequena alteração - um cabeçalho mais uma coluna em api_request_log - mas abre toda uma classe de otimizações que antes eram um ponto cego.
Como funciona: exemplo
Seção intitulada “Como funciona: exemplo”curl -I -X POST https://api.astroway.info/v1/chart \ -H "X-Api-Key: aw_live_..." \ -H "Content-Type: application/json" \ -d '{ "date": "1990-05-15", "time": "14:30:00", "timezoneOffset": 3, "latitude": 50.45, "longitude": 30.52 }' | grep -i x-cache# X-Cache: MISSExecuta uma segunda vez com o mesmo corpo de pedido:
# X-Cache: HITPara endpoints como /v1/transits/now (tempo dinâmico) - X-Cache: BYPASS, porque o resultado depende do Date.now() atual e não faz sentido armazenar em cache.
Que endpoints devolvem o quê
Seção intitulada “Que endpoints devolvem o quê”Nem todos os endpoints são armazenados em cache - e isso é intencional. Distribuição:
- Cálculos determinísticos de mapa astral (
/v1/chart,/v1/houses,/v1/aspects,/v1/synastry,/v1/dasha/*,/v1/vargas/*) - são totalmente armazenados em cache. Padrão esperado: MISS → HIT. - Dependentes do tempo (
/v1/transits/now,/v1/horoscope/today,/v1/moon/phase-now) -BYPASS. O cache só seria válido até ao final do minuto, por isso é mais simples não armazenar em cache de todo. - Conteúdo gerado por IA (
/v1/horoscope/personal,/v1/interpret/*):BYPASS. As respostas do LLM não são determinísticas mesmo para o mesmoprompt, armazenar em cache = fixar a aleatoriedade. Endpointsde renderização (/v1/render/*):MISS/HITpara alguns,BYPASSpara aqueles que aceitampayloadsgrandes (caminho de eclipse com 500 pontos).
O marcador para um endpoint específico é visível diretamente na response - não precisas de ler a documentação para entender se é armazenado em cache ou não.
Impacto no Pricing: pedidos em cache ainda custam
Seção intitulada “Impacto no Pricing: pedidos em cache ainda custam”Esta é a coisa mais importante a entender - pedidos em cache continuam a deduzir créditos no mesmo tier que um MISS. Porquê:
- O nosso
pricingé calibrado para o valor de negócio doendpoint, não para o custo de CPU. O/v1/chartcusta o mesmo, quer o mapa tenha sido recalculado, quer tenha vindo do cache - o cliente recebeu o mesmo mapa astral. - Transparência. Não queremos uma situação em que uma base de utilizadores paga por um MISS e outra por um HIT (teoricamente por “sorte com o cache”).
Pricingprevisível. - Infraestrutura de cache: é infraestrutura, não um valor acrescentado. Nós subsidiamos isso no
tier.
Mas isso não significa que o X-Cache seja sem sentido no contexto do pricing - ele mostra visivelmente as oportunidades arquitetónicas para o cliente (vê a próxima secção).
O que fazer com isto no lado do cliente
Seção intitulada “O que fazer com isto no lado do cliente”Quatro padrões práticos:
1. Camada de cache do lado do cliente para MISS
Seção intitulada “1. Camada de cache do lado do cliente para MISS”Se vires X-Cache: MISS para um pedido que pode ser repetido no cenário (mapa astral da mesma pessoa), armazena-o localmente em Redis/Memcached/IndexedDB. O nosso cache do lado do servidor tem um TTL e uma política de evict - a tua camada de cliente com um TTL controlado evitará deduções de crédito desnecessárias.
import { Astroway } from "@astroway/sdk";
const cache = new Redis();const client = new Astroway({ apiKey: process.env.ASTROWAY_KEY, // optional: hook on response headers onResponse(req, res) { const cacheStatus = res.headers["x-cache"]; metrics.increment(`astroway.cache.${cacheStatus.toLowerCase()}`); },});
async function getChart(input: ChartInput) { const cacheKey = `chart:${hashChart(input)}`; const cached = await cache.get(cacheKey); if (cached) return JSON.parse(cached);
const chart = await client.chart.create(input); await cache.set(cacheKey, JSON.stringify(chart), "EX", 86400); return chart;}2. Batch + dedupe no backend
Seção intitulada “2. Batch + dedupe no backend”Se o teu serviço aceita pedidos em massa para os mesmos birth-data (campanha de onboarding onde os colegas testam com os mesmos dados de demonstração), usa um dedupe baseado em Promise:
const inFlight = new Map<string, Promise<Chart>>();
function getChart(input: ChartInput) { const key = hashChart(input); if (inFlight.has(key)) return inFlight.get(key)!;
const promise = client.chart.create(input); inFlight.set(key, promise); promise.finally(() => inFlight.delete(key)); return promise;}Isto não poupará créditos (cada chamada ainda conta), mas elimina congestionamentos durante picos de concurrency.
3. Pre-warm de caminhos críticos
Seção intitulada “3. Pre-warm de caminhos críticos”Se o produto tem mapas astrais rituais (signos populares de horóscopo diário), faz um pre-warm deles com um cron agendado. A primeira chamada do dia é um MISS, todas as seguintes até ao evict são HIT. O utilizador recebe uma response em subsegundos.
4. Dashboard insight: onde estás a pagar a mais
Seção intitulada “4. Dashboard insight: onde estás a pagar a mais”A nova coluna “cache hit %” em /dashboard/usage por endpoint mostra:
- HIT% = 90+ - o
endpointé bem armazenado em cache, provavelmente o mesmo mapa astral é enviado várias vezes. Considera oclient-side dedupe(#2). - HIT% = 0 e BYPASS: o
endpointnão é armazenado em cache por design (transits/now, AI). Isso é normal. - HIT% = 50% e MISS: metade dos pedidos tem parâmetros únicos, metade são repetições.
Client-side cacheque vale a pena. - HIT% baixo +
endpointdeterminístico: suspeito. Verifica se o teu cliente não está a adicionarfieldsaleatórios aopayload(timestamps,request-id), que entopem a chave do cache.
Implementação técnica: para os curiosos
Seção intitulada “Implementação técnica: para os curiosos”O tracking custa uma coluna em api_request_log.cache_status (enum: MISS|HIT|BYPASS, migration 030). A coluna é preenchida a partir do mesmo handler que toma a decisão de cache-lookup - não é feita uma consulta adicional à DB.
O GET /v1/me/usage/endpoints agora devolve o cache_hit_pct real em vez de null para cada endpoint no teu histórico. O método client.me.usage.endpoints() do SDK receberá o campo automaticamente (tipos na próxima versão do codegen).
O que se segue
Seção intitulada “O que se segue”A instrumentação do cache abre um nível intermédio de otimizações entre “no caching” e “full edge cache”. Os próximos passos no roadmap:
- Cabeçalho de
responseCache-Controlcom TTL real paraendpointsem cache - permitiráCDN/proxy-cachedo lado do cliente If-None-MatchETags: verificações MISS repetidas sem opayloadcompleto naresponse- Estatísticas de cache por utilizador no
dashboardcom a possibilidade deinvalidate(por exemplo, recalcular forçadamente umcertain chartapós a correção dabirth-time)
Documentação - /docs/api/ → Performance & Caching. Referência específica do X-Cache - na secção de headers de cada endpoint.
O mesmo Swiss Ephemeris que no Solar Fire - em 4 linhas de código.
Chave gratuita sem cartão. 5 000 chamadas por mês até o primeiro pagamento.