Solving the Time-Traveler Dilemma: Engineering a Deterministic Global Spotlight
How I address greedy candidate depletion in daily photo scheduling using two-phase orchestration, backward date planning, and skeleton hydration.
Building a daily photo spotlight sounds straightforward until you consider that calendar dates depend on observer location. At any given moment, the world spans nearly forty-eight hours of active local timezones, ranging from UTC+14 in Kiritimati to UTC-12 in Baker Island.
When designing the spotlight system for my photography portfolio, I wanted curated selections grounded in real-world calendar context. These include cultural holidays, historical anniversaries using capture dates from past years, and aesthetic showcases matching the season.
A naive cron job running at midnight local time produces fragmented state. A visitor in Tokyo already browses tomorrow's date while a visitor in Los Angeles still views yesterday's date. If the scheduler picks candidates sequentially starting from the earliest timezone, that selection can consume a high-quality photo that would have been ideal for a special occasion occurring hours later elsewhere. By the time the scheduler reaches the final timezone, the candidate pool is depleted.
To solve this greedy algorithm trap, I anchor all scheduling computations to an authoritative source timezone, defined in :
export const DAILY_PHOTO_SOURCE_TIMEZONE = "Africa/Cairo"; export interface OccasionConfig { label: string; searchQuery: string; } export type SpecialOccasionMap = Record< string, OccasionConfig | OccasionConfig[] >;
By anchoring date logic to a single authoritative reference zone, the scheduler can compute and persist the entire global window deterministically.
The cron orchestrator runs daily via an authenticated endpoint. The execution window sits comfortably between the end of the day in western timezones and the start of the day in eastern timezones, allowing the orchestrator to resolve the full global calendar window in a single coordinated run.
To prevent photo cannibalization, the orchestrator divides scheduling into two coordinated phases:
- Phase 1 evaluates priority days with special occasions first. Special occasions can reuse photos because thematic relevance outweighs strict catalog variety, and their selected identifiers are immediately locked into an exclusion set.
- Phase 2 processes remaining showcase dates in descending calendar order, from the furthest future date back to the present. Sorting future dates first gives upcoming showcases first choice from the candidate pool, preventing earlier dates from taking the best photos.
As implemented in , the route executes this strategy directly:
// ============================================ // PHASE 1: Process Priority Days (Special Occasions) // ============================================ const priorityDates = datePlans .filter((plan) => plan.hasSpecialOccasion) .map((plan) => plan.dateISO); for (const dateISO of priorityDates) { try { // Check if already cached const cached = await getDailyPhotoSkeleton(dateISO); if (cached) { // Lock cached IDs, we already put them in exclusion list but just to be sure cached.photoIds.forEach((ref) => excludedIds.add(ref.id)); results.push({ dateISO, success: true, type: cached.type, selectedIds: cached.photoIds.map((ref) => ref.id), }); continue; } // Compute fresh selection // Note: Special occasions do NOT pass excludedIds - they can reuse photos const skeleton = await computeDailyPhotoSelection(dateISO); // Cache it await setDailyPhotoSkeleton( dateISO, skeleton, getSkeletonTtlSeconds(dateISO), ); // Lock selected IDs for subsequent days const selectedIds = skeleton.photoIds.map((ref) => ref.id); selectedIds.forEach((id) => excludedIds.add(id)); results.push({ dateISO, success: true, type: skeleton.type, selectedIds, }); } catch (error) { results.push({ dateISO, success: false, error: (error as Error).message, }); } } // ============================================ // PHASE 2: Process Showcase & On This Day (in Descending Order) // ============================================ // Sort remaining dates in DESCENDING order (furthest date first) // This ensures photos planned for future days aren't used on earlier days const showcaseDates = datePlans .filter((plan) => !plan.hasSpecialOccasion) .map((plan) => plan.dateISO) .sort((a, b) => b.localeCompare(a)); // Descending order for (const dateISO of showcaseDates) { try { // Check if already cached const cached = await getDailyPhotoSkeleton(dateISO); if (cached) { // Lock cached IDs for earlier dates cached.photoIds.forEach((ref) => excludedIds.add(ref.id)); results.push({ dateISO, success: true, type: cached.type, selectedIds: cached.photoIds.map((ref) => ref.id), }); continue; } // Compute fresh selection with exclusion list const orchestration: OrchestrationContext = { excludedIds: Array.from(excludedIds), isOrchestrated: true, }; const skeleton = await computeDailyPhotoSelection( dateISO, orchestration, ); // Cache it await setDailyPhotoSkeleton( dateISO, skeleton, getSkeletonTtlSeconds(dateISO), ); // Lock selected IDs for earlier dates in the window const selectedIds = skeleton.photoIds.map((ref) => ref.id); selectedIds.forEach((id) => excludedIds.add(id)); results.push({ dateISO, success: true, type: skeleton.type, selectedIds, }); } catch (error) { results.push({ dateISO, success: false, error: (error as Error).message, }); } }
The curation architecture uses an ordered three-tier priority structure documented in restricted source file (source restricted):
/** * Tier-based selection computations for daily photo selection. * * Selection Tiers: * - Tier 1 (Highest): Special Day - holidays, occasions, AI-detected events * - Tier 2: On This Day - photos taken on this calendar date in past years * - Tier 3 (Fallback): Showcase - AI-curated selection from least-shown photos */
Tier 1 evaluates special occasions through static date mapping and dynamic lunar or cultural calendar rules. When Tier 1 yields no matching photos, the engine falls back to Tier 2. Tier 2 queries the database for photographs taken on the exact month and day during previous years.
To compute accurate anniversary intervals across varying timestamp representations, the calculation normalizes database offsets, as shown in :
export function calculateYearsAgo( takenAt: string | null | undefined, reference: DateTime, ): number | undefined { if (!takenAt) return undefined; // Normalize PostgreSQL timestamp format to ISO8601 // "2023-12-02 22:55:48+00" -> "2023-12-02T22:55:48+00:00" const normalized = takenAt .replace(" ", "T") // Replace space with T .replace(/([+-]\d{2})$/, "$1:00"); // Add :00 to timezone offset const taken = DateTime.fromISO(normalized); if (!taken.isValid) return undefined; return reference.year - taken.year; }
If no historical anniversaries exist for the date, the engine falls through to Tier 3. Tier 3 selects least-shown photos and groups them into thematic showcases using vector search and AI curation.
Storing full photo payloads in persistent spotlight records leads to immediate data drift. Photo titles, descriptions, location tags, and love counts change continuously. If the scheduler stored serialized records containing image URLs and engagement counts, visitors would see stale counters and outdated data.
The system uses the skeleton-hydration pattern. The orchestrator persists only lightweight skeleton records containing photo identifiers, selection types, and curation metadata in PostgreSQL. When a client requests the daily spotlight, the API reads the skeleton and hydrates fresh photo entities in a single batch query, utilizing utilities defined in restricted source file (source restricted):
/** * Hydrate photo IDs with full photo data including tags, locations, and love counts. */ export async function hydratePhotos(photoIds: number[]): Promise<Photo[]> { if (!photoIds.length) { return []; }
On a cache miss, concurrent incoming requests could trigger duplicate AI evaluations. The API route prevents computation stampedes by acquiring a distributed coordination claim before running computations, as shown in :
let skeleton = !forceRecompute && (await getDailyPhotoSkeleton(userDate)); if (!skeleton) { // Use distributed locking to prevent race conditions on cache miss const lockKey = await acquireComputeLock(userDate); if (lockKey === DAILY_PHOTO_LOCK_UNAVAILABLE) { return dailyPhotoUnavailableResponse(); } if (lockKey) { // We acquired the lock - compute the selection try { // Double-check cache after acquiring lock (another request may have just filled it) skeleton = forceRecompute ? null : await getDailyPhotoSkeleton(userDate); if (!skeleton) { skeleton = await computeDailyPhotoSelection(userDate, { forceRecompute, }); await setDailyPhotoSkeleton( userDate, skeleton, getSkeletonTtlSeconds(userDate), ); } } finally { await releaseComputeLock(lockKey); } } else { // Lock is held by another process - wait for the skeleton to appear const waitedSkeleton = await waitForSkeleton(userDate); if ( waitedSkeleton === DAILY_PHOTO_LOCK_UNAVAILABLE || !waitedSkeleton ) { return dailyPhotoUnavailableResponse(); } skeleton = waitedSkeleton; } }
If another process holds the compute claim, the second request waits and polls for the authoritative skeleton record, preventing duplicate downstream generation work.
The platform supports managing and configuring daily photo selections. When an administrator modifies a photo selection, the update triggers recalculation workflows for the affected date.
If an override features a photo that was previously scheduled for two days later, leaving the remaining dates unchanged causes a duplicate appearance. The cascade recomputation system handles this by invalidating and recomputing subsequent dates in descending calendar order, as shown in restricted source file (source restricted):
export async function cascadeRecompute(triggerDateISO: string): Promise<void> { const normalized = normalizeDateISO(triggerDateISO); if (!normalized) { return; } // Check in-memory lock if (CASCADE_LOCKS.has(normalized)) { logRevalidationEvent( "cascade_skip", { triggerDate: normalized, reason: "already_running" }, "warn", ); return; } CASCADE_LOCKS.add(normalized); // Acquire global orchestration lock const orchestrationLock = await acquireOrchestrationLock( `cascade:${normalized}`, ); if (!orchestrationLock) { CASCADE_LOCKS.delete(normalized); logRevalidationEvent( "cascade_skip", { triggerDate: normalized, reason: "orchestration_lock_held" }, "warn", ); return; } try { const keys = await listDailyPhotoCacheKeys(); const targets = keys .map((key) => ({ key, date: extractDateFromCacheKey(key), })) .filter( (entry) => entry.date && entry.date.localeCompare(normalized) >= 0, ) as { key: string; date: string }[]; // The override date must still be recomputed when Redis is disabled or // its key registry is empty. In both cases there may be no cache key to // drive the cascade, but a persisted selection can still mask the // override unless the trigger date is explicitly included. if (!targets.some((target) => target.date === normalized)) { targets.push({ key: buildDailyPhotoCacheKey(normalized), date: normalized, }); } // Sort by date DESCENDING (furthest date first) - matches Cron Orchestrator strategy targets.sort((a, b) => b.date.localeCompare(a.date));
The cascade engine sorts all future scheduled targets from furthest to nearest date, clearing cached entries and recomputing each date with the latest exclusion list. This prevents manual adjustments from creating duplicate appearances on later dates.
Building a deterministic daily spotlight across international timezones requires treating time as a coordinated window rather than an isolated midnight trigger. Planning future dates before present dates avoids greedy candidate depletion and prioritizes high-value candidates for upcoming showcases.
The most critical architectural decision in this system is decoupling selection decisions from entity payloads. Storing compact selection skeletons in PostgreSQL and hydrating them on read ensures that metadata, image URLs, and live love counts reflect current database records across client requests.
When designing similar calendar-bound scheduling systems, establish an authoritative timezone boundary early and coordinate concurrent executions with database-backed lock claims. Decoupling storage skeletons from hydrated representations avoids secondary caching layers while keeping the public data surface strictly up to date.