perf: replace cidranger with bart for significant performance improvements (#675)

* feat: replace cidranger with bart improving performance by 3-20x

Signed-off-by: Jason Cameron <git@jasoncameron.dev>

* perf: replace cidranger with bart for IP range checking

- Replace cidranger.Ranger with bart.Lite in RemoteAddrChecker
- Use netip.ParsePrefix instead of net.ParseCIDR for modern IP handling
- Improve performance: 3-20x faster lookups with zero heap allocations
- Update imports to use github.com/gaissmai/bart and net/netip
- Remove cidranger dependency from go.mod

Benchmark results:
- IPv4 lookups: 4x faster (15.58ns vs 63.25ns, 0 vs 2 allocs)
- IPv6 lookups: 3x faster (26.51ns vs 76.96ns, 0 vs 2 allocs)
- Insertions: 20x faster (976ns vs 19,191ns)
- Large tables: 14x faster (5.2ns vs 74.85ns)

* docs: clarify CHANGELOG to not give false impressions

Signed-off-by: Jason Cameron <git@jasoncameron.dev>

* perf: optimize string concatenation in RemoteAddrChecker hash generation

Replace fmt.Fprintln with strings.Join for 7x faster performance:
- Before: 935.1 ns/op, 784 B/op, 22 allocs/op
- After: 133.2 ns/op, 192 B/op, 1 alloc/op

The hash is used for JWT cookie validation and error code generation.
Comma separation provides the same deterministic uniqueness as newlines
but with significantly better performance during policy initialization.

* chore: remove accidentally commited string benchmark

Signed-off-by: Jason Cameron <git@jasoncameron.dev>

* style: apply Copilot suggestions

Signed-off-by: Jason Cameron <git@jasoncameron.dev>

* fix: reference the right var name

i cannot write a merge commit

Signed-off-by: Jason Cameron <git@jasoncameron.dev>

---------

Signed-off-by: Jason Cameron <git@jasoncameron.dev>
This commit is contained in:
Jason Cameron 2025-06-17 11:57:55 -04:00 committed by GitHub
parent e2b46fc5e7
commit b2b2679bae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 60 additions and 39 deletions

View file

@ -3,14 +3,14 @@ package policy
import (
"errors"
"fmt"
"net"
"net/http"
"net/netip"
"regexp"
"strings"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/yl2chen/cidranger"
"github.com/gaissmai/bart"
)
var (
@ -32,30 +32,25 @@ func NewStaticHashChecker(hashable string) checker.Impl {
}
type RemoteAddrChecker struct {
ranger cidranger.Ranger
hash string
prefixTable *bart.Lite
hash string
}
func NewRemoteAddrChecker(cidrs []string) (checker.Impl, error) {
ranger := cidranger.NewPCTrieRanger()
var sb strings.Builder
table := new(bart.Lite)
for _, cidr := range cidrs {
_, rng, err := net.ParseCIDR(cidr)
prefix, err := netip.ParsePrefix(cidr)
if err != nil {
return nil, fmt.Errorf("%w: range %s not parsing: %w", ErrMisconfiguration, cidr, err)
}
err = ranger.Insert(cidranger.NewBasicRangerEntry(*rng))
if err != nil {
return nil, fmt.Errorf("%w: error inserting ip range: %w", ErrMisconfiguration, err)
}
fmt.Fprintln(&sb, cidr)
table.Insert(prefix)
}
return &RemoteAddrChecker{
ranger: ranger,
hash: internal.FastHash(sb.String()),
prefixTable: table,
hash: internal.FastHash(strings.Join(cidrs, ",")),
}, nil
}
@ -65,21 +60,12 @@ func (rac *RemoteAddrChecker) Check(r *http.Request) (bool, error) {
return false, fmt.Errorf("%w: header X-Real-Ip is not set", ErrMisconfiguration)
}
addr := net.ParseIP(host)
if addr == nil {
return false, fmt.Errorf("%w: %s is not an IP address", ErrMisconfiguration, host)
}
ok, err := rac.ranger.Contains(addr)
addr, err := netip.ParseAddr(host)
if err != nil {
return false, err
return false, fmt.Errorf("%w: %s is not an IP address: %w", ErrMisconfiguration, host, err)
}
if ok {
return true, nil
}
return false, nil
return rac.prefixTable.Contains(addr), nil
}
func (rac *RemoteAddrChecker) Hash() string {