maxminddb-golang

repository·main·Indexed 20 days ago

https://github.com/oschwald/maxminddb-golang

A high-performance Go reader for the MaxMind DB (.mmdb) format, supporting GeoLite2, GeoIP2, and other third-party databases. It utilizes netip.Addr for efficiency and provides features such as zero-allocation custom unmarshaling via the Unmarshaler interface, path-based decoding with DecodePath, and network iteration compatible with Go 1.23+ range syntax.

Tokens
5.9K
Snippets
22
Records
33
Agent score
23%

What's inside maxminddb-golang

  1. Quick Start: Perform a basic IP lookup

    main

    To perform a basic lookup, open a database file using maxminddb.Open, parse an IP address using netip.ParseAddr, and decode the result into a struct using db.Lookup(ip).Decode(&record). Note that this library uses netip.Addr for improved performance over the standard net.IP.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/maxminddb-golang/v2"
    )
    
    func main() {
    	db, err := maxminddb.Open("GeoLite2-City.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("81.2.69.142")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	var record struct {
    		Country struct {
    			ISOCode string            `maxminddb:"iso_code"`
    			Names   map[string]string `maxminddb:"names"`
    		} `maxminddb:"country"`
    		City struct {
    			Names map[string]string `maxminddb:"names"`
    		} `maxminddb:"city"`
    	}
    
    	err = db.Lookup(ip).Decode(&record)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	fmt.Printf("Country: %s (%s)\n", record.Country.Names["en"], record.Country.ISOCode)
    	fmt.Printf("City: %s\n", record.City.Names["en"])
    }
  2. Performance tips for MaxMind DB lookups

    main

    To optimize performance when using this library:

    1. Reuse Reader instances: Lookups, decoding, and iteration are safe to run concurrently. Call Close only when all readers are finished.
    2. Use specific structs: Avoid decoding into any; define structs with only the fields you need.
    3. Implement Unmarshaler: Use the Unmarshaler interface for high-throughput requirements to minimize allocations.
    4. Consider caching: Use Result.Offset() as a cache key to store and retrieve database records efficiently.
  3. Migrate from v1 to v2 package imports

    main

    When upgrading to v2, update your import path to include the /v2 suffix.

    // Old v1 import
    import "github.com/oschwald/maxminddb-golang"
    
    // New v2 import
    import "github.com/oschwald/maxminddb-golang/v2"
    import "github.com/oschwald/maxminddb-golang/v2"
  4. Implement custom unmarshaling for performance

    main

    For high-performance requirements, you can implement the mmdbdata.Unmarshaler interface on your custom types. This allows you to bypass reflection and use fine-grained decoding methods like d.ReadMap() or d.ReadString() directly.

    type FastCity struct {
    	CountryISO string
    	CityName   string
    }
    
    func (c *FastCity) UnmarshalMaxMindDB(d *mmdbdata.Decoder) error {
    	// Custom decoding logic using d.ReadMap(), d.ReadString(), etc.
    	return nil
    }
  5. Understand the Result type in network iteration

    main

    When iterating over networks using Networks or NetworksWithin, each step yields a Result object. A Result contains the following fields (accessible via methods or fields depending on the implementation):

    • IP: The netip.Addr of the network.
    • Offset: The uint offset in the database where the data starts.
    • PrefixLen: The uint8 length of the network prefix.
    • Err: An error if the iteration failed or if the specific network encountered an issue.
    • Reader: A reference to the *Reader used for the iteration.
  6. Iterate over networks using Go 1.23+ range syntax

    main

    In v2, Reader.Networks() and Reader.NetworksWithin() yield iterators compatible with Go 1.23+ range syntax. This replaces the old Next()/Network() pattern. The Decode method on the yielded result handles errors, which are returned by the range loop.

    Options for iteration (like SkipAliasedNetworks) now use an options pattern passed to the method.

    for result := range reader.Networks() {
    	var record struct {
    		ConnectionType string `maxminddb:"connection_type"` 
    	}
    
    	if err := result.Decode(&record); err != nil {
    		return err
    	}
    	fmt.Println(result.Prefix(), record.ConnectionType)
    }
  7. Decode specific fields using Custom Structs

    main

    You can map database fields to Go struct fields using the maxminddb struct tag. This allows you to extract only the specific data you need from the database record.

    type City struct {
    	Country struct {
    		ISOCode string `maxminddb:"iso_code"`
    		Names   struct {
    			English string `maxminddb:"en"`
    			German  string `maxminddb:"de"`
    		} `maxminddb:"names"`
    	} `maxminddb:"country"`
    }
    
    var city City
    err = db.Lookup(ip).Decode(&city)
  8. Decode specific values using Path-Based Decoding

    main

    If you only need a single value from a deep path in the database, use DecodePath. This avoids the need to define complex nested structs for simple lookups.

    var countryCode string
    err = db.Lookup(ip).DecodePath(&countryCode, "country", "iso_code")
    
    var cityName string
    err = db.Lookup(ip).DecodePath(&cityName, "city", "names", "en")
  9. Update Lookup API calls for v2

    main

    The Lookup method signature has changed. Instead of passing a destination pointer directly to Lookup, you now call Lookup with a netip.Addr and chain the .Decode() or .DecodePath() method to the returned Result object.

    Migration steps:

    1. Replace net.IP inputs with net/netip.Addr (use netip.ParseAddr or addr.AsSlice() for interoperability).
    2. Change reader.Lookup(ip, &result) to reader.Lookup(addr).Decode(&result).
    // v1 pattern
    err := reader.Lookup(net.IP, &result)
    
    // v2 pattern
    err := reader.Lookup(netip.Addr).Decode(&result)
  10. Use negative indices in DecodePath

    main

    The Result.DecodePath method supports negative indices for arrays, allowing you to access elements from the end of a slice, similar to standard Go slice behavior.

    Example: result.DecodePath(&value, "array", -1) fetches the last element of the array.