From ce47438e1600fff973c9278e3ff242d966c63c06 Mon Sep 17 00:00:00 2001 From: char-yb Date: Thu, 26 Mar 2026 14:19:33 +0900 Subject: [PATCH] [Refactor] Optimize Find FlowerSpotAll Process --- docker/init.sql | 12 +++++- .../com/pida/category/MapCategoryService.kt | 19 +++++++- .../com/pida/flowerspot/FlowerSpotFacade.kt | 43 +++++++++++-------- .../com/pida/flowerspot/FlowerSpotFinder.kt | 31 ++----------- .../db/core/flowerspot/FlowerSpotEntity.kt | 8 +++- 5 files changed, 63 insertions(+), 50 deletions(-) diff --git a/docker/init.sql b/docker/init.sql index 04892e25..47a8b24e 100644 --- a/docker/init.sql +++ b/docker/init.sql @@ -1 +1,11 @@ -CREATE EXTENSION IF NOT EXISTS postgis; \ No newline at end of file +CREATE EXTENSION IF NOT EXISTS postgis; + +-- Spatial GIST index for bbox queries (ST_Contains) +CREATE INDEX IF NOT EXISTS idx_flower_spot_pin_point_gist + ON t_flower_spot USING GIST (pin_point) + WHERE deleted_at IS NULL; + +-- Spatial GIST index for radius queries (ST_DWithin) +CREATE INDEX IF NOT EXISTS idx_flower_spot_pin_point_geog_gist + ON t_flower_spot USING GIST ((pin_point::geography)) + WHERE deleted_at IS NULL; \ No newline at end of file diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/category/MapCategoryService.kt b/pida-core/core-domain/src/main/kotlin/com/pida/category/MapCategoryService.kt index bf8aa74d..a39f05a1 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/category/MapCategoryService.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/category/MapCategoryService.kt @@ -1,15 +1,30 @@ package com.pida.category +import com.fasterxml.jackson.core.type.TypeReference +import com.pida.support.cache.CacheAdvice import org.springframework.stereotype.Service @Service class MapCategoryService( private val mapCategoryRepository: MapCategoryRepository, + private val cacheAdvice: CacheAdvice, ) { + companion object { + private const val ALL_CATEGORY_KEY = "category:all" + private const val CATEGORY_TTL = 600L + } + suspend fun readBy(categoryId: Long): MapCategory = mapCategoryRepository.findBy(categoryId) - suspend fun findAll(): List = mapCategoryRepository.findAll() + suspend fun findAll(): List = + cacheAdvice.invoke( + ttl = CATEGORY_TTL, + key = ALL_CATEGORY_KEY, + typeReference = object : TypeReference>() {}, + ) { + mapCategoryRepository.findAll() + } suspend fun findAllByCategoryLabel(categoryLabel: CategoryLabel): List = - mapCategoryRepository.findAllByCategoryLabel(categoryLabel) + findAll().filter { it.categoryLabel == categoryLabel } } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFacade.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFacade.kt index 8d0b8644..15ebca11 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFacade.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFacade.kt @@ -37,25 +37,32 @@ class FlowerSpotFacade( suspend fun findAllFlowerSpot( region: Region?, location: FlowerSpotLocation, - ): List { - val flowerSpots = flowerSpotService.readAllFlowerSpot(region, location) - if (flowerSpots.isEmpty()) return emptyList() + ): List = + coroutineScope { + val flowerSpots = flowerSpotService.readAllFlowerSpot(region, location) + if (flowerSpots.isEmpty()) return@coroutineScope emptyList() + + val bloomingDeferred = async { bloomingService.recentlyBloomingBySpotIds(flowerSpots.map { it.id }) } + val previewDeferred = + flowerSpots.map { spot -> + async { + spot.id to + imageS3Caller.getPreviewImage( + prefix = ImagePrefix.FLOWERSPOT.value, + prefixId = spot.id, + ) + } + } - val recentlyBlooming = bloomingService.recentlyBloomingBySpotIds(flowerSpots.map { it.id }) - val bloomingBySpotId = recentlyBlooming.groupBy { it.flowerSpotId } + val bloomingBySpotId = bloomingDeferred.await().groupBy { it.flowerSpotId } + val previewBySpotId = previewDeferred.associate { it.await() } - return flowerSpots.map { flowerSpot -> - FlowerSpotDetails.of( - flowerSpot = flowerSpot, - bloomings = bloomingBySpotId[flowerSpot.id] ?: emptyList(), - images = - listOfNotNull( - imageS3Caller.getPreviewImage( - prefix = ImagePrefix.FLOWERSPOT.value, - prefixId = flowerSpot.id, - ), - ), - ) + flowerSpots.map { flowerSpot -> + FlowerSpotDetails.of( + flowerSpot = flowerSpot, + bloomings = bloomingBySpotId[flowerSpot.id] ?: emptyList(), + images = listOfNotNull(previewBySpotId[flowerSpot.id]), + ) + } } - } } diff --git a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFinder.kt b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFinder.kt index 8b4963ea..c93aa0c0 100644 --- a/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFinder.kt +++ b/pida-core/core-domain/src/main/kotlin/com/pida/flowerspot/FlowerSpotFinder.kt @@ -2,7 +2,6 @@ package com.pida.flowerspot import com.fasterxml.jackson.core.type.TypeReference import com.pida.support.cache.CacheAdvice -import com.pida.support.geo.GeoJson import com.pida.support.geo.Region import org.springframework.stereotype.Component @@ -25,7 +24,7 @@ class FlowerSpotFinder( flowerSpotRepository.findAll() } - suspend fun readAllByRegion(region: Region): List = readAll().filterBy(region = region) + suspend fun readAllByRegion(region: Region): List = readAll().filter { it.region == region } suspend fun readBy(spotId: Long): FlowerSpot = flowerSpotRepository.findBy(spotId) @@ -37,12 +36,12 @@ class FlowerSpotFinder( is FindFlowerSpotPolicyCondition.ByRegionAndLocation -> readAllByLocationAndRegion(condition.region, condition.location) } - suspend fun readAllByLocation(location: FlowerSpotLocation): List = readAll().filterBy(location = location) + suspend fun readAllByLocation(location: FlowerSpotLocation): List = flowerSpotRepository.findAllByLocation(location) suspend fun readAllByLocationAndRegion( region: Region, location: FlowerSpotLocation, - ): List = readAll().filterBy(region = region, location = location) + ): List = flowerSpotRepository.findAllByLocationAndRegion(region, location) suspend fun searchByStreetName(streetName: String): List = cacheAdvice.invoke( @@ -52,28 +51,4 @@ class FlowerSpotFinder( ) { flowerSpotRepository.findByStreetNameContaining(streetName) } - - private fun List.filterBy( - region: Region? = null, - location: FlowerSpotLocation? = null, - ): List = - asSequence() - .filter { region == null || it.region == region } - .filter { location == null || it.isWithin(location) } - .toList() - - private fun FlowerSpot.isWithin(location: FlowerSpotLocation): Boolean { - if (!location.hasBounds()) return true - - val point = pinPoint as? GeoJson.Point ?: return false - if (point.coordinates.size < 2) return false - - val longitude = point.coordinates[0] - val latitude = point.coordinates[1] - - return longitude > location.swLng!! && - longitude < location.neLng!! && - latitude > location.swLat!! && - latitude < location.neLat!! - } } diff --git a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotEntity.kt b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotEntity.kt index 80fd96c9..07766e4a 100644 --- a/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotEntity.kt +++ b/pida-storage/db-core/src/main/kotlin/com/pida/storage/db/core/flowerspot/FlowerSpotEntity.kt @@ -10,12 +10,18 @@ import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated +import jakarta.persistence.Index import jakarta.persistence.Table import org.locationtech.jts.geom.LineString import org.locationtech.jts.geom.Point @Entity -@Table(name = "t_flower_spot") +@Table( + name = "t_flower_spot", + indexes = [ + Index(name = "idx_flower_spot_region_deleted_at", columnList = "region, deleted_at"), + ], +) class FlowerSpotEntity( val streetName: String, val address: String?,