Server-side кеш у нас працював давно — детермінований чарт-розрахунок для тієї самої date/time/lat/lon повертається з кешу, не повторно через WASM ephemeris. Але клієнт цього не бачив. У dashboard колонка “cache hit %” показувала — тому, що бекенд логував cache-outcome у внутрішню метрику, а не у відповідь.
Тепер кожна response несе один з трьох заголовків:
X-Cache: HIT # обслужено з кешуX-Cache: MISS # обчислено з нуля, результат збереженоX-Cache: BYPASS # не кешується за дизайномЦе маленька зміна — заголовок плюс одна колонка в api_request_log — але вона відкриває цілий клас оптимізацій, які раніше були сліпою плямою.
Як це працює — приклад
Section titled “Як це працює — приклад”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", "timezoneOffset": 3, "latitude": 50.45, "longitude": 30.52 }' | grep -i x-cache# X-Cache: MISSВиконати другий раз з тим самим тілом запиту:
# X-Cache: HITДля ендпоінтів типу /v1/transits/now (динамічний час) — X-Cache: BYPASS, бо результат залежить від поточного Date.now() і кешувати немає сенсу.
Які ендпоінти що повертають
Section titled “Які ендпоінти що повертають”Не всі ендпоінти кешуються — і це навмисно. Розбивка:
- Детерміновані чарт-розрахунки (
/v1/chart,/v1/houses,/v1/aspects,/v1/synastry,/v1/dasha/*,/v1/vargas/*) — кешуються повністю. Очікувано MISS → HIT pattern. - Time-dependent (
/v1/transits/now,/v1/horoscope/today,/v1/moon/phase-now) —BYPASS. Кеш би міг бути правильним лише до кінця хвилини, тому простіше не кешувати взагалі. - AI-generated content (
/v1/horoscope/personal,/v1/interpret/*) —BYPASS. LLM-відповіді не детерміновані навіть на однаковий промпт, кешувати = фіксувати випадковість. - Render endpoints (
/v1/render/*) —MISS/HITдля деяких,BYPASSдля тих, що приймають великі payload-и (eclipse-path з 500 точками).
Маркер на конкретний endpoint видно зразу з response — не треба читати документацію щоб зрозуміти кешується чи ні.
Pricing impact — кешовані запити все одно коштують
Section titled “Pricing impact — кешовані запити все одно коштують”Це найважливіша річ для розуміння — кешовані запити продовжують deduct credits на той самий tier, що й MISS. Чому:
- Pricing у нас calibrated на business value endpoint-а, не на CPU-cost.
/v1/chartкоштує однаково чи WASM крутився, чи ні — клієнт отримав однаковий чарт. - Прозорість. Не хочемо ситуації коли один user-base платить за MISS, а інший за HIT (теоретично через “пощастило з кешем”). Pricing predictable.
- Cache infrastructure — це інфра, не value-add. Ми її субсидуємо у tier’і.
Але це не значить що X-Cache беззмістовний у pricing-контексті — він видимо показує архітектурні можливості для клієнта (див. наступний розділ).
Що з цим робити на клієнтській стороні
Section titled “Що з цим робити на клієнтській стороні”Чотири практичні pattern-и:
1. Client-side cache layer для MISS
Section titled “1. Client-side cache layer для MISS”Якщо ви бачите X-Cache: MISS для запиту, який сценарієм може повторитись (натальна карта тієї самої людини), кешуйте локально у Redis/Memcached/IndexedDB. Server-side кеш у нас має TTL і evict policy — ваш клієнтський шар з контрольованим TTL уникне лишніх credit-deduct.
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 на бекенді
Section titled “2. Batch + dedupe на бекенді”Якщо ваш сервіс приймає масові запити на однакові birth-data (онбординг кампанія, де колеги тестують однаковими демо-даними), використовуйте промісовий dedupe:
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;}Це не зекономить credits (кожен виклик все одно зараховує), але прибирає затори при concurrency-сплесках.
3. Pre-warm critical paths
Section titled “3. Pre-warm critical paths”Якщо в продукті є ритуальні чарти (популярні daily-horoscope знаки), pre-warm-те їх scheduled cron-ом. Перший виклик дня — MISS, всі наступні до evict-у — HIT. Користувач отримує subsecond response.
4. Dashboard insight: де ви над-платите
Section titled “4. Dashboard insight: де ви над-платите”Нова колонка cache hit % у /dashboard/usage за endpoint показує:
- HIT% = 90+ — endpoint добре кешується, ймовірно той самий чарт пересилається кілька разів. Розгляньте client-side dedupe (#2).
- HIT% = 0 і BYPASS — endpoint не кешується дизайном (transits/now, AI). Це нормально.
- HIT% = 50% і MISS — половина запитів несе унікальні параметри, половина — повтори. Worth-while client-side cache.
- HIT% низький + endpoint детермінований — підозріло. Перевірте чи ваш клієнт не додає випадкових fields у payload (timestamps, request-id), які забивають кеш-ключ.
Технічна імплементація — для curious
Section titled “Технічна імплементація — для curious”Tracking коштує одну колонку у api_request_log.cache_status (enum: MISS|HIT|BYPASS, migration 030). Колонка populate-нута з того ж handler-а, який приймає рішення про кеш-lookup — додатковий запит до DB не робиться.
GET /v1/me/usage/endpoints тепер повертає реальне cache_hit_pct замість null для кожного endpoint-а у вашій історії. SDK-метод client.me.usage.endpoints() отримає поле автоматично (типи у наступному codegen-релізі).
Що далі
Section titled “Що далі”Cache instrumentation відкриває проміжний рівень оптимізацій між “no caching” і “full edge cache”. Наступні штрихи у роадмапі:
Cache-Controlреспонсний заголовок з реальним TTL для кешованих endpoint-ів — дозволить CDN/proxy-кеш на стороні клієнтаIf-None-MatchETags — повторні MISS-перевірки без повного payload-у у відповіді- Per-user cache statistics у dashboard з можливістю invalidate (наприклад, форсовано пересчити certain chart після виправлення birth-time)
Документація — /docs/api/ → Performance & Caching. Конкретний X-Cache reference — у секції headers кожного endpoint-а.
Той самий Swiss Ephemeris, що й у Solar Fire — у 4 рядках коду.
Безкоштовний ключ без картки. 5 000 викликів на місяць до першої оплати.