takescake.com Performance & Reliability Architecture Upgrades
2026-08-07 | 2 min read
Building a lightning-fast Magic: The Gathering search engine requires instant access to thousands of cards, printings, market prices, and vector embeddings. In this article, we detail the technical performance optimizations implemented across takescake.com to eliminate server lag, reduce memory pressure, and deliver sub-50ms query speeds.
1. Process-Wide Global Caching for Large Datasets
The Challenge
Our compact Magic card database (cards-index.json) spans over 141 MB, and the nightly TCGplayer price store (tcgcsv-prices.json) spans 21 MB.
Under Next.js 14 App Router, individual route handlers and server components are compiled into isolated Webpack bundles. Standard module-level variables (let cardsCache = null) are re-evaluated within isolated module contexts. As a result, when multiple routes (/api/cards/search, /cards/[oracle], /deck, /scan, /upgrade-planner) were invoked, Node.js loaded and parsed the 141 MB JSON file multiple times into process memory. This created heavy garbage collection (GC) pauses and spiked heap memory to several gigabytes.
The Solution: Global Singleton Pattern
We refactored dataset loaders across lib/cards.ts, lib/price-store.ts, lib/card-images.ts, and lib/card-embedding-cache.ts to attach cached instances directly to globalThis:
declare global {
var __cards_index_cache: Card[] | undefined;
var __cards_index_loading: Promise<Card[]> | undefined;
}
export async function loadCards(): Promise<Card[]> {
if (globalThis.__cards_index_cache) return globalThis.__cards_index_cache;
if (globalThis.__cards_index_loading) return globalThis.__cards_index_loading;
globalThis.__cards_index_loading = (async () => {
const raw = await fs.readFile(indexFile, 'utf8');
const items = JSON.parse(raw) as CardIndexRecord[];
globalThis.__cards_index_cache = items;
return items;
})();
const result = await globalThis.__cards_index_loading;
globalThis.__cards_index_loading = undefined;
return result;
}
Impact
- RAM Overhead: Reduced process heap footprint by ~75% across active routes.
- Boot & Route Latency: Initial dataset parse occurs once; subsequent API calls retrieve pre-parsed cards in <1ms.
2. In-Memory Vector Embedding Shard Caching
The Challenge
Semantic search (AI concept matching like "cards that draw cards on upkeep") and card synergy searches compute cosine similarity against high-dimensional vector embeddings stored in sharded JSON files (content/cards/embeddings-shard-*.json).
Previously, every search request re-opened and parsed embedding shards from disk. Synchronous file I/O added 1,200ms – 1,800ms of delay to semantic queries.
The Solution: Shard Cache Map
We introduced an in-memory shard cache (globalThis.__embedding_shards_cache) in lib/cards-embeddings.ts:
export async function loadEmbeddingShardItems(file: string): Promise<CardEmbeddingItem[]> {
if (!globalThis.__embedding_shards_cache) {
globalThis.__embedding_shards_cache = new Map<string, CardEmbeddingItem[]>();
}
const cached = globalThis.__embedding_shards_cache.get(file);
if (cached) return cached;
const raw = await fs.readFile(shardPath, 'utf8');
const json = JSON.parse(raw) as CardEmbeddingStore;
const items = json.items || [];
globalThis.__embedding_shards_cache.set(file, items);
return items;
}
Impact
- Semantic Search Response Time: Reduced from ~1,500ms down to ~5ms.
- Disk I/O: Eliminates repetitive disk reads during search spikes.
3. Server Startup & Background Automation Resilience
To ensure 99.9% uptime on takescake.com:
- Custom HTTP Server (
server.js): Bypasses Next.js middleware for static card image assets (/cards/*), preventing Next.js file-system scanner overhead on large card image stores. - Automated Cron Scheduler (
scripts/automation-scheduler.js): Executes nightly price refreshes and card updates using Bun (bun run), preventing host Node.js runner syntax mismatch errors in background logs.
Summary of Results
| Metric | Before Optimization | After Optimization |
|---|---|---|
| Card Index Load Time | 450ms – 1,200ms per route | <1ms (global singleton) |
| Semantic Search Query Speed | ~1,500ms | ~5ms |
| Server Heap Memory | ~3.5 GB (multi-route GC spikes) | ~350 MB stable |
| Price Data Lookup | Re-parsed per route | Instant in-memory Map |
With these upgrades in place, takescake.com delivers instantaneous search results, robust deck studio recommendations, and snappy collection management!