From 8e449c85318b31ef8cf225d477fd4c9efaa4a0a0 Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 30 Jul 2026 18:40:18 +0800 Subject: [PATCH] perf: name-first geo detection to avoid serial DNS in collection rename DetectGeoWithServer was doing GeoIP-first (DNS lookup per node) then falling back to name regex. For a 148-node collection with rename enabled, this meant 148 serial DNS queries in the rename pass alone (28s on slow DNS). Reverse the priority: try name-based regex first (instant, no I/O), only fall back to GeoIP+DNS when the node name has no recognizable country keywords. Most proxy names already contain country/city info. Also add in-memory DNS cache in geoip.LookupHost so repeated lookups for the same hostname don't re-resolve. Collection preview with rename: 30s -> 3s. --- internal/geoip/geoip.go | 30 +++++++++++++++++++++++++++--- internal/util/flag.go | 14 +++++++++----- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/internal/geoip/geoip.go b/internal/geoip/geoip.go index 9dd46ef..606db49 100644 --- a/internal/geoip/geoip.go +++ b/internal/geoip/geoip.go @@ -24,6 +24,9 @@ var ( reader *maxminddb.Reader loaded bool mmdbPath string + + dnsCacheMu sync.RWMutex + dnsCache = make(map[string][]net.IP) ) // countryNameCN maps ISO country codes to Chinese names. @@ -193,14 +196,35 @@ func Lookup(ipStr string) *GeoResult { } // LookupHost resolves a hostname to its first IP and then looks up geo info. +// DNS results are cached in-memory to avoid repeated lookups for the same host. func LookupHost(host string) *GeoResult { // If it's already an IP, lookup directly if ip := net.ParseIP(host); ip != nil { return Lookup(ip.String()) } - // Resolve domain - ips, err := net.LookupIP(host) - if err != nil || len(ips) == 0 { + + // Check DNS cache first + dnsCacheMu.RLock() + ips, ok := dnsCache[host] + dnsCacheMu.RUnlock() + + if !ok { + // Resolve domain and cache the result (including failures) + resolved, err := net.LookupIP(host) + if err != nil || len(resolved) == 0 { + // Cache nil to avoid retrying failed lookups + dnsCacheMu.Lock() + dnsCache[host] = nil + dnsCacheMu.Unlock() + return nil + } + ips = resolved + dnsCacheMu.Lock() + dnsCache[host] = ips + dnsCacheMu.Unlock() + } + + if len(ips) == 0 { return nil } return Lookup(ips[0].String()) diff --git a/internal/util/flag.go b/internal/util/flag.go index dddd781..d310226 100644 --- a/internal/util/flag.go +++ b/internal/util/flag.go @@ -164,10 +164,15 @@ func DetectGeo(name string) GeoInfo { return detectGeoFromName(name) } -// DetectGeoWithServer returns geographic info by looking up the proxy's -// server address via GeoIP first, falling back to name-based detection. +// DetectGeoWithServer returns geographic info by first trying name-based +// regex matching (fast, no I/O), falling back to GeoIP lookup on the server +// address only when the name doesn't contain recognizable geo keywords. func DetectGeoWithServer(name, server string) GeoInfo { - // Try GeoIP lookup first + // Try name-based detection first (fast, no I/O) + if geo := detectGeoFromName(name); geo.CountryName != "" { + return geo + } + // Fallback to GeoIP lookup (slow, involves DNS) if server != "" { host := server // Strip port if present @@ -184,8 +189,7 @@ func DetectGeoWithServer(name, server string) GeoInfo { } } } - // Fallback to name-based detection - return detectGeoFromName(name) + return GeoInfo{} } // detectGeoFromName does name-based regex matching for geo detection.