Skip to content

feat: LRU chunk cache for Zarr pixel time series#31

Open
aivanchenk wants to merge 3 commits into
mainfrom
EPR-4--caching-implementation
Open

feat: LRU chunk cache for Zarr pixel time series#31
aivanchenk wants to merge 3 commits into
mainfrom
EPR-4--caching-implementation

Conversation

@aivanchenk

@aivanchenk aivanchenk commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add an LRU-backed ZarrChunkReader that downloads full spatial Zarr chunks and reuses them for nearby pixel clicks, avoiding repeat network fetches.
  • Introduce chunk helpers (pixelToChunkKey, slice ranges, extractTimeSeries) and wire the map to the cached reader instead of single-pixel fetches.
  • Add Vitest unit tests for the cache, chunk math, and reader (mocked fetch); CI now runs npm run test on every PR.

Details

  • Default cache size: 16 spatial chunks per reader instance.
  • NEE array spatial chunk shape: 40×40 pixels (chunks[2] / chunks[3]).
  • On cache hit, only in-memory extraction runs; fetchChunk is skipped.

@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown

🚀 Vercel preview deployed

Preview URL: https://earth-prints-6sbp0rsg2-anastasiia-s-projects10.vercel.app

@aivanchenk aivanchenk requested a review from lazarusA June 14, 2026 18:47
@lazarusA

Copy link
Copy Markdown
Member

/gemini review

@aivanchenk

Copy link
Copy Markdown
Collaborator Author

/gemini review

@lazarusA do you have a subscription to use it?

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new ZarrChunkReader utility that utilizes a custom LRUCache to optimize Zarr data fetching and prevent redundant network requests, alongside setting up Vitest for unit testing. The review feedback focuses on improving concurrency and performance by caching initialization and fetch promises instead of raw values, adding a delete method to the cache to handle failed requests, enforcing a minimum cache size, and passing pre-opened arrays to avoid redundant open calls.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/components/map/EarthMap.tsx Outdated
Comment thread src/components/map/EarthMap.tsx Outdated
Comment thread src/lib/zarr/ZarrChunkReader.ts Outdated
Comment thread src/lib/zarr/ZarrChunkReader.ts
Comment thread src/lib/cache/lru.ts Outdated
Comment thread src/lib/cache/lru.ts
Comment thread src/lib/zarr/store.ts Outdated
@lazarusA

Copy link
Copy Markdown
Member

/gemini summary

@lazarusA

Copy link
Copy Markdown
Member

/gemini review

@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the summary. You can try again by commenting /gemini summary.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a chunk-based caching and prefetching mechanism for Zarr time series data. It adds an LRUCache utility, a ZarrChunkReader class to manage prefetching and caching of native Zarr chunks, and updates the EarthMap and MapReadout components to utilize this reader. Additionally, it integrates Vitest for unit testing, adding several test files for the cache and chunk reader utilities. The review feedback highlights two important improvement opportunities in ZarrChunkReader.ts: addressing a potential unhandled promise rejection in the background prefetching logic (prefetchNativeChunks) and removing a redundant .catch() block in loadNativeChunk that duplicates cleanup logic already handled by the .finally() block.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +180 to +192
const promise = Promise.all(
missing.map((timeChunkIdx) =>
this.loadNativeChunk(
array,
variable,
this.nativeCoords(context, timeChunkIdx),
),
),
)
.then(() => undefined)
.finally(() => {
this.prefetchInFlight.delete(prefetchKey);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If any of the chunk loads in Promise.all fail, the promise will reject. Since this prefetch task is executed in the background without being awaited or having a .catch() handler, it will result in an unhandled promise rejection. This can crash the server process in Node.js or pollute the console in the browser.

Adding a .catch() block to silently ignore or log prefetch failures will prevent this issue.

    const promise = Promise.all(
      missing.map((timeChunkIdx) =>
        this.loadNativeChunk(
          array,
          variable,
          this.nativeCoords(context, timeChunkIdx),
        ),
      ),
    )
      .then(() => undefined)
      .catch(() => {
        // Silently ignore prefetch failures as they will be retried on explicit request
      })
      .finally(() => {
        this.prefetchInFlight.delete(prefetchKey);
      });

Comment on lines +120 to +126
.catch((error: unknown) => {
this.chunkLoadsInFlight.delete(key);
throw error;
})
.finally(() => {
this.chunkLoadsInFlight.delete(key);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The .catch block is redundant because the .finally block is guaranteed to run regardless of whether the promise resolves or rejects. Removing the .catch block simplifies the promise chain while maintaining the exact same cleanup behavior.

      .finally(() => {
        this.chunkLoadsInFlight.delete(key);
      });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants