Emit only updater job changes instead of full status (#1302)

The update subscription emitted the full update status, which, depending on how big the status was, took forever because the graphql subscription does not support data loader batching, causing it to run into the n+1 problem
This commit is contained in:
schroda
2025-03-23 00:34:43 +01:00
committed by GitHub
parent 7d079a8728
commit 439e0c8284
10 changed files with 342 additions and 47 deletions

View File

@@ -16,7 +16,7 @@ import suwayomi.tachidesk.graphql.types.DownloadUpdates
import suwayomi.tachidesk.manga.impl.download.DownloadManager
class DownloadSubscription {
@GraphQLDeprecated("Replaced width downloadStatusChanged", ReplaceWith("downloadStatusChanged(input)"))
@GraphQLDeprecated("Replaced with downloadStatusChanged", ReplaceWith("downloadStatusChanged(input)"))
fun downloadChanged(): Flow<DownloadStatus> =
DownloadManager.status.map { downloadStatus ->
DownloadStatus(downloadStatus)

View File

@@ -7,17 +7,68 @@
package suwayomi.tachidesk.graphql.subscriptions
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import suwayomi.tachidesk.graphql.types.UpdateStatus
import suwayomi.tachidesk.graphql.types.UpdaterUpdates
import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.manga.impl.update.UpdateUpdates
import uy.kohesive.injekt.injectLazy
class UpdateSubscription {
private val updater: IUpdater by injectLazy()
@GraphQLDeprecated("Replaced with updates", ReplaceWith("updates(input)"))
fun updateStatusChanged(): Flow<UpdateStatus> =
updater.status.map { updateStatus ->
UpdateStatus(updateStatus)
}
data class LibraryUpdateStatusChangedInput(
@GraphQLDescription(
"Sets a max number of updates that can be contained in a updater update message." +
"Everything above this limit will be omitted and the \"updateStatus\" should be re-fetched via the " +
"corresponding query. Due to the graphql subscription execution strategy not supporting batching for data loaders, " +
"the data loaders run into the n+1 problem, which can cause the server to get unresponsive until the status " +
"update has been handled. This is an issue e.g. when starting an update.",
)
val maxUpdates: Int?,
)
fun libraryUpdateStatusChanged(input: LibraryUpdateStatusChangedInput): Flow<UpdaterUpdates> {
val omitUpdates = input.maxUpdates != null
val maxUpdates = input.maxUpdates ?: 50
return updater.updates.map { updates ->
val categoryUpdatesCount = updates.categoryUpdates.size
val mangaUpdatesCount = updates.mangaUpdates.size
val totalUpdatesCount = categoryUpdatesCount + mangaUpdatesCount
val needToOmitUpdates = omitUpdates && totalUpdatesCount > maxUpdates
if (!needToOmitUpdates) {
return@map UpdaterUpdates(updates, omittedUpdates = false)
}
val maxUpdatesAfterCategoryUpdates = (maxUpdates - categoryUpdatesCount).coerceAtLeast(0)
// the graphql subscription execution strategy does not support data loader batching which causes the n+1 problem,
// thus, too many updates (e.g. on mass enqueue or dequeue) causes unresponsiveness of the server until the
// update has been handled
UpdaterUpdates(
UpdateUpdates(
updates.isRunning,
updates.categoryUpdates.subList(0, maxUpdates),
updates.mangaUpdates.subList(0, maxUpdatesAfterCategoryUpdates),
updates.totalJobs,
updates.finishedJobs,
updates.skippedCategoriesCount,
updates.skippedMangasCount,
updates.initial,
),
omittedUpdates = true,
)
}
}
}