perf: optimize database transactions to avoid locking

This commit is contained in:
Gauthier Roebroeck 2021-06-30 15:18:33 +08:00
parent b9546e8605
commit 39dcf5969e
12 changed files with 39 additions and 51 deletions

View file

@ -15,7 +15,7 @@ import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.persistence.MediaRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.support.TransactionTemplate
import java.io.FileNotFoundException
import java.nio.file.FileAlreadyExistsException
import java.util.zip.Deflater
@ -38,6 +38,7 @@ class BookConverter(
private val bookRepository: BookRepository,
private val mediaRepository: MediaRepository,
private val libraryRepository: LibraryRepository,
private val transactionTemplate: TransactionTemplate,
) {
private val convertibleTypes = listOf("application/x-rar-compressed; version=4")
@ -55,7 +56,6 @@ class BookConverter(
fun getConvertibleBookIds(library: Library): Collection<String> =
bookRepository.findAllIdsByLibraryIdAndMediaTypes(library.id, convertibleTypes)
@Transactional
fun convertToCbz(book: Book) {
if (!libraryRepository.findById(book.libraryId).convertToCbz)
return logger.info { "Book conversion is disabled for the library, it may have changed since the task was submitted, skipping" }
@ -127,8 +127,10 @@ class BookConverter(
if (book.path.deleteIfExists())
logger.info { "Deleted converted file: ${book.path}" }
bookRepository.update(convertedBook)
mediaRepository.update(convertedMedia)
transactionTemplate.executeWithoutResult {
bookRepository.update(convertedBook)
mediaRepository.update(convertedMedia)
}
}
fun getMismatchedExtensionBookIds(library: Library): Collection<String> =
@ -136,7 +138,6 @@ class BookConverter(
bookRepository.findAllIdsByLibraryIdAndMismatchedExtension(library.id, mediaType, extension)
}
@Transactional
fun repairExtension(book: Book) {
if (!libraryRepository.findById(book.libraryId).repairExtensions)
return logger.info { "Repair extensions is disabled for the library, it may have changed since the task was submitted, skipping" }

View file

@ -22,6 +22,7 @@ import org.gotson.komga.infrastructure.image.ImageConverter
import org.gotson.komga.infrastructure.image.ImageType
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.support.TransactionTemplate
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
@ -39,28 +40,29 @@ class BookLifecycle(
private val bookAnalyzer: BookAnalyzer,
private val imageConverter: ImageConverter,
private val eventPublisher: EventPublisher,
private val transactionTemplate: TransactionTemplate,
) {
@Transactional
fun analyzeAndPersist(book: Book): Boolean {
logger.info { "Analyze and persist book: $book" }
val media = bookAnalyzer.analyze(book)
// if the number of pages has changed, delete all read progress for that book
mediaRepository.findById(book.id).let { previous ->
if (previous.status == Media.Status.OUTDATED && previous.pages.size != media.pages.size) {
readProgressRepository.deleteByBookId(book.id)
transactionTemplate.executeWithoutResult {
// if the number of pages has changed, delete all read progress for that book
mediaRepository.findById(book.id).let { previous ->
if (previous.status == Media.Status.OUTDATED && previous.pages.size != media.pages.size) {
readProgressRepository.deleteByBookId(book.id)
}
}
}
mediaRepository.update(media)
mediaRepository.update(media)
}
eventPublisher.publishEvent(DomainEvent.BookUpdated(book))
return media.status == Media.Status.READY
}
@Transactional
fun generateThumbnailAndPersist(book: Book) {
logger.info { "Generate thumbnail and persist for book: $book" }
try {
@ -70,7 +72,6 @@ class BookLifecycle(
}
}
@Transactional
fun addThumbnailForBook(thumbnail: ThumbnailBook) {
when (thumbnail.type) {
ThumbnailBook.Type.GENERATED -> {
@ -97,7 +98,6 @@ class BookLifecycle(
thumbnailsHouseKeeping(thumbnail.bookId)
}
@Transactional
fun getThumbnail(bookId: String): ThumbnailBook? {
val selected = thumbnailBookRepository.findSelectedByBookIdOrNull(bookId)
@ -200,6 +200,7 @@ class BookLifecycle(
}
}
@Transactional
fun deleteOne(book: Book) {
logger.info { "Delete book id: ${book.id}" }
@ -215,6 +216,7 @@ class BookLifecycle(
eventPublisher.publishEvent(DomainEvent.BookDeleted(book))
}
@Transactional
fun deleteMany(books: Collection<Book>) {
val bookIds = books.map { it.id }
logger.info { "Delete book ids: $bookIds" }

View file

@ -14,7 +14,7 @@ import org.gotson.komga.domain.persistence.SidecarRepository
import org.gotson.komga.infrastructure.configuration.KomgaProperties
import org.gotson.komga.infrastructure.language.notEquals
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.support.TransactionTemplate
import java.nio.file.Paths
import kotlin.time.measureTime
@ -33,9 +33,9 @@ class LibraryContentLifecycle(
private val sidecarRepository: SidecarRepository,
private val komgaProperties: KomgaProperties,
private val taskReceiver: TaskReceiver,
private val transactionTemplate: TransactionTemplate,
) {
@Transactional
fun scanRootFolder(library: Library) {
logger.info { "Updating library: $library" }
measureTime {
@ -94,10 +94,12 @@ class LibraryContentLifecycle(
fileLastModified = newBook.fileLastModified,
fileSize = newBook.fileSize
)
mediaRepository.findById(existingBook.id).let {
mediaRepository.update(it.copy(status = Media.Status.OUTDATED))
transactionTemplate.executeWithoutResult {
mediaRepository.findById(existingBook.id).let {
mediaRepository.update(it.copy(status = Media.Status.OUTDATED))
}
bookRepository.update(updatedBook)
}
bookRepository.update(updatedBook)
}
}
}

View file

@ -25,7 +25,6 @@ import org.gotson.komga.infrastructure.metadata.barcode.IsbnBarcodeProvider
import org.gotson.komga.infrastructure.metadata.comicrack.ComicInfoProvider
import org.gotson.komga.infrastructure.metadata.epub.EpubMetadataProvider
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
private val logger = KotlinLogging.logger {}
@ -48,7 +47,6 @@ class MetadataLifecycle(
private val eventPublisher: EventPublisher,
) {
@Transactional
fun refreshMetadata(book: Book, capabilities: List<BookMetadataPatchCapability>) {
logger.info { "Refresh metadata for book: $book with capabilities: $capabilities" }
val media = mediaRepository.findById(book.id)
@ -143,7 +141,6 @@ class MetadataLifecycle(
}
}
@Transactional
fun refreshMetadata(series: Series) {
logger.info { "Refresh metadata for series: $series" }
@ -233,7 +230,6 @@ class MetadataLifecycle(
}
}
@Transactional
fun aggregateMetadata(series: Series) {
logger.info { "Aggregate book metadata for series: $series" }

View file

@ -173,7 +173,6 @@ class SeriesLifecycle(
eventPublisher.publishEvent(DomainEvent.ReadProgressSeriesDeleted(seriesId, user.id))
}
@Transactional
fun getThumbnail(seriesId: String): ThumbnailSeries? {
val selected = thumbnailsSeriesRepository.findSelectedBySeriesIdOrNull(seriesId)
@ -196,7 +195,6 @@ class SeriesLifecycle(
return null
}
@Transactional
fun addThumbnailForSeries(thumbnail: ThumbnailSeries) {
// delete existing thumbnail with the same url
thumbnailsSeriesRepository.findAllBySeriesId(thumbnail.seriesId)

View file

@ -238,17 +238,14 @@ class BookDao(
.execute()
}
@Transactional
override fun delete(bookId: String) {
dsl.deleteFrom(b).where(b.ID.eq(bookId)).execute()
}
@Transactional
override fun delete(bookIds: Collection<String>) {
dsl.deleteFrom(b).where(b.ID.`in`(bookIds)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(b).execute()
}

View file

@ -176,14 +176,12 @@ class ReadListDao(
insertBooks(readList)
}
@Transactional
override fun removeBookFromAll(bookId: String) {
dsl.deleteFrom(rlb)
.where(rlb.BOOK_ID.eq(bookId))
.execute()
}
@Transactional
override fun removeBooksFromAll(bookIds: Collection<String>) {
dsl.deleteFrom(rlb)
.where(rlb.BOOK_ID.`in`(bookIds))

View file

@ -9,7 +9,6 @@ import org.jooq.Condition
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.net.URL
import java.time.LocalDateTime
import java.time.ZoneId
@ -84,7 +83,6 @@ class SeriesDao(
.map { it.toDomain() }
}
@Transactional
override fun insert(series: Series) {
dsl.insertInto(s)
.set(s.ID, series.id)
@ -95,7 +93,6 @@ class SeriesDao(
.execute()
}
@Transactional
override fun update(series: Series) {
dsl.update(s)
.set(s.NAME, series.name)
@ -108,17 +105,14 @@ class SeriesDao(
.execute()
}
@Transactional
override fun delete(seriesId: String) {
dsl.deleteFrom(s).where(s.ID.eq(seriesId)).execute()
}
@Transactional
override fun deleteAll() {
dsl.deleteFrom(s).execute()
}
@Transactional
override fun delete(seriesIds: Collection<String>) {
dsl.deleteFrom(s).where(s.ID.`in`(seriesIds)).execute()
}

View file

@ -7,7 +7,6 @@ import org.gotson.komga.jooq.Tables
import org.gotson.komga.jooq.tables.records.SidecarRecord
import org.jooq.DSLContext
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.net.URL
@Component
@ -20,7 +19,6 @@ class SidecarDao(
override fun findAll(): Collection<SidecarStored> =
dsl.selectFrom(sc).fetch().map { it.toDomain() }
@Transactional
override fun save(libraryId: String, sidecar: Sidecar) {
dsl.insertInto(sc)
.values(
@ -36,7 +34,6 @@ class SidecarDao(
.execute()
}
@Transactional
override fun deleteByLibraryIdAndUrls(libraryId: String, urls: Collection<URL>) {
dsl.deleteFrom(sc)
.where(sc.LIBRARY_ID.eq(libraryId))
@ -44,7 +41,6 @@ class SidecarDao(
.execute()
}
@Transactional
override fun deleteByLibraryId(libraryId: String) {
dsl.deleteFrom(sc)
.where(sc.LIBRARY_ID.eq(libraryId))

View file

@ -37,7 +37,6 @@ class ThumbnailBookDao(
.map { it.toDomain() }
.firstOrNull()
@Transactional
override fun insert(thumbnail: ThumbnailBook) {
dsl.insertInto(tb)
.set(tb.ID, thumbnail.id)
@ -49,7 +48,6 @@ class ThumbnailBookDao(
.execute()
}
@Transactional
override fun update(thumbnail: ThumbnailBook) {
dsl.update(tb)
.set(tb.BOOK_ID, thumbnail.bookId)
@ -76,22 +74,18 @@ class ThumbnailBookDao(
.execute()
}
@Transactional
override fun delete(thumbnailBookId: String) {
dsl.deleteFrom(tb).where(tb.ID.eq(thumbnailBookId)).execute()
}
@Transactional
override fun deleteByBookId(bookId: String) {
dsl.deleteFrom(tb).where(tb.BOOK_ID.eq(bookId)).execute()
}
@Transactional
override fun deleteByBookIds(bookIds: Collection<String>) {
dsl.deleteFrom(tb).where(tb.BOOK_ID.`in`(bookIds)).execute()
}
@Transactional
override fun deleteByBookIdAndType(bookId: String, type: ThumbnailBook.Type) {
dsl.deleteFrom(tb)
.where(tb.BOOK_ID.eq(bookId))

View file

@ -30,7 +30,6 @@ class ThumbnailSeriesDao(
.map { it.toDomain() }
.firstOrNull()
@Transactional
override fun insert(thumbnail: ThumbnailSeries) {
dsl.insertInto(ts)
.set(ts.ID, thumbnail.id)
@ -55,17 +54,14 @@ class ThumbnailSeriesDao(
.execute()
}
@Transactional
override fun delete(thumbnailSeriesId: String) {
dsl.deleteFrom(ts).where(ts.ID.eq(thumbnailSeriesId)).execute()
}
@Transactional
override fun deleteBySeriesId(seriesId: String) {
dsl.deleteFrom(ts).where(ts.SERIES_ID.eq(seriesId)).execute()
}
@Transactional
override fun deleteBySeriesIds(seriesIds: Collection<String>) {
dsl.deleteFrom(ts).where(ts.SERIES_ID.`in`(seriesIds)).execute()
}

View file

@ -0,0 +1,14 @@
package org.gotson.komga.infrastructure.transaction
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
@Configuration
class TransactionConfiguration {
@Bean
fun transactionTemplate(transactionManager: PlatformTransactionManager) =
TransactionTemplate(transactionManager)
}