Internal Caching Strategy
Relevant source files
The following files were used as context for generating this wiki page:
The internal caching strategy of this project is designed as a durable, refresh-only key/value system primarily used to optimize upstream data fetching from Park4Night. Because Park4Night spots are effectively static, the system prioritizes high availability and persistence over short-term expiration (TTL).
The cache resides in memory for rapid access during the process lifecycle and is mirrored to a JSON file on disk to ensure a warm cache survives application restarts or redeploys. It is engineered to degrade gracefully, falling back to memory-only mode if the filesystem becomes unavailable or read-only, ensuring that cache failures never crash an active request.
Sources: src/lib/cache.ts:1-12
Core Cache Architecture
The caching module is implemented as a Node-only utility that leverages a local Map for in-memory storage and the fs module for disk persistence.
Data Structures and Persistence
The system uses a specific JSON structure for its flat-file storage, versioned to handle potential schema migrations.
| Element | Type | Description |
|---|---|---|
CACHE_VERSION | 1 | Constant used to validate the disk file compatibility. |
MAX_ENTRIES | 20,000 | Maximum number of keys stored before a 25% cleanup is triggered. |
WRITE_DEBOUNCE_MS | 800 | Delay before flushing memory changes to the disk. |
Entry<T> | Interface | Wraps the cached value with an at epoch timestamp. |
CacheFile | Interface | The root object saved to disk, containing version and entries. |
Sources: src/lib/cache.ts:16-32
Disk IO and Synchronization
The system employs a debounced flush mechanism to minimize disk wear and performance impact. When the cache is "dirty" (modified), a timer is scheduled. If further writes occur before the timer expires, the timer is not reset, but the eventual flush captures all pending changes.
The following diagram illustrates the flow from a request through the cache-through helper to the persistence layer:
The cache uses an atomic write strategy: it writes to a temporary file using the current PID as a suffix before renaming it to the final destination, preventing file corruption during crashes.
Sources: src/lib/cache.ts:69-106
Integration with Upstream Services
The primary consumer of the caching layer is the Park4Night client, specifically for spatial queries.
Spatial Query Caching
To optimize caching for geographic data (lat/lng), the system rounds coordinates to three decimal places (approximately 111m grid cells). This ensures that overlapping or nearly identical query points collapse into the same cache entry, significantly increasing the hit rate for common corridors.
// src/lib/p4n.ts:303-305
export async function getPlacesAround(lat: number, lng: number, radiusKm: number, opts: { refresh?: boolean } = {}): Promise<P4nPlace[]> {
// ~111 m grid cell so overlapping query points collapse to one cache entry.
const key = `pa:${lat.toFixed(3)}:${lng.toFixed(3)}:${Math.round(radiusKm)}`;
return cacheThrough<P4nPlace[]>(key, async () => { ... }, { refresh: opts.refresh });
}
Sources: src/lib/p4n.ts:303-311
Cache-Through Mechanism
The cacheThrough function is the standard pattern for data retrieval. It attempts a getCached call first. If it results in a miss (or if an explicit refresh is requested), it executes the provided function, stores the result, and returns it.
Sources: src/lib/cache.ts:133-144
Monitoring and Maintenance
The system provides transparency into its performance via internal statistics and a dedicated API endpoint.
Cache Stats Endpoint
A dynamic route allows administrators to monitor cache health, including size, hits, misses, and total disk writes.
- Endpoint:
GET /api/cache - Returns: JSON object with
ok,size,hits,misses, andwrites.
Sources: src/app/api/cache/route.ts:5-14, src/lib/cache.ts:147-149
Eviction Policy
When the store.size reaches the MAX_ENTRIES threshold (20,000), the system performs a simple FIFO-like eviction by dropping the oldest 25% (5,000) of the entries based on the Map insertion order.
Sources: src/lib/cache.ts:119-126
Configuration
The cache location can be adjusted via environment variables, which is critical for deployments on platforms like Fly.io where persistent data must be stored on mounted volumes.
| Variable | Default | Purpose |
|---|---|---|
P4N_CACHE_DIR | ./.p4n-cache | Directory for the p4n-cache.json file. |
Sources: src/lib/cache.ts:35-39, README.md:6-10
Conclusion
The Internal Caching Strategy provides a robust backbone for the Park4Night trip planner by significantly reducing upstream API dependency and latency. By combining in-memory performance with atomic disk persistence and coordinate-grid collapsing, it ensures that even complex planning routes remain responsive and reliable across application restarts.