feat: キューのタイムアウトを監視するように
This commit is contained in:
		
							parent
							
								
									43f9bfbc61
								
							
						
					
					
						commit
						154e5efd57
					
				|  | @ -16,6 +16,7 @@ | |||
| 
 | ||||
| package dev.usbharu.owl.broker.mongodb | ||||
| 
 | ||||
| import com.mongodb.client.model.Filters | ||||
| import com.mongodb.client.model.Filters.* | ||||
| import com.mongodb.client.model.FindOneAndUpdateOptions | ||||
| import com.mongodb.client.model.ReplaceOptions | ||||
|  | @ -24,6 +25,8 @@ import com.mongodb.client.model.Updates.set | |||
| import com.mongodb.kotlin.client.coroutine.MongoDatabase | ||||
| import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask | ||||
| import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTaskRepository | ||||
| import dev.usbharu.owl.broker.domain.model.task.Task | ||||
| import dev.usbharu.owl.common.property.PropertySerializeUtils | ||||
| import dev.usbharu.owl.common.property.PropertySerializerFactory | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.map | ||||
|  | @ -35,12 +38,15 @@ import java.time.Instant | |||
| import java.util.* | ||||
| 
 | ||||
| @Singleton | ||||
| class MongodbQueuedTaskRepository(private val propertySerializerFactory: PropertySerializerFactory,database: MongoDatabase) : QueuedTaskRepository { | ||||
| class MongodbQueuedTaskRepository( | ||||
|     private val propertySerializerFactory: PropertySerializerFactory, | ||||
|     database: MongoDatabase | ||||
| ) : QueuedTaskRepository { | ||||
| 
 | ||||
|     private val collection = database.getCollection<QueuedTaskMongodb>("queued_task") | ||||
|     override suspend fun save(queuedTask: QueuedTask): QueuedTask { | ||||
|         collection.replaceOne( | ||||
|             eq("_id", queuedTask.task.id.toString()), QueuedTaskMongodb.of(propertySerializerFactory,queuedTask), | ||||
|             eq("_id", queuedTask.task.id.toString()), QueuedTaskMongodb.of(propertySerializerFactory, queuedTask), | ||||
|             ReplaceOptions().upsert(true) | ||||
|         ) | ||||
|         return queuedTask | ||||
|  | @ -75,6 +81,11 @@ class MongodbQueuedTaskRepository(private val propertySerializerFactory: Propert | |||
|             ) | ||||
|         ).map { it.toQueuedTask(propertySerializerFactory) } | ||||
|     } | ||||
| 
 | ||||
|     override fun findByQueuedAtBeforeAndAssignedConsumerIsNull(instant: Instant): Flow<QueuedTask> { | ||||
|         return collection.find(Filters.lte(QueuedTaskMongodb::queuedAt.name, instant)) | ||||
|             .map { it.toQueuedTask(propertySerializerFactory) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| data class QueuedTaskMongodb( | ||||
|  | @ -93,16 +104,55 @@ data class QueuedTaskMongodb( | |||
|             attempt, | ||||
|             queuedAt, | ||||
|             task.toTask(propertySerializerFactory), | ||||
|             UUID.fromString(assignedConsumer), | ||||
|             assignedConsumer?.let { UUID.fromString(it) }, | ||||
|             assignedAt | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     data class TaskMongodb( | ||||
|         val name: String, | ||||
|         val id: String, | ||||
|         val publishProducerId: String, | ||||
|         val publishedAt: Instant, | ||||
|         val nextRetry: Instant, | ||||
|         val completedAt: Instant?, | ||||
|         val attempt: Int, | ||||
|         val properties: Map<String, String> | ||||
|     ) { | ||||
| 
 | ||||
|         fun toTask(propertySerializerFactory: PropertySerializerFactory): Task { | ||||
|             return Task( | ||||
|                 name = name, | ||||
|                 id = UUID.fromString(id), | ||||
|                 publishProducerId = UUID.fromString(publishProducerId), | ||||
|                 publishedAt = publishedAt, | ||||
|                 nextRetry = nextRetry, | ||||
|                 completedAt = completedAt, | ||||
|                 attempt = attempt, | ||||
|                 properties = PropertySerializeUtils.deserialize(propertySerializerFactory, properties) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         companion object { | ||||
|             fun of(propertySerializerFactory: PropertySerializerFactory, task: Task): TaskMongodb { | ||||
|                 return TaskMongodb( | ||||
|                     task.name, | ||||
|                     task.id.toString(), | ||||
|                     task.publishProducerId.toString(), | ||||
|                     task.publishedAt, | ||||
|                     task.nextRetry, | ||||
|                     task.completedAt, | ||||
|                     task.attempt, | ||||
|                     PropertySerializeUtils.serialize(propertySerializerFactory, task.properties) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     companion object { | ||||
|         fun of(propertySerializerFactory: PropertySerializerFactory,queuedTask: QueuedTask): QueuedTaskMongodb { | ||||
|         fun of(propertySerializerFactory: PropertySerializerFactory, queuedTask: QueuedTask): QueuedTaskMongodb { | ||||
|             return QueuedTaskMongodb( | ||||
|                 queuedTask.task.id.toString(), | ||||
|                 TaskMongodb.of(propertySerializerFactory,queuedTask.task), | ||||
|                 TaskMongodb.of(propertySerializerFactory, queuedTask.task), | ||||
|                 queuedTask.attempt, | ||||
|                 queuedTask.queuedAt, | ||||
|                 queuedTask.assignedConsumer?.toString(), | ||||
|  |  | |||
|  | @ -25,6 +25,9 @@ import dev.usbharu.owl.common.property.PropertySerializeUtils | |||
| import dev.usbharu.owl.common.property.PropertySerializerFactory | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.map | ||||
| import org.bson.BsonType | ||||
| import org.bson.codecs.pojo.annotations.BsonId | ||||
| import org.bson.codecs.pojo.annotations.BsonRepresentation | ||||
| import org.koin.core.annotation.Singleton | ||||
| import java.time.Instant | ||||
| import java.util.* | ||||
|  | @ -50,6 +53,8 @@ class MongodbTaskRepository(database: MongoDatabase, private val propertySeriali | |||
| 
 | ||||
| data class TaskMongodb( | ||||
|     val name: String, | ||||
|     @BsonId | ||||
|     @BsonRepresentation(BsonType.STRING) | ||||
|     val id: String, | ||||
|     val publishProducerId: String, | ||||
|     val publishedAt: Instant, | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ package dev.usbharu.owl.broker | |||
| 
 | ||||
| import dev.usbharu.owl.broker.service.DefaultRetryPolicyFactory | ||||
| import dev.usbharu.owl.broker.service.RetryPolicyFactory | ||||
| import dev.usbharu.owl.common.retry.ExponentialRetryPolicy | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import org.koin.core.context.startKoin | ||||
| import org.koin.dsl.module | ||||
|  | @ -40,7 +41,7 @@ fun main() { | |||
| 
 | ||||
|         val module = module { | ||||
|             single<RetryPolicyFactory> { | ||||
|                 DefaultRetryPolicyFactory(emptyMap()) | ||||
|                 DefaultRetryPolicyFactory(mapOf("" to ExponentialRetryPolicy())) | ||||
|             } | ||||
|         } | ||||
|         modules(module, defaultModule, moduleContext.module()) | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ class OwlBrokerApplication( | |||
|         ) | ||||
| 
 | ||||
|         return coroutineScope.launch { | ||||
|             taskManagementService.startManagement() | ||||
|             taskManagementService.startManagement(this) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ | |||
| package dev.usbharu.owl.broker.domain.model.queuedtask | ||||
| 
 | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import java.time.Instant | ||||
| import java.util.* | ||||
| 
 | ||||
| interface QueuedTaskRepository { | ||||
|  | @ -28,4 +29,6 @@ interface QueuedTaskRepository { | |||
|     suspend fun findByTaskIdAndAssignedConsumerIsNullAndUpdate(id:UUID,update:QueuedTask):QueuedTask | ||||
| 
 | ||||
|     fun findByTaskNameInAndAssignedConsumerIsNullAndOrderByPriority(tasks:List<String>,limit:Int): Flow<QueuedTask> | ||||
| 
 | ||||
|     fun findByQueuedAtBeforeAndAssignedConsumerIsNull(instant: Instant): Flow<QueuedTask> | ||||
| } | ||||
|  | @ -0,0 +1,48 @@ | |||
| /* | ||||
|  * Copyright (C) 2024 usbharu | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package dev.usbharu.owl.broker.service | ||||
| 
 | ||||
| import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask | ||||
| import kotlinx.coroutines.currentCoroutineContext | ||||
| import kotlinx.coroutines.delay | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.emitAll | ||||
| import kotlinx.coroutines.flow.flow | ||||
| import kotlinx.coroutines.isActive | ||||
| import org.koin.core.annotation.Singleton | ||||
| import java.time.Instant | ||||
| 
 | ||||
| interface QueueScanner { | ||||
|     fun startScan(): Flow<QueuedTask> | ||||
| } | ||||
| 
 | ||||
| @Singleton | ||||
| class QueueScannerImpl(private val queueStore: QueueStore) : QueueScanner { | ||||
|     override fun startScan(): Flow<QueuedTask> { | ||||
|         return flow { | ||||
|             while (currentCoroutineContext().isActive) { | ||||
|                 emitAll(scanQueue()) | ||||
|                 delay(1000) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun scanQueue(): Flow<QueuedTask> { | ||||
|         return queueStore.findByQueuedAtBeforeAndAssignedConsumerIsNull(Instant.now().minusSeconds(10)) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -20,6 +20,7 @@ import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask | |||
| import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTaskRepository | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import org.koin.core.annotation.Singleton | ||||
| import java.time.Instant | ||||
| 
 | ||||
| interface QueueStore { | ||||
|     suspend fun enqueue(queuedTask: QueuedTask) | ||||
|  | @ -28,6 +29,8 @@ interface QueueStore { | |||
|     suspend fun dequeue(queuedTask: QueuedTask) | ||||
|     suspend fun dequeueAll(queuedTaskList: List<QueuedTask>) | ||||
|     fun findByTaskNameInAndAssignedConsumerIsNullAndOrderByPriority(tasks: List<String>, limit: Int): Flow<QueuedTask> | ||||
| 
 | ||||
|     fun findByQueuedAtBeforeAndAssignedConsumerIsNull(instant: Instant): Flow<QueuedTask> | ||||
| } | ||||
| 
 | ||||
| @Singleton | ||||
|  | @ -55,4 +58,8 @@ class QueueStoreImpl(private val queuedTaskRepository: QueuedTaskRepository) : Q | |||
|         return queuedTaskRepository.findByTaskNameInAndAssignedConsumerIsNullAndOrderByPriority(tasks, limit) | ||||
|     } | ||||
| 
 | ||||
|     override fun findByQueuedAtBeforeAndAssignedConsumerIsNull(instant: Instant): Flow<QueuedTask> { | ||||
|         return queuedTaskRepository.findByQueuedAtBeforeAndAssignedConsumerIsNull(instant) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -21,10 +21,14 @@ import dev.usbharu.owl.broker.domain.model.queuedtask.QueuedTask | |||
| import dev.usbharu.owl.broker.domain.model.task.Task | ||||
| import dev.usbharu.owl.broker.domain.model.task.TaskRepository | ||||
| import dev.usbharu.owl.broker.domain.model.taskdefinition.TaskDefinitionRepository | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.coroutineScope | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.collect | ||||
| import kotlinx.coroutines.flow.flowOf | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import kotlinx.coroutines.joinAll | ||||
| import kotlinx.coroutines.launch | ||||
| import org.koin.core.annotation.Singleton | ||||
| import org.slf4j.LoggerFactory | ||||
| import java.time.Instant | ||||
|  | @ -33,7 +37,7 @@ import java.util.* | |||
| 
 | ||||
| interface TaskManagementService { | ||||
| 
 | ||||
|     suspend fun startManagement() | ||||
|     suspend fun startManagement(coroutineScope: CoroutineScope) | ||||
|     fun findAssignableTask(consumerId: UUID, numberOfConcurrent: Int): Flow<QueuedTask> | ||||
| } | ||||
| 
 | ||||
|  | @ -44,17 +48,35 @@ class TaskManagementServiceImpl( | |||
|     private val taskDefinitionRepository: TaskDefinitionRepository, | ||||
|     private val assignQueuedTaskDecider: AssignQueuedTaskDecider, | ||||
|     private val retryPolicyFactory: RetryPolicyFactory, | ||||
|     private val taskRepository: TaskRepository | ||||
|     private val taskRepository: TaskRepository, | ||||
|     private val queueScanner: QueueScanner | ||||
| ) : TaskManagementService { | ||||
| 
 | ||||
|     private var flow: Flow<Task> = flowOf() | ||||
|     override suspend fun startManagement() { | ||||
|         flow = taskScanner.startScan() | ||||
| 
 | ||||
|         flow.onEach { | ||||
|             enqueueTask(it) | ||||
|         }.collect() | ||||
|     private var taskFlow: Flow<Task> = flowOf() | ||||
|     private var queueFlow: Flow<QueuedTask> = flowOf() | ||||
|     override suspend fun startManagement(coroutineScope: CoroutineScope) { | ||||
|         taskFlow = taskScanner.startScan() | ||||
|         queueFlow = queueScanner.startScan() | ||||
| 
 | ||||
|         coroutineScope { | ||||
|             listOf( | ||||
|                 launch { | ||||
|                     taskFlow.onEach { | ||||
|                         enqueueTask(it) | ||||
|                     }.collect() | ||||
|                 }, | ||||
|                 launch { | ||||
|                     queueFlow.onEach { | ||||
|                         logger.warn( | ||||
|                             "Queue timed out. name: {} id: {} attempt: {}", | ||||
|                             it.task.name, | ||||
|                             it.task.id, | ||||
|                             it.attempt | ||||
|                         ) | ||||
|                     }.collect() | ||||
|                 } | ||||
|             ).joinAll() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,13 +0,0 @@ | |||
| package dev.usbharu.owl.broker.service | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test | ||||
| 
 | ||||
| class TaskManagementServiceImplTest { | ||||
| 
 | ||||
|     @Test | ||||
|     fun findAssignableTask() { | ||||
|         val taskManagementServiceImpl = TaskManagementServiceImpl() | ||||
| 
 | ||||
|         Thread.sleep(10000) | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue