feat: Use postgres as a queue

We've been keen to try this for a while as it means we can remove redis as a
dependency, which makes Immich easier to setup and run.

This replaces bullmq with a bespoke postgres queue. Jobs in the queue are
processed either immediately via triggers and notifications, or eventually if a
notification is missed.
This commit is contained in:
Thomas Way
2025-04-30 20:43:51 +01:00
parent b845184c80
commit 8c0c8a8d0e
46 changed files with 731 additions and 915 deletions

View File

@@ -26,7 +26,7 @@ class JobCommand {
static const start = JobCommand._(r'start');
static const pause = JobCommand._(r'pause');
static const resume = JobCommand._(r'resume');
static const empty = JobCommand._(r'empty');
static const clear = JobCommand._(r'clear');
static const clearFailed = JobCommand._(r'clear-failed');
/// List of all possible values in this [enum][JobCommand].
@@ -34,7 +34,7 @@ class JobCommand {
start,
pause,
resume,
empty,
clear,
clearFailed,
];
@@ -77,7 +77,7 @@ class JobCommandTypeTransformer {
case r'start': return JobCommand.start;
case r'pause': return JobCommand.pause;
case r'resume': return JobCommand.resume;
case r'empty': return JobCommand.empty;
case r'clear': return JobCommand.clear;
case r'clear-failed': return JobCommand.clearFailed;
default:
if (!allowNull) {

View File

@@ -14,54 +14,42 @@ class JobCountsDto {
/// Returns a new [JobCountsDto] instance.
JobCountsDto({
required this.active,
required this.completed,
required this.delayed,
required this.failed,
required this.paused,
required this.waiting,
});
int active;
int completed;
int delayed;
int failed;
int paused;
int waiting;
@override
bool operator ==(Object other) => identical(this, other) || other is JobCountsDto &&
other.active == active &&
other.completed == completed &&
other.delayed == delayed &&
other.failed == failed &&
other.paused == paused &&
other.waiting == waiting;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(active.hashCode) +
(completed.hashCode) +
(delayed.hashCode) +
(failed.hashCode) +
(paused.hashCode) +
(waiting.hashCode);
@override
String toString() => 'JobCountsDto[active=$active, completed=$completed, delayed=$delayed, failed=$failed, paused=$paused, waiting=$waiting]';
String toString() => 'JobCountsDto[active=$active, delayed=$delayed, failed=$failed, waiting=$waiting]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'active'] = this.active;
json[r'completed'] = this.completed;
json[r'delayed'] = this.delayed;
json[r'failed'] = this.failed;
json[r'paused'] = this.paused;
json[r'waiting'] = this.waiting;
return json;
}
@@ -76,10 +64,8 @@ class JobCountsDto {
return JobCountsDto(
active: mapValueOfType<int>(json, r'active')!,
completed: mapValueOfType<int>(json, r'completed')!,
delayed: mapValueOfType<int>(json, r'delayed')!,
failed: mapValueOfType<int>(json, r'failed')!,
paused: mapValueOfType<int>(json, r'paused')!,
waiting: mapValueOfType<int>(json, r'waiting')!,
);
}
@@ -129,10 +115,8 @@ class JobCountsDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'active',
'completed',
'delayed',
'failed',
'paused',
'waiting',
};
}

View File

@@ -13,32 +13,26 @@ part of openapi.api;
class QueueStatusDto {
/// Returns a new [QueueStatusDto] instance.
QueueStatusDto({
required this.isActive,
required this.isPaused,
required this.paused,
});
bool isActive;
bool isPaused;
bool paused;
@override
bool operator ==(Object other) => identical(this, other) || other is QueueStatusDto &&
other.isActive == isActive &&
other.isPaused == isPaused;
other.paused == paused;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(isActive.hashCode) +
(isPaused.hashCode);
(paused.hashCode);
@override
String toString() => 'QueueStatusDto[isActive=$isActive, isPaused=$isPaused]';
String toString() => 'QueueStatusDto[paused=$paused]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'isActive'] = this.isActive;
json[r'isPaused'] = this.isPaused;
json[r'paused'] = this.paused;
return json;
}
@@ -51,8 +45,7 @@ class QueueStatusDto {
final json = value.cast<String, dynamic>();
return QueueStatusDto(
isActive: mapValueOfType<bool>(json, r'isActive')!,
isPaused: mapValueOfType<bool>(json, r'isPaused')!,
paused: mapValueOfType<bool>(json, r'paused')!,
);
}
return null;
@@ -100,8 +93,7 @@ class QueueStatusDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'isActive',
'isPaused',
'paused',
};
}