fix(server): Split database queries based on PostgreSQL bound params limit (#6034)

* fix(server): Split database queries based on PostgreSQL bound params limit

PostgreSQL uses a 16-bit integer to indicate the number of bound
parameters.

This means that the maximum number of parameters for any query is 65535.
Any query that tries to bind more than that (e.g. searching by a list of
IDs) requires splitting the query into multiple chunks.

This change includes refactoring every Repository that runs queries
using a list of ids, and either flattening or merging results.

Fixes #5788, #5997.

Also, potentially a fix for #4648 (at least based on
[this comment](https://github.com/immich-app/immich/issues/4648#issuecomment-1826134027)).

References:

* https://github.com/typeorm/typeorm/issues/7565
* [PostgreSQL message format - Bind](https://www.postgresql.org/docs/15/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-BIND)

* misc: Create Chunked decorator to simplify implementation

* feat: Add ChunkedArray/ChunkedSet decorators
This commit is contained in:
Michael Manganiello
2024-01-06 20:36:12 -05:00
committed by GitHub
parent 6835d4519a
commit e262298090
11 changed files with 392 additions and 221 deletions

View File

@@ -13,6 +13,7 @@ import {
ValidationOptions,
} from 'class-validator';
import { CronJob } from 'cron';
import _ from 'lodash';
import { basename, extname } from 'node:path';
import sanitize from 'sanitize-filename';
@@ -175,6 +176,32 @@ export function Optional({ nullable, ...validationOptions }: OptionalOptions = {
return ValidateIf((obj: any, v: any) => v !== undefined, validationOptions);
}
/**
* Chunks an array or set into smaller arrays of the specified size.
*
* @param collection The collection to chunk.
* @param size The size of each chunk.
*/
export function chunks<T>(collection: Array<T> | Set<T>, size: number): T[][] {
if (collection instanceof Set) {
const result = [];
let chunk = [];
for (const elem of collection) {
chunk.push(elem);
if (chunk.length === size) {
result.push(chunk);
chunk = [];
}
}
if (chunk.length > 0) {
result.push(chunk);
}
return result;
} else {
return _.chunk(collection, size);
}
}
// NOTE: The following Set utils have been added here, to easily determine where they are used.
// They should be replaced with native Set operations, when they are added to the language.
// Proposal reference: https://github.com/tc39/proposal-set-methods