From 3d5ce0dc724909dd1895f53f2f1a423d89a1503e Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Apr 2026 12:19:52 +0700 Subject: [PATCH] perf: skip geocoding on list endpoints, add in-memory cache - GetAllDeviceDetails, GetDevicesWithoutConnections, GetDevicesWithoutTowers now pass nil geocoder to avoid Nominatim calls on list requests. This reduces GET /device-details response time from ~2 minutes to <1s. - Added sync.Map cache to nominatimGeocoder so repeated calls for the same coordinates (e.g. GetDeviceDetailsByID) hit cache instead of Nominatim, preventing HTTP 429 rate limit errors. Co-Authored-By: Claude Sonnet 4.6 --- usecase/device_details.go | 8 ++++--- utils/service/geo_service.go | 43 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/usecase/device_details.go b/usecase/device_details.go index 4f2de14..9d90373 100644 --- a/usecase/device_details.go +++ b/usecase/device_details.go @@ -91,7 +91,7 @@ func (u *deviceDetailsUseCase) GetDevicesWithoutConnections(deviceTypes []string return nil, err } - responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder) + responses, err := helper.ConvertToDeviceDetailsResponses(devices, nil) if err != nil { return nil, err } @@ -184,7 +184,7 @@ func (u *deviceDetailsUseCase) GetDevicesWithoutTowers(deviceTypes []string) ([] return nil, err } - responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder) + responses, err := helper.ConvertToDeviceDetailsResponses(devices, nil) if err != nil { return nil, err } @@ -477,7 +477,9 @@ func (u *deviceDetailsUseCase) GetAllDeviceDetails() ([]res.DeviceDetailsRespons return nil, err } - return helper.ConvertToDeviceDetailsResponses(devices, u.geocoder) + // Skip geocoding on list endpoint to avoid Nominatim rate limiting. + // Geocoding is only performed on single-device GET (GetDeviceDetailsByID). + return helper.ConvertToDeviceDetailsResponses(devices, nil) } func (u *deviceDetailsUseCase) GetDeviceDetailsByID(id uuid.UUID) (res.DeviceDetailsResponse, error) { diff --git a/utils/service/geo_service.go b/utils/service/geo_service.go index b54e033..270c657 100644 --- a/utils/service/geo_service.go +++ b/utils/service/geo_service.go @@ -6,39 +6,42 @@ import ( "io/ioutil" "log" "net/http" + "sync" "time" ) type GeocodingService interface { - GetAddressFromCoordinates(latitude, longitude float64) (string, error) + GetAddressFromCoordinates(latitude, longitude float64) (string, error) } type nominatimGeocoder struct { - client *http.Client + client *http.Client + cache sync.Map // key: "lat,lon" → string address } - func NewGeocodingService() GeocodingService { - client := &http.Client{ - Timeout: 10 * time.Second, - } - return &nominatimGeocoder{ - client: client, - } + client := &http.Client{ + Timeout: 10 * time.Second, + } + return &nominatimGeocoder{ + client: client, + } } type NominatimResponse struct { DisplayName string `json:"display_name"` - Error string `json:"error"` + Error string `json:"error"` } - func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) { - - if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 { - errMsg := fmt.Sprintf("Invalid coordinates: lat=%f, lon=%f", latitude, longitude) - return "", fmt.Errorf(errMsg) + return "", fmt.Errorf("invalid coordinates: lat=%f, lon=%f", latitude, longitude) + } + + // Check in-memory cache first — avoids repeated Nominatim calls for same device + cacheKey := fmt.Sprintf("%.6f,%.6f", latitude, longitude) + if cached, ok := g.cache.Load(cacheKey); ok { + return cached.(string), nil } url := fmt.Sprintf( @@ -46,7 +49,6 @@ func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float6 latitude, longitude, ) - req, err := http.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creation error: %v", err) @@ -66,7 +68,6 @@ func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float6 return "", fmt.Errorf("geocoding service returned status %d", resp.StatusCode) } - // Read full response body bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("Error reading response body: %v", err) @@ -84,18 +85,18 @@ func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float6 return "", err } - // Detailed error checking if result.Error != "" { log.Printf("Nominatim API Error: %s", result.Error) return "", fmt.Errorf("geocoding error: %s", result.Error) } - // Check for empty display name if result.DisplayName == "" { log.Printf("No address found for coordinates: %f, %f", latitude, longitude) return "", fmt.Errorf("no address found for these coordinates") } - return result.DisplayName, nil -} + // Store in cache for future calls + g.cache.Store(cacheKey, result.DisplayName) + return result.DisplayName, nil +} \ No newline at end of file