Cache po stronie serwera działał u nas od dawna – zdeterminowane obliczenie wykresu dla tej samej daty/czasu/szerokości/długości geograficznej jest zwracane z cache’u, a nie obliczane od nowa. Ale klient tego nie widział. W panelu kontrolnym kolumna „cache hit %” pokazywała –, ponieważ backend logował wynik cache’owania do wewnętrznej metryki, a nie do odpowiedzi.
Teraz każda odpowiedź zawiera jeden z trzech nagłówków:
X-Cache: HIT # обслужено з кешуX-Cache: MISS # обчислено з нуля, результат збереженоX-Cache: BYPASS # не кешується за дизайномTo mała zmiana – nagłówek plus jedna kolumna w api_request_log – ale otwiera całą klasę optymalizacji, które wcześniej były martwym punktem.
Jak to działa: przykład
Dział zatytułowany „Jak to działa: przykład”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: MISSWykonaj drugi raz z tym samym ciałem zapytania:
# X-Cache: HITDla endpointów typu /v1/transits/now (czas dynamiczny) – X-Cache: BYPASS, ponieważ wynik zależy od bieżącego Date.now() i cache’owanie nie ma sensu.
Które endpointy co zwracają
Dział zatytułowany „Które endpointy co zwracają”Nie wszystkie endpointy są cache’owane – i to celowo. Podział:
- Zdeterminowane obliczenia wykresów (
/v1/chart,/v1/houses,/v1/aspects,/v1/synastry,/v1/dasha/*,/v1/vargas/*) – są w pełni cache’owane. Oczekiwany wzorzec MISS → HIT. - Time-dependent (
/v1/transits/now,/v1/horoscope/today,/v1/moon/phase-now) –BYPASS. Cache mógłby być poprawny tylko do końca minuty, dlatego prościej jest w ogóle nie cache’ować. - AI-generated content (
/v1/horoscope/personal,/v1/interpret/*):BYPASS. Odpowiedzi LLM nie są zdeterminowane nawet dla tego samego promptu, cache’owanie = utrwalanie losowości. - Render endpoints (
/v1/render/*):MISS/HITdla niektórych,BYPASSdla tych, które przyjmują duże payloady (ścieżka zaćmienia z 500 punktami).
Wskaźnik dla konkretnego endpointu jest widoczny od razu w odpowiedzi – nie musisz czytać dokumentacji, aby zrozumieć, czy jest cache’owany, czy nie.
Wpływ na ceny: cache’owane zapytania nadal kosztują
Dział zatytułowany „Wpływ na ceny: cache’owane zapytania nadal kosztują”To najważniejsza rzecz do zrozumienia – cache’owane zapytania nadal odejmują kredyty na tym samym poziomie (tier), co MISS. Dlaczego:
- Nasze ceny są skalibrowane na wartość biznesową endpointu, a nie na koszt CPU.
/v1/chartkosztuje tyle samo, niezależnie od tego, czy wykres został obliczony od nowa, czy pochodzi z cache’u – klient otrzymał ten sam wykres. - Przejrzystość. Nie chcemy sytuacji, w której jedna baza użytkowników płaci za MISS, a inna za HIT (teoretycznie z powodu „szczęścia z cache’em”). Ceny są przewidywalne.
- Infrastruktura cache’u: to infrastruktura, a nie wartość dodana. Subwencjonujemy ją w ramach tieru.
Ale to nie znaczy, że X-Cache jest bez znaczenia w kontekście cen – widocznie pokazuje możliwości architektoniczne dla klienta (patrz następna sekcja).
Co z tym zrobić po stronie klienta
Dział zatytułowany „Co z tym zrobić po stronie klienta”Cztery praktyczne wzorce:
1. Warstwa cache’u po stronie klienta dla MISS
Dział zatytułowany „1. Warstwa cache’u po stronie klienta dla MISS”Jeśli widzisz X-Cache: MISS dla zapytania, które w scenariuszu może się powtórzyć (np. mapa urodzeniowa tej samej osoby), cache’uj lokalnie w Redis/Memcached/IndexedDB. Cache po stronie serwera ma u nas TTL i politykę usuwania (evict policy) – Twoja warstwa kliencka z kontrolowanym TTL pozwoli uniknąć zbędnych odjęć kredytów.
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 + deduplikacja na backendzie
Dział zatytułowany „2. Batch + deduplikacja na backendzie”Jeśli Twój serwis przyjmuje masowe zapytania o te same dane urodzeniowe (kampania onboardingowa, gdzie koledzy testują tymi samymi danymi demo), użyj deduplikacji opartej na 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;}To nie zaoszczędzi kredytów (każde wywołanie i tak jest naliczane), ale eliminuje zatory podczas skoków współbieżności.
3. Wstępne rozgrzewanie krytycznych ścieżek
Dział zatytułowany „3. Wstępne rozgrzewanie krytycznych ścieżek”Jeśli w produkcie są rytualne wykresy (popularne znaki horoskopu dziennego), wstępnie je rozgrzej za pomocą zaplanowanego crona. Pierwsze wywołanie dnia – MISS, wszystkie kolejne do momentu usunięcia z cache’u – HIT. Użytkownik otrzymuje odpowiedź w czasie poniżej sekundy.
4. Wgląd w panelu kontrolnym: gdzie przepłacasz
Dział zatytułowany „4. Wgląd w panelu kontrolnym: gdzie przepłacasz”Nowa kolumna „cache hit %” w /dashboard/usage dla każdego endpointu pokazuje:
- HIT% = 90+ – endpoint jest dobrze cache’owany, prawdopodobnie ten sam wykres jest przesyłany kilka razy. Rozważ deduplikację po stronie klienta (#2).
- HIT% = 0 i BYPASS: endpoint nie jest cache’owany z założenia (transits/now, AI). To normalne.
- HIT% = 50% i MISS: połowa zapytań zawiera unikalne parametry, połowa: powtórzenia. Warto rozważyć cache po stronie klienta.
- Niski HIT% + zdeterminowany endpoint: podejrzane. Sprawdź, czy Twój klient nie dodaje losowych pól do payloadu (timestamps, request-id), które zaśmiecają klucz cache’u.
Implementacja techniczna: dla ciekawskich
Dział zatytułowany „Implementacja techniczna: dla ciekawskich”Śledzenie kosztuje jedną kolumnę w api_request_log.cache_status (enum: MISS|HIT|BYPASS, migracja 030). Kolumna jest wypełniana z tego samego handlera, który podejmuje decyzję o wyszukiwaniu w cache’u – dodatkowe zapytanie do bazy danych nie jest wykonywane.
GET /v1/me/usage/endpoints teraz zwraca rzeczywiste cache_hit_pct zamiast null dla każdego endpointu w Twojej historii. Metoda SDK client.me.usage.endpoints() automatycznie otrzyma to pole (typy w następnym wydaniu codegen).
Co dalej
Dział zatytułowany „Co dalej”Instrumentacja cache’u otwiera pośredni poziom optymalizacji między „brakiem cache’owania” a „pełnym cache’em brzegowym”. Następne punkty w roadmapie:
- Nagłówek odpowiedzi
Cache-Controlz rzeczywistym TTL dla cache’owanych endpointów – umożliwi cache CDN/proxy po stronie klienta If-None-MatchETags: ponowne sprawdzenia MISS bez pełnego payloadu w odpowiedzi- Statystyki cache’u dla poszczególnych użytkowników w panelu kontrolnym z możliwością unieważnienia (na przykład, wymuszone ponowne obliczenie określonego wykresu po poprawieniu czasu urodzenia)
Dokumentacja – /docs/api/ → Performance & Caching. Konkretne odniesienie do X-Cache – w sekcji nagłówków każdego endpointu.
Ten sam Swiss Ephemeris, co w Solar Fire - w 4 liniach kodu.
Darmowy klucz bez karty. 5 000 wywołań miesięcznie do pierwszej płatności.