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 <noreply@anthropic.com>
This commit is contained in:
parent
36cb2bd3e5
commit
3d5ce0dc72
|
|
@ -91,7 +91,7 @@ func (u *deviceDetailsUseCase) GetDevicesWithoutConnections(deviceTypes []string
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder)
|
responses, err := helper.ConvertToDeviceDetailsResponses(devices, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +184,7 @@ func (u *deviceDetailsUseCase) GetDevicesWithoutTowers(deviceTypes []string) ([]
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
responses, err := helper.ConvertToDeviceDetailsResponses(devices, u.geocoder)
|
responses, err := helper.ConvertToDeviceDetailsResponses(devices, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +477,9 @@ func (u *deviceDetailsUseCase) GetAllDeviceDetails() ([]res.DeviceDetailsRespons
|
||||||
return nil, err
|
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) {
|
func (u *deviceDetailsUseCase) GetDeviceDetailsByID(id uuid.UUID) (res.DeviceDetailsResponse, error) {
|
||||||
|
|
|
||||||
|
|
@ -6,39 +6,42 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GeocodingService interface {
|
type GeocodingService interface {
|
||||||
GetAddressFromCoordinates(latitude, longitude float64) (string, error)
|
GetAddressFromCoordinates(latitude, longitude float64) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type nominatimGeocoder struct {
|
type nominatimGeocoder struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
cache sync.Map // key: "lat,lon" → string address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func NewGeocodingService() GeocodingService {
|
func NewGeocodingService() GeocodingService {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
return &nominatimGeocoder{
|
return &nominatimGeocoder{
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NominatimResponse struct {
|
type NominatimResponse struct {
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) {
|
func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) {
|
||||||
|
|
||||||
|
|
||||||
if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 {
|
if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 {
|
||||||
errMsg := fmt.Sprintf("Invalid coordinates: lat=%f, lon=%f", latitude, longitude)
|
return "", fmt.Errorf("invalid coordinates: lat=%f, lon=%f", latitude, longitude)
|
||||||
return "", fmt.Errorf(errMsg)
|
}
|
||||||
|
|
||||||
|
// 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(
|
url := fmt.Sprintf(
|
||||||
|
|
@ -46,7 +49,6 @@ func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float6
|
||||||
latitude, longitude,
|
latitude, longitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Request creation error: %v", err)
|
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)
|
return "", fmt.Errorf("geocoding service returned status %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read full response body
|
|
||||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading response body: %v", err)
|
log.Printf("Error reading response body: %v", err)
|
||||||
|
|
@ -84,18 +85,18 @@ func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float6
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detailed error checking
|
|
||||||
if result.Error != "" {
|
if result.Error != "" {
|
||||||
log.Printf("Nominatim API Error: %s", result.Error)
|
log.Printf("Nominatim API Error: %s", result.Error)
|
||||||
return "", fmt.Errorf("geocoding error: %s", result.Error)
|
return "", fmt.Errorf("geocoding error: %s", result.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for empty display name
|
|
||||||
if result.DisplayName == "" {
|
if result.DisplayName == "" {
|
||||||
log.Printf("No address found for coordinates: %f, %f", latitude, longitude)
|
log.Printf("No address found for coordinates: %f, %f", latitude, longitude)
|
||||||
return "", fmt.Errorf("no address found for these coordinates")
|
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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue