Skip to content

Commit

Permalink
Merge pull request #227 from TorMap/dev
Browse files Browse the repository at this point in the history
Improve Caching
  • Loading branch information
JuliusHenke authored Jan 7, 2024
2 parents a3c28c3 + c3bfc42 commit c475f65
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 46 deletions.
5 changes: 4 additions & 1 deletion backend/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

group = "org.tormap"
version = "2.1.0"
version = "2.2.0"
java.sourceCompatibility = JavaVersion.VERSION_11

plugins {
Expand Down Expand Up @@ -46,6 +46,9 @@ dependencies {
// Postgres Database
implementation("org.postgresql:postgresql:42.7.1")

// Caching with Ehcache https://www.ehcache.org/
implementation("org.ehcache:ehcache:3.10.8")

// Run Flyway DB migration tool on startup https://flywaydb.org/
implementation("org.flywaydb:flyway-core:8.5.13")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.tormap.adapter.controller

import io.swagger.v3.oas.annotations.Operation
import org.springframework.web.bind.annotation.*
import org.tormap.adapter.controller.exception.RelayNotFoundException
import org.tormap.database.entity.RelayDetails
Expand All @@ -10,18 +11,22 @@ import org.tormap.database.repository.RelayDetailsRepositoryImpl
class RelayDetailsController(
val relayDetailsRepositoryImpl: RelayDetailsRepositoryImpl,
) {
@Operation(summary = "Returns all relay details for a given relay.")
@GetMapping("relay/{id}")
fun getRelay(@PathVariable id: Long): RelayDetails {
val details = relayDetailsRepositoryImpl.findById(id)
return if (details.isPresent) details.get() else throw RelayNotFoundException()
}

@Operation(summary = "Returns all relay details for a given family.")
@GetMapping("family/{id}")
fun getFamily(@PathVariable id: Long) = relayDetailsRepositoryImpl.findAllByFamilyId(id)

@Operation(summary = "Returns all identifiers that are associated with a list of relay details IDs.")
@PostMapping("relay/identifiers")
fun getRelayIdentifiers(@RequestBody ids: List<Long>) = relayDetailsRepositoryImpl.findRelayIdentifiers(ids)

@Operation(summary = "Returns family identifiers that are associated with a list of family IDs.")
@PostMapping("family/identifiers")
fun getFamilyIdentifiers(@RequestBody familyIds: List<Long>) =
relayDetailsRepositoryImpl.findFamilyIdentifiers(familyIds)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.tormap.adapter.controller

import io.swagger.v3.oas.annotations.Operation
import org.springframework.cache.annotation.Cacheable
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.tormap.adapter.dto.RelayLocationDto
import org.tormap.config.CacheConfig
import org.tormap.database.repository.RelayLocationRepositoryImpl
import java.time.LocalDate

Expand All @@ -14,14 +16,13 @@ import java.time.LocalDate
class RelayLocationController(
val relayLocationRepositoryImpl: RelayLocationRepositoryImpl,
) {
object CacheName {
const val RELAY_LOCATION_DAYS = "RELAY_LOCATION_DAYS"
}

@Cacheable(CacheName.RELAY_LOCATION_DAYS)
@Cacheable(CacheConfig.RELAY_LOCATION_DISTINCT_DAYS, key = "T(org.tormap.config.CacheConfig).RELAY_LOCATION_DISTINCT_DAYS_KEY")
@Operation(summary = "Returns all distinct days for which relay locations are available.")
@GetMapping("days")
fun getDays() = relayLocationRepositoryImpl.findDistinctDays()
fun getDays(): Set<LocalDate> = relayLocationRepositoryImpl.findDistinctDays()

@Cacheable(CacheConfig.RELAY_LOCATIONS_PER_DAY, key = "#day")
@Operation(summary = "Returns all relay locations for a given day. In Swagger UI, the large result might freeze your browser tab!")
@GetMapping("day/{day}")
fun getDay(@PathVariable day: String): List<RelayLocationDto> =
relayLocationRepositoryImpl.findAllUsingDay(LocalDate.parse(day))
Expand Down
2 changes: 0 additions & 2 deletions backend/src/main/kotlin/org/tormap/config/AppConfig.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.tormap.config

import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.EnableAsync
Expand All @@ -17,7 +16,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@EnableScheduling
@ConfigurationPropertiesScan
@EnableAsync
@EnableCaching
class AppConfig : WebMvcConfigurer {

/**
Expand Down
47 changes: 47 additions & 0 deletions backend/src/main/kotlin/org/tormap/config/CacheConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.tormap.config

import org.ehcache.config.builders.CacheConfigurationBuilder
import org.ehcache.config.builders.ResourcePoolsBuilder
import org.ehcache.jsr107.Eh107Configuration
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.cache.CacheManager
import javax.cache.Caching

@Configuration
@EnableCaching
class CacheConfig {
companion object {
const val RELAY_LOCATION_DISTINCT_DAYS = "RELAY_LOCATION_DISTINCT_DAYS"
const val RELAY_LOCATION_DISTINCT_DAYS_KEY = "RELAY_LOCATION_DISTINCT_DAYS_KEY"
const val RELAY_LOCATIONS_PER_DAY = "RELAY_LOCATIONS_OF_DAY"
}

@Bean
fun getCacheManager(): CacheManager {
val provider = Caching.getCachingProvider()
val cacheManager = provider.cacheManager

cacheManager.createCache(
RELAY_LOCATION_DISTINCT_DAYS,
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String::class.java, Set::class.java,
ResourcePoolsBuilder.heap(1)
)
)
)

cacheManager.createCache(
RELAY_LOCATIONS_PER_DAY,
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String::class.java, List::class.java,
ResourcePoolsBuilder.heap(40) // 1 entry ~= 2.5 MB of memory -> 40 entries ~= 100 MB of memory
)
)
)
return cacheManager
}
}
85 changes: 85 additions & 0 deletions backend/src/main/kotlin/org/tormap/service/CacheService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.tormap.service

import org.springframework.cache.CacheManager
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import org.tormap.config.CacheConfig
import org.tormap.database.repository.RelayLocationRepositoryImpl
import org.tormap.util.logger
import java.time.LocalDate
import java.time.YearMonth
import java.util.concurrent.CompletableFuture
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock


@Service
class CacheService(
private val cacheManager: CacheManager,
private val relayLocationRepositoryImpl: RelayLocationRepositoryImpl,
) {
private val logger = logger()
private val lockRelayLocationDistinctDays: Lock = ReentrantLock()
private val lockRelayLocationsPerDay: Lock = ReentrantLock()

@Async
fun cacheRelayLocationDistinctDays(): CompletableFuture<Void> {
if (lockRelayLocationDistinctDays.tryLock()) {
try {
logger.info("Caching distinct relay location days")
cacheManager.getCache(CacheConfig.RELAY_LOCATION_DISTINCT_DAYS)?.put(
CacheConfig.RELAY_LOCATION_DISTINCT_DAYS_KEY,
relayLocationRepositoryImpl.findDistinctDays()
)
} finally {
lockRelayLocationDistinctDays.unlock()
}
} else {
logger.debug("Cache update of relay location distinct days already in progress. Waiting 1 second...")
Thread.sleep(1000)
cacheRelayLocationDistinctDays()
}
return CompletableFuture.completedFuture(null)
}

@Async
fun cacheRelayLocationsPerDay(months: Set<String>): CompletableFuture<Void> {
if (lockRelayLocationsPerDay.tryLock()) {
try {
logger.info("Caching relay locations for each day of months: ${months.joinToString(", ")}")
months.forEach { month ->
val yearMonth = YearMonth.parse(month)
yearMonth.atDay(1).datesUntil(yearMonth.plusMonths(1).atDay(1)).forEach {
val day = it.toString()
val relayLocations = relayLocationRepositoryImpl.findAllUsingDay(LocalDate.parse(day))
if (relayLocations.isNotEmpty()) {
cacheManager.getCache(CacheConfig.RELAY_LOCATIONS_PER_DAY)?.put(
day,
relayLocations
)
}
}
}
} finally {
lockRelayLocationsPerDay.unlock()
}
} else {
logger.debug("Cache update of relay location per day already in progress. Waiting 1 second...")
Thread.sleep(1000)
cacheRelayLocationsPerDay(months)
}
return CompletableFuture.completedFuture(null)
}

@Async
fun evictRelayLocationsPerDay(months: Set<String>): CompletableFuture<Void> {
logger.info("Evicting cache of relay locations per day for months: ${months.joinToString(", ")}")
months.forEach { month ->
val yearMonth = YearMonth.parse(month)
yearMonth.atDay(1).datesUntil(yearMonth.plusMonths(1).atDay(1)).forEach {
cacheManager.getCache(CacheConfig.RELAY_LOCATIONS_PER_DAY)?.evict(it.toString())
}
}
return CompletableFuture.completedFuture(null)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.tormap.service

import org.springframework.cache.CacheManager
import org.springframework.stereotype.Service
import org.tormap.adapter.controller.RelayLocationController
import org.tormap.config.value.DescriptorConfig
import org.tormap.database.entity.DescriptorType
import org.tormap.database.entity.isRecent
Expand All @@ -28,8 +26,7 @@ class DescriptorCoordinationService(
private val descriptorProcessingService: DescriptorProcessingService,
private val relayDetailsRepository: RelayDetailsRepository,
private val relayLocationRepository: RelayLocationRepository,
private val relayLocationController: RelayLocationController,
private val cacheManager: CacheManager,
private val cacheService: CacheService,
) {
private val logger = logger()
private val descriptorCollector: DescriptorCollector = DescriptorIndexCollector()
Expand Down Expand Up @@ -63,58 +60,81 @@ class DescriptorCoordinationService(
private fun processLocalDescriptorFiles(apiPath: String, descriptorType: DescriptorType) {
var lastProcessedFile: File? = null
var errorCount = 0
val processedMonths = mutableSetOf<String>()
val processedMonthsFromAllFiles = mutableSetOf<String>()
val processedMonthsFromFile = mutableSetOf<String>()
descriptorFileService.getDescriptorDiskReader(apiPath, descriptorType).forEach { descriptor ->
lastProcessedFile?.let {
if (it != descriptor.descriptorFile) {
flushRelayRepositoryAndSaveProcessedFile(it, descriptorType, errorCount)
if (descriptorType.isRelayServerType()) {
relayDetailsUpdateService.computeFamilies(processedMonths)
relayDetailsUpdateService.lookupMissingAutonomousSystems(processedMonths)
}
processedMonths.clear()
handleFinishedFile(descriptorType, it, errorCount, processedMonthsFromFile)
errorCount = 0
}
}
val descriptorInfo = descriptorProcessingService.processDescriptor(descriptor)
descriptorInfo.yearMonth?.let { processedMonths.add(it) }
descriptorInfo.yearMonth?.let {
processedMonthsFromFile.add(it)
processedMonthsFromAllFiles.add(it)
}
descriptorInfo.error?.let { errorCount++ }
lastProcessedFile = descriptor.descriptorFile
}
if (descriptorType.isRecent()) {
computeRelayDetailsAndCaches(descriptorType, processedMonthsFromAllFiles)
}
}

private fun handleFinishedFile(
descriptorType: DescriptorType,
it: File,
errorCount: Int,
processedMonthsFromFile: MutableSet<String>
) {
flushRelayRepository(descriptorType)
saveProcessedFileReference(it, descriptorType, errorCount)
if (descriptorType.isRecent()) {
updateCaches(descriptorType, processedMonthsFromFile.toSet())
} else {
computeRelayDetailsAndCaches(descriptorType, processedMonthsFromFile.toSet())
}
processedMonthsFromFile.clear()
}

private fun flushRelayRepositoryAndSaveProcessedFile(file: File, descriptorType: DescriptorType, errorCount: Int) {
private fun flushRelayRepository(descriptorType: DescriptorType) {
try {
when {
descriptorType.isRelayServerType() -> relayDetailsRepository.flush()
descriptorType.isRelayConsensusType() -> {
relayLocationRepository.flush()
updateRelayLocationDaysCache()
}
descriptorType.isRelayConsensusType() -> relayLocationRepository.flush()
else -> throw Exception("Descriptor type ${descriptorType.name} is not yet supported!")
}
if (errorCount == 0) {
descriptorFileService.saveProcessedFileReference(file, descriptorType)
}
logFinishedProcessingDescriptorFile(file.name, errorCount)
} catch (exception: Exception) {
logger.error("Could not flush relay repository for ${descriptorType.name}! ${exception.message}")
}
}

private fun updateRelayLocationDaysCache() {
cacheManager.getCache(RelayLocationController.CacheName.RELAY_LOCATION_DAYS)?.invalidate()
relayLocationController.getDays()
private fun saveProcessedFileReference(file: File, descriptorType: DescriptorType, errorCount: Int) {
if (errorCount == 0) {
descriptorFileService.saveProcessedFileReference(file, descriptorType)
logger.info("Finished ${file.name} with 0 errors")
} else {
logger.error("Failed ${file.name} with $errorCount errors. Not saving as finished file reference!")
}
}

private fun logFinishedProcessingDescriptorFile(
filename: String,
errorCount: Int,
) {
if (errorCount == 0) {
logger.info("Finished $filename with 0 errors")
private fun computeRelayDetailsAndCaches(descriptorType: DescriptorType, processedMonths: Set<String>) {
if (descriptorType.isRelayServerType()) {
relayDetailsUpdateService.computeFamilies(processedMonths)
relayDetailsUpdateService.lookupMissingAutonomousSystems(processedMonths)
}
updateCaches(descriptorType, processedMonths)
}

private fun updateCaches(descriptorType: DescriptorType, processedMonths: Set<String>) {
if (descriptorType.isRelayConsensusType()) {
cacheService.cacheRelayLocationDistinctDays()
}
if (descriptorType.isRecent()) {
cacheService.cacheRelayLocationsPerDay(processedMonths)
} else {
logger.error("Finished $filename with $errorCount errors")
cacheService.evictRelayLocationsPerDay(processedMonths)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import javax.transaction.Transactional
class RelayDetailsUpdateService(
private val relayDetailsRepositoryImpl: RelayDetailsRepositoryImpl,
private val ipLookupService: IpLookupService,
private val cacheService: CacheService,
dataSource: DataSource,
) {
private val logger = logger()
Expand Down Expand Up @@ -72,10 +73,12 @@ class RelayDetailsUpdateService(
/**
* Updates [RelayDetails.familyId] for all entities
*/
fun computeAllMissingFamilies() {
fun computeAllMissingFamiliesAndEvictCache() {
val monthFamilyMemberCount =
relayDetailsRepositoryImpl.findDistinctMonthFamilyMemberCount().filter { it.count == 0L }
computeFamilies(monthFamilyMemberCount.map { it.month }.toSet())
val monthsToProcess = monthFamilyMemberCount.map { it.month }.toSet()
computeFamilies(monthsToProcess)
cacheService.evictRelayLocationsPerDay(monthsToProcess)
}


Expand Down
Loading

0 comments on commit c475f65

Please sign in to comment.