diff --git a/.jules/bolt.md b/.jules/bolt.md index 2de2bd0..9b7817f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -11,3 +11,6 @@ **Learning:** `Buffer` inherits from `Uint8Array` in Node.js. Passing a `Buffer` into `new Uint8Array(...)` creates a completely new `ArrayBuffer` and copies all elements, which is an O(N) memory allocation and copy. **Action:** When decoding base64 strings or similar byte streams in Node.js where a `Uint8Array` is expected, return `Buffer.from(data, 'base64')` directly instead of wrapping it. +## 2026-04-24 - [OOM prevention through promise chunking] +**Learning:** When fetching unbounded large collections (like photo albums and their assets), using concurrent `Promise.all` over the entire array can cause Node.js Out of Memory (OOM) crashes. +**Action:** Instead of `Promise.all(array.map(...))`, use a chunked data fetching approach (e.g., `Promise.all` within a `for` loop processing chunks of e.g. 10 items) to balance memory constraints with concurrent network speed. diff --git a/lib/mapService.ts b/lib/mapService.ts index a225270..7e2d176 100644 --- a/lib/mapService.ts +++ b/lib/mapService.ts @@ -34,7 +34,15 @@ export async function getMapData(): Promise { } // Fetch full album data (with assets) for each - const fullAlbums = await Promise.all(albums.map((a) => immich.getAlbum(a.id))); + // Process in chunks of 10 to balance network speed and prevent Out of Memory (OOM) + // crashes from fetching thousands of assets simultaneously. + const fullAlbums: (Awaited> | null)[] = []; + const chunkSize = 10; + for (let i = 0; i < albums.length; i += chunkSize) { + const chunk = albums.slice(i, i + chunkSize); + const chunkResults = await Promise.all(chunk.map((a) => immich.getAlbum(a.id))); + fullAlbums.push(...chunkResults); + } // Bucket assets by city+country const buckets = new Map<