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.
This commit is contained in:
2026-07-30 18:40:18 +08:00
parent eba9fa1be0
commit 8e449c8531
2 changed files with 36 additions and 8 deletions
+27 -3
View File
@@ -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())
+9 -5
View File
@@ -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.