mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-23 09:12:29 +00:00
perf: query only matching node locations in handleObservers (#579)
## Summary `handleObservers()` in `routes.go` was calling `GetNodeLocations()` which fetches ALL nodes from the DB just to match ~10 observer IDs against node public keys. With 500+ nodes this is wasteful. ## Changes - **`db.go`**: Added `GetNodeLocationsByKeys(keys []string)` — queries only the rows matching the given public keys using a parameterized `WHERE LOWER(public_key) IN (?, ?, ...)` clause. - **`routes.go`**: `handleObservers` now collects observer IDs and calls the targeted method instead of the full-table scan. - **`coverage_test.go`**: Added `TestGetNodeLocationsByKeys` covering known key, empty keys, and unknown key cases. ## Performance With ~10 observers and 500+ nodes, the query goes from scanning all 500 rows to fetching only ~10. The original `GetNodeLocations()` is preserved for any other callers. Fixes #378 Co-authored-by: you <you@example.com>
This commit is contained in:
1 parent
f68e98c376
commit
45d8116880
3 files changed
+73
-2
No files matched your search
@@ -1426,6 +1426,40 @@ func TestGetNodeLocations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- GetNodeLocationsByKeys ---
|
||||
|
||||
func TestGetNodeLocationsByKeys(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
seedTestData(t, db)
|
||||
|
||||
// Query with a known key
|
||||
pk := "aabbccdd11223344"
|
||||
locs := db.GetNodeLocationsByKeys([]string{pk})
|
||||
if len(locs) != 1 {
|
||||
t.Errorf("expected 1 location, got %d", len(locs))
|
||||
}
|
||||
if entry, ok := locs[strings.ToLower(pk)]; ok {
|
||||
if entry["lat"] == nil {
|
||||
t.Error("expected non-nil lat")
|
||||
}
|
||||
} else {
|
||||
t.Error("expected node location for test repeater")
|
||||
}
|
||||
|
||||
// Query with no keys returns empty map
|
||||
empty := db.GetNodeLocationsByKeys([]string{})
|
||||
if len(empty) != 0 {
|
||||
t.Errorf("expected 0 locations for empty keys, got %d", len(empty))
|
||||
}
|
||||
|
||||
// Query with unknown key returns empty map
|
||||
unknown := db.GetNodeLocationsByKeys([]string{"nonexistent"})
|
||||
if len(unknown) != 0 {
|
||||
t.Errorf("expected 0 locations for unknown key, got %d", len(unknown))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Store edge cases ---
|
||||
|
||||
func TestStoreQueryPacketsEdgeCases(t *testing.T) {
|
||||
|
||||
@@ -1497,6 +1497,39 @@ func (db *DB) GetNodeLocations() map[string]map[string]interface{} {
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNodeLocationsByKeys returns location data only for the given public keys.
|
||||
// This avoids fetching ALL nodes when only a few keys need to be matched.
|
||||
func (db *DB) GetNodeLocationsByKeys(keys []string) map[string]map[string]interface{} {
|
||||
result := make(map[string]map[string]interface{})
|
||||
if len(keys) == 0 {
|
||||
return result
|
||||
}
|
||||
placeholders := make([]string, len(keys))
|
||||
args := make([]interface{}, len(keys))
|
||||
for i, k := range keys {
|
||||
placeholders[i] = "?"
|
||||
args[i] = strings.ToLower(k)
|
||||
}
|
||||
query := "SELECT public_key, lat, lon, role FROM nodes WHERE LOWER(public_key) IN (" + strings.Join(placeholders, ",") + ")"
|
||||
rows, err := db.conn.Query(query, args...)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var pk string
|
||||
var role sql.NullString
|
||||
var lat, lon sql.NullFloat64
|
||||
rows.Scan(&pk, &lat, &lon, &role)
|
||||
result[strings.ToLower(pk)] = map[string]interface{}{
|
||||
"lat": nullFloat(lat),
|
||||
"lon": nullFloat(lon),
|
||||
"role": nullStr(role),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// QueryMultiNodePackets returns transmissions referencing any of the given pubkeys.
|
||||
func (db *DB) QueryMultiNodePackets(pubkeys []string, limit, offset int, order, since, until string) (*PacketResult, error) {
|
||||
if len(pubkeys) == 0 {
|
||||
|
||||
@@ -1560,8 +1560,12 @@ func (s *Server) handleObservers(w http.ResponseWriter, r *http.Request) {
|
||||
oneHourAgo := time.Now().Add(-1 * time.Hour).Unix()
|
||||
pktCounts := s.db.GetObserverPacketCounts(oneHourAgo)
|
||||
|
||||
// Batch lookup: node locations (observer ID may match a node public_key)
|
||||
nodeLocations := s.db.GetNodeLocations()
|
||||
// Batch lookup: node locations only for observer IDs (not all nodes)
|
||||
observerIDs := make([]string, len(observers))
|
||||
for i, o := range observers {
|
||||
observerIDs[i] = o.ID
|
||||
}
|
||||
nodeLocations := s.db.GetNodeLocationsByKeys(observerIDs)
|
||||
|
||||
result := make([]ObserverResp, 0, len(observers))
|
||||
for _, o := range observers {
|
||||
|
||||
Reference in new issue
Block a user