UNPKG

signalk-parquet

Version:

Vessel data Parquet file archive with automated value and geospatial triggers. History API compliant with cloud backups and queries.

191 lines (164 loc) 4.69 kB
import { Context, Path } from '@signalk/server-api'; import { ZonedDateTime } from '@js-joda/core'; import { CACHE_TTL, CACHE_SIZE } from '../config/cache-defaults'; interface PathCacheEntry { timeRange: { from: string; to: string }; paths: Path[]; timestamp: number; } interface ContextCacheEntry { timeRange: { from: string; to: string }; contexts: Context[]; timestamp: number; } const pathCache = new Map<string, PathCacheEntry>(); const contextCache = new Map<string, ContextCacheEntry>(); /** * Round timestamp to nearest minute for cache key generation * This allows queries within the same minute to share cache entries * @param dateTime - ZonedDateTime to round * @returns ISO string rounded to the minute (e.g., "2025-11-02T10:15:00Z") */ function roundToMinute(dateTime: ZonedDateTime): string { // Get the instant and convert to epoch milliseconds const epochMs = dateTime.toInstant().toEpochMilli(); // Round to nearest minute (60000 ms) const roundedMs = Math.floor(epochMs / 60000) * 60000; // Convert back to ISO string return new Date(roundedMs).toISOString(); } /** * Get cached paths for a specific context and time range */ export function getCachedPaths( context: Context, from: ZonedDateTime, to: ZonedDateTime ): Path[] | null { // Use rounded timestamps for cache key to improve hit rate const key = `${context}:${roundToMinute(from)}:${roundToMinute(to)}`; const cached = pathCache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_TTL.PATH_CONTEXT) { return cached.paths; } // Clean up expired entry if (cached) { pathCache.delete(key); } return null; } /** * Cache paths for a specific context and time range */ export function setCachedPaths( context: Context, from: ZonedDateTime, to: ZonedDateTime, paths: Path[] ): void { // Use rounded timestamps for cache key to improve hit rate const key = `${context}:${roundToMinute(from)}:${roundToMinute(to)}`; pathCache.set(key, { timeRange: { from: from.toString(), to: to.toString() }, paths, timestamp: Date.now(), }); // Clean up old entries if cache is too large if (pathCache.size > CACHE_SIZE.PATH_CONTEXT_MAX) { const oldestKey = Array.from(pathCache.entries()).sort( (a, b) => a[1].timestamp - b[1].timestamp )[0][0]; pathCache.delete(oldestKey); } } /** * Clear all cached paths */ export function clearPathCache(): void { pathCache.clear(); } /** * Get cache statistics for monitoring */ export function getPathCacheStats() { return { size: pathCache.size, maxSize: CACHE_SIZE.PATH_CONTEXT_MAX, ttlMs: CACHE_TTL.PATH_CONTEXT, }; } // ============================================================================ // Context Caching Functions // ============================================================================ /** * Get cached contexts for a specific time range */ export function getCachedContexts( from: ZonedDateTime, to: ZonedDateTime ): Context[] | null { // Use rounded timestamps for cache key to improve hit rate const key = `${roundToMinute(from)}:${roundToMinute(to)}`; const cached = contextCache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_TTL.PATH_CONTEXT) { return cached.contexts; } // Clean up expired entry if (cached) { contextCache.delete(key); } return null; } /** * Cache contexts for a specific time range */ export function setCachedContexts( from: ZonedDateTime, to: ZonedDateTime, contexts: Context[] ): void { // Use rounded timestamps for cache key to improve hit rate const key = `${roundToMinute(from)}:${roundToMinute(to)}`; contextCache.set(key, { timeRange: { from: from.toString(), to: to.toString() }, contexts, timestamp: Date.now(), }); // Clean up old entries if cache is too large if (contextCache.size > CACHE_SIZE.PATH_CONTEXT_MAX) { const oldestKey = Array.from(contextCache.entries()).sort( (a, b) => a[1].timestamp - b[1].timestamp )[0][0]; contextCache.delete(oldestKey); } } /** * Clear all cached contexts */ export function clearContextCache(): void { contextCache.clear(); } /** * Clear all caches (paths and contexts) */ export function clearAllCaches(): void { pathCache.clear(); contextCache.clear(); } /** * Get all cache statistics */ export function getAllCacheStats() { return { paths: { size: pathCache.size, maxSize: CACHE_SIZE.PATH_CONTEXT_MAX, ttlMs: CACHE_TTL.PATH_CONTEXT, }, contexts: { size: contextCache.size, maxSize: CACHE_SIZE.PATH_CONTEXT_MAX, ttlMs: CACHE_TTL.PATH_CONTEXT, }, }; }