Skip to content

Improve reader robustness and lookup performance - #220

Merged
oschwald merged 10 commits into
mainfrom
greg/package-robustness-performance
Jul 12, 2026
Merged

Improve reader robustness and lookup performance#220
oschwald merged 10 commits into
mainfrom
greg/package-robustness-performance

Conversation

@oschwald

@oschwald oschwald commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • reject impossible or malformed large containers before destination allocation
  • keep memory-mapped readers alive during lookup, decode, and iteration, and release decoder references on close
  • reject invalid addresses, invalid UTF-8 during verification, and invalid pointer chains consistently
  • reduce reader opening memory and add DisableStringCache
  • optimize 28-bit tree traversal, recurring string-cache collisions, and reflected struct field lookup
  • correct cold-cache and concurrent lookup benchmarks

Performance

Benchmarks used Go 1.26.4 on Linux/amd64 with GOMAXPROCS=1 and alternating before/after samples.

  • OpenBytes: approximately 42% faster and 49.7% fewer allocated bytes
  • IPv4 lookup for 28-bit trees: approximately 4% faster
  • IPv6 lookup for 28-bit trees: approximately 6% faster
  • checked city lookup plus decode: approximately 13% faster
  • checked city allocation: 16 B/op and 1 allocation to 0 B/op and 0 allocations
  • reused 1,024-entry slice decoding: approximately 16% faster and allocation-free

No allocation regressions were found. Tests against real 24-bit and 32-bit databases in /var/lib/GeoIP did not identify a justified traversal rewrite for those formats.

Validation

  • go test ./...
  • go test -race ./...
  • go vet ./...
  • repository commit hooks and linters
  • FuzzDecode short fuzz run
  • focused alternating lookup, decode, container, cache, and concurrency benchmarks

Summary by CodeRabbit

  • New Features
    • Added a DisableStringCache() reader option for lower-memory decoding.
  • Performance
    • Faster IPv4/IPv6 lookups and decoding with fewer allocations and improved container/struct handling.
    • Benchmark methodology updated for more consistent cold-cache/concurrent measurements.
  • Bug Fixes
    • Stricter rejection of malformed containers/pointers (including “pointer to pointer”), invalid IP addresses, and invalid UTF-8 in both metadata and decoded data.
    • Improved iterator/decoder lifetime safety and corrected network prefix handling.
  • Tests
    • Added/updated unit tests and new benchmarks for cache-disabled decoding and validation failures.
  • Documentation
    • Updated the changelog under “Unreleased.”

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c437080a-118a-475d-aab0-cc08a9d084c9

📥 Commits

Reviewing files that changed from the base of the PR and between 285e715 and acf0eda.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • internal/decoder/reflection.go
  • internal/decoder/reflection_test.go
  • internal/decoder/string_cache.go
  • internal/decoder/string_cache_test.go
  • internal/decoder/verifier.go
  • internal/decoder/verifier_test.go
  • reader.go
  • reader_test.go
  • verifier.go
  • verifier_test.go

Walkthrough

The PR adds optional cache-free decoding, stronger container and UTF-8 validation, fingerprint-based struct field lookup, improved string-cache collision handling, reader lifetime safeguards, IP validation, and expanded benchmarks.

Changes

Decoder cache and offset handling

Layer / File(s) Summary
Decoder construction and cache paths
CHANGELOG.md, internal/decoder/data_decoder.go, internal/decoder/reflection.go, internal/decoder/string_cache.go, internal/decoder/*_test.go
Adds uncached decoder constructors, direct uncached string decoding, overflow-safe offset checks, alternate-slot cache admission, and related tests and benchmarks.

Reflection validation and lookup

Layer / File(s) Summary
Reflection preflight and field lookup
internal/decoder/reflection.go, internal/decoder/reflection_test.go
Rejects pointer-to-pointer and oversized container encodings before allocation, and adds fingerprint-based struct field lookup with collision fallback.

UTF-8 verification

Layer / File(s) Summary
UTF-8 verification
internal/decoder/verifier.go, internal/decoder/verifier_test.go, verifier.go, verifier_test.go
Validates UTF-8 in decoded strings, map keys, database type, descriptions, and language metadata.

Reader lifecycle and lookup safeguards

Layer / File(s) Summary
Reader lifecycle and lookup safeguards
reader.go, traverse.go, result.go, reader_test.go
Adds DisableStringCache, clears decoder state on close, preserves reader liveness during decoding and traversal, validates IP inputs, and updates traversal behavior and tests.

Performance measurement

Layer / File(s) Summary
Lookup and decoding benchmarks
internal/decoder/performance_test.go, reader_test.go, result_test.go
Adds benchmarks for decoder sizes, open options, IPv4/IPv6 lookup, pointer lookup, city decoding, concurrent lookup, and Result.Found.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Reader
  participant ReflectionDecoder
  participant DataDecoder
  Reader->>ReflectionDecoder: NewWithoutStringCache(buffer)
  ReflectionDecoder->>DataDecoder: NewDataDecoderWithoutStringCache(buffer)
  DataDecoder-->>ReflectionDecoder: Decode values without string interning
Loading

Possibly related PRs

Poem

A rabbit hops through caches bright,
Two slots now share the lookup night.
UTF-8 strings stand neat and true,
Safe bounds guard each value too.
The reader stays alive with cheer—
Benchmarks thump from far and near!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the PR’s main themes of reader robustness and lookup performance improvements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch greg/package-robustness-performance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/decoder/reflection.go (1)

56-66: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the now-unreachable depth check in IsEmptyValueAt.

The pointer-to-pointer guard at line 56 (followedPointers > 0) fires on every iteration after the first, so the depth check at line 62 (followedPointers >= maximumDataStructureDepth) can never be reached — followedPointers is always 0 when the depth check executes, and 0 >= maximumDataStructureDepth is false for any positive constant. This is dead code left behind by the new pointer-to-pointer rejection.

♻️ Proposed fix
 		if kindNum == KindPointer {
 			if followedPointers > 0 {
 				return false, mmdberrors.NewInvalidDatabaseError(
 					"invalid pointer to pointer at offset %d",
 					dataOffset,
 				)
 			}
-			if followedPointers >= maximumDataStructureDepth {
-				return false, mmdberrors.NewInvalidDatabaseError(
-					"exceeded maximum data structure depth; database is likely corrupt",
-				)
-			}
 			followedPointers++
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/decoder/reflection.go` around lines 56 - 66, Remove the unreachable
maximum-depth validation block from IsEmptyValueAt, specifically the check
against maximumDataStructureDepth and its associated error. Keep the
followedPointers > 0 pointer-to-pointer guard and all surrounding traversal
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reader.go`:
- Around line 367-370: Update the decoder construction in the reader flow to
choose exactly one constructor: call decoder.NewWithoutStringCache(dataSection)
when opts.disableStringCache is set, otherwise call decoder.New(dataSection).
Replace the current unconditional construction and reassignment so the string
cache is never allocated unnecessarily.

---

Outside diff comments:
In `@internal/decoder/reflection.go`:
- Around line 56-66: Remove the unreachable maximum-depth validation block from
IsEmptyValueAt, specifically the check against maximumDataStructureDepth and its
associated error. Keep the followedPointers > 0 pointer-to-pointer guard and all
surrounding traversal behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3cb7db82-cc96-4939-b32c-d0858ee01bca

📥 Commits

Reviewing files that changed from the base of the PR and between f98ac8d and fb61b83.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • internal/decoder/data_decoder.go
  • internal/decoder/performance_test.go
  • internal/decoder/reflection.go
  • internal/decoder/reflection_test.go
  • internal/decoder/string_cache.go
  • internal/decoder/string_cache_test.go
  • internal/decoder/verifier.go
  • internal/decoder/verifier_test.go
  • reader.go
  • reader_test.go
  • result.go
  • result_test.go
  • traverse.go
  • verifier.go
  • verifier_test.go

Comment thread reader.go Outdated
@oschwald
oschwald force-pushed the greg/package-robustness-performance branch from fb61b83 to 03151a2 Compare July 11, 2026 19:31
@oschwald

Copy link
Copy Markdown
Owner Author

Evaluated both review findings against the code and measurements; both were valid and are addressed in the rewritten branch. In addition to avoiding the discarded string-cache allocation, I removed the unreachable maximum-depth check in IsEmptyValueAt: the preceding followedPointers > 0 return made it impossible to execute. The focused tests, full test suite, race detector, vet, and configured lint hooks pass. The standard checked-database lookup/decode benchmark retained 0 B/op and 0 allocs/op and was within 1.2% in interleaved pinned-CPU measurements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reader_test.go`:
- Around line 1000-1012: Extend TestLookupRejectsInvalidAddress to cover the
24-bit and 32-bit tree fixtures in addition to MaxMind-DB-test-ipv4-28.mmdb.
Parameterize the test or add cases using the corresponding files, and assert the
same invalid-address error and not-found result for each.

In `@reader.go`:
- Around line 604-606: Move the !ip.IsValid() check from traverseTree24,
traverseTree28, and traverseTree32 into their shared caller lookupPointer before
dispatching to any tree variant, preserving the existing error return. Remove
the three duplicated guards so all record-size variants inherit the centralized
validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc09ef20-ff35-419a-bcaa-eac1148a7f7f

📥 Commits

Reviewing files that changed from the base of the PR and between fb61b83 and 03151a2.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • internal/decoder/data_decoder.go
  • internal/decoder/reflection.go
  • internal/decoder/reflection_test.go
  • internal/decoder/string_cache.go
  • internal/decoder/string_cache_test.go
  • internal/decoder/verifier.go
  • internal/decoder/verifier_test.go
  • reader.go
  • reader_test.go
  • result_test.go
  • verifier.go
  • verifier_test.go

Comment thread reader_test.go
Comment thread reader.go Outdated
@oschwald
oschwald force-pushed the greg/package-robustness-performance branch from 03151a2 to 285e715 Compare July 11, 2026 23:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/decoder/verifier_test.go`:
- Around line 9-25: Extend TestVerifyDataSectionRejectsInvalidUTF8 with a test
case whose encoded data represents a []any array containing an invalid UTF-8
string. Keep the existing string and map-key cases unchanged, and assert the
same “invalid UTF-8” verification error for the array case.

In `@verifier_test.go`:
- Around line 66-79: Extend TestVerifyMetadataRejectsInvalidUTF8 with cases
covering invalid UTF-8 in Metadata.Description keys and values, and
Metadata.Languages entries. For each case, construct metadata with the invalid
field and assert verifyMetadata returns the corresponding validation error,
while preserving the existing DatabaseType coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 79c911da-c46b-4472-b305-ec1a1ab55766

📥 Commits

Reviewing files that changed from the base of the PR and between 03151a2 and 285e715.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • internal/decoder/reflection.go
  • internal/decoder/reflection_test.go
  • internal/decoder/string_cache.go
  • internal/decoder/string_cache_test.go
  • internal/decoder/verifier.go
  • internal/decoder/verifier_test.go
  • reader.go
  • reader_test.go
  • result_test.go
  • verifier.go
  • verifier_test.go

Comment thread internal/decoder/verifier_test.go
Comment thread verifier_test.go
@oschwald
oschwald force-pushed the greg/package-robustness-performance branch from 285e715 to acf0eda Compare July 11, 2026 23:52
@oschwald
oschwald merged commit 91542f2 into main Jul 12, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant