Skip to content

perf: replace uuid.Parse with zero-alloc isGUID format validator in route constraint matching#4392

Closed
pageton wants to merge 2 commits into
gofiber:mainfrom
pageton:perf/guid-check-zero-alloc
Closed

perf: replace uuid.Parse with zero-alloc isGUID format validator in route constraint matching#4392
pageton wants to merge 2 commits into
gofiber:mainfrom
pageton:perf/guid-check-zero-alloc

Conversation

@pageton
Copy link
Copy Markdown
Contributor

@pageton pageton commented Jun 2, 2026

Summary

Replaces uuid.Parse() with a custom isGUID() format validator for the guidConstraint route constraint check, eliminating the dependency on github.com/google/uuid in path.go and reducing per-match overhead.

Problem

When routes are registered with a GUID constraint (e.g., /users/:id<guid>), the CheckConstraint function in path.go calls uuid.Parse() on every matching request to validate the parameter:

case guidConstraint:
    _, err = uuid.Parse(param)

While uuid.Parse is well-optimized in recent versions of google/uuid, it performs full parsing into a 16-byte [16]byte UUID struct — work that is unnecessary when the constraint only needs to know whether the format is valid. This also introduces an import of github.com/google/uuid in path.go solely for this one call.

When is this triggered?

The guidConstraint code path is only executed when:

  1. A route is registered with the <guid> constraint (e.g., /:id<guid>)
  2. A request matches that route pattern

This is not a hot path for every request. It only affects apps that use GUID-constrained route parameters. The optimization is narrow in scope but has zero downside.

Solution

Replace uuid.Parse(param) with isGUID(param) — a pure format validator that checks hex characters and hyphen positions without allocating or parsing into a UUID struct.

What changed

path.go:

  • Removed import "github.com/google/uuid" (the package is still used in state.go)
  • Added isGUID(s string) bool — validates GUID/UUID format without allocation
  • Added isHexGroup(s string, n int) bool — helper for checking consecutive hex chars
  • Changed guidConstraint case from _, err = uuid.Parse(param) to early-return with isGUID

path_test.go:

  • Added Test_isGUID — 17 test cases covering standard, uppercase, braced, URN, no-hyphen, and invalid formats
  • Added Benchmark_isGUID, Benchmark_isGUID_NoHyphens, Benchmark_isGUID_Invalid, Benchmark_CheckConstraint_GUID

Supported formats

isGUID supports all formats that uuid.Parse accepts for the common URL parameter use cases:

Format Example Supported
Standard hyphenated f0fa66cc-d22e-445b-866d-1d76e776371d
Uppercase F0FA66CC-D22E-445B-866D-1D76E776371D
No hyphens (32 hex) f0fa66ccd22e445b866d1d76e776371d
Braced {f0fa66cc-d22e-445b-866d-1d76e776371d}
URN urn:uuid:f0fa66cc-d22e-445b-866d-1d76e776371d

Benchmark Results

Tested on AMD Ryzen 5 7600X, Go 1.26.2, linux/amd64.

Before (uuid.Parse)

Benchmark_uuid_Parse_Valid-12      	   5	   17.65 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)
Benchmark_uuid_Parse_Invalid-12    	   5	    2.56 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)

After (isGUID)

Benchmark_isGUID-12                	   5	   15.90 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)
Benchmark_isGUID_NoHyphens-12      	   5	   15.75 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)
Benchmark_isGUID_Invalid-12        	   5	    2.91 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)
Benchmark_CheckConstraint_GUID-12  	   5	   19.30 ns/op	       0 B/op	       0 allocs/op  (avg of 5 runs)

Comparison

Scenario Before (uuid.Parse) After (isGUID) Delta
Valid UUID 17.65 ns/op, 0 allocs 15.90 ns/op, 0 allocs ~10% faster
Invalid (early reject) 2.56 ns/op, 0 allocs 2.91 ns/op, 0 allocs ~14% slower
No-hyphen format 17.65 ns/op, 0 allocs 15.75 ns/op, 0 allocs ~11% faster

Honest note: The performance improvement is modest (~10%) because uuid.Parse is already well-optimized in recent versions. The primary benefit is eliminating the github.com/google/uuid import from path.go and avoiding the internal UUID struct allocation. The invalid path is slightly slower due to the length check happening before format validation, but this is negligible (0.35 ns difference).

Checklist

  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community. (No new dependencies — one import removed.)
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7f77a5bf-b4dd-4c29-a5f7-ebcabf99abcb

📥 Commits

Reviewing files that changed from the base of the PR and between 69d5737 and 568af61.

📒 Files selected for processing (1)
  • path.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • path.go

Walkthrough

Removes the github.com/google/uuid dependency and adds allocation-free unexported helpers isGUID/isHexGroup that validate URN, braced, hyphenated, and 32-hex UUID forms. Updates Constraint.CheckConstraint to use isGUID. Adds unit tests and benchmarks for validation and constraint checks.

Changes

GUID Validation Refactor

Layer / File(s) Summary
GUID validation helpers and constraint integration
path.go
Removes the github.com/google/uuid import; adds unexported isGUID and isHexGroup helpers that validate UUIDs in urn:uuid:, braced, hyphenated 8-4-4-4-12, and 32-hex formats without allocation. Updates Constraint.CheckConstraint guidConstraint case to call isGUID(param) instead of uuid.Parse.
GUID validation tests and performance benchmarks
path_test.go
Adds Test_isGUID table-driven tests covering accepted and rejected GUID formats (uppercase, braces, urn:uuid: prefix, hyphen/no-hyphen variants, invalid strings) and benchmarks for isGUID on hyphenated, no-hyphens, invalid inputs, plus Benchmark_CheckConstraint_GUID. Also imports github.com/stretchr/testify/assert.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A tiny hop, a hex-string bright,
We check the GUID by starlit byte,
No extra crate, just careful code,
Fast and neat down the rabbit road.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% 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
Title check ✅ Passed The title accurately describes the main change: replacing uuid.Parse with a zero-alloc isGUID validator in route constraint matching for performance improvement.
Description check ✅ Passed The description is comprehensive and well-structured, covering the problem, solution, supported formats, benchmarks, and a completed checklist aligned with the template requirements.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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 and usage tips.

@ReneWerner87 ReneWerner87 added this to v3 Jun 2, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jun 2, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request replaces the external github.com/google/uuid dependency with a custom, allocation-free isGUID validation function to check GUIDs/UUIDs in various formats. It also adds corresponding unit tests and benchmarks. The review feedback suggests removing the unused orig variable in the isGUID function to clean up the code.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread path.go
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.40%. Comparing base (3026a5e) to head (568af61).

Files with missing lines Patch % Lines
path.go 92.85% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4392   +/-   ##
=======================================
  Coverage   91.40%   91.40%           
=======================================
  Files         132      132           
  Lines       13120    13147   +27     
=======================================
+ Hits        11992    12017   +25     
- Misses        711      712    +1     
- Partials      417      418    +1     
Flag Coverage Δ
unittests 91.40% <92.85%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
path_test.go (1)

771-806: ⚡ Quick win

Use b.Loop() for consistency with existing benchmarks.

These benchmarks use the old-style for i := 0; i < b.N; i++ loop pattern, while existing benchmarks in this file (e.g., line 342) use the modern for b.Loop() pattern. Update for consistency and to follow current Go benchmarking best practices.

♻️ Proposed fix for all four benchmarks
 func Benchmark_isGUID(b *testing.B) {
 	val := "f0fa66cc-d22e-445b-866d-1d76e776371d"
 	b.ReportAllocs()
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for b.Loop() {
 		_ = isGUID(val)
 	}
 }
 
 func Benchmark_isGUID_NoHyphens(b *testing.B) {
 	val := "f0fa66ccd22e445b866d1d76e776371d"
 	b.ReportAllocs()
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for b.Loop() {
 		_ = isGUID(val)
 	}
 }
 
 func Benchmark_isGUID_Invalid(b *testing.B) {
 	val := "not-a-guid-at-all"
 	b.ReportAllocs()
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for b.Loop() {
 		_ = isGUID(val)
 	}
 }
 
 func Benchmark_CheckConstraint_GUID(b *testing.B) {
 	b.ReportAllocs()
 	c := &Constraint{ID: guidConstraint}
 	val := "f0fa66cc-d22e-445b-866d-1d76e776371d"
 	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
+	for b.Loop() {
 		_ = c.CheckConstraint(val)
 	}
 }
🤖 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 `@path_test.go` around lines 771 - 806, Update the four benchmarks
Benchmark_isGUID, Benchmark_isGUID_NoHyphens, Benchmark_isGUID_Invalid, and
Benchmark_CheckConstraint_GUID to use the modern Go benchmark loop by replacing
the manual for i := 0; i < b.N; i++ loop with for b.Loop() { ... }; keep
existing setup (val, c := &Constraint{...}, b.ReportAllocs(), b.ResetTimer())
and call isGUID or c.CheckConstraint inside the new for b.Loop() body.
path.go (1)

798-820: ⚡ Quick win

Remove unused orig variable.

The orig variable is stored on line 798 but never actually used—line 819's _ = orig is a no-op. This appears to be leftover development code and should be removed to eliminate confusion.

♻️ Proposed fix
 func isGUID(s string) bool {
-	orig := s
 	if strings.HasPrefix(s, "urn:uuid:") {
 		s = s[9:]
 	} else if len(s) > 2 && s[0] == '{' && s[len(s)-1] == '}' {
 		s = s[1 : len(s)-1]
 	}
 
 	if len(s) == 36 {
 		return isHexGroup(s, 8) &&
 			s[8] == '-' &&
 			isHexGroup(s[9:], 4) &&
 			s[13] == '-' &&
 			isHexGroup(s[14:], 4) &&
 			s[18] == '-' &&
 			isHexGroup(s[19:], 4) &&
 			s[23] == '-' &&
 			isHexGroup(s[24:], 12)
 	}
 	if len(s) == 32 {
 		return isHexGroup(s, 32)
 	}
-	_ = orig
 	return false
 }
🤖 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 `@path.go` around lines 798 - 820, The local variable orig is unused; remove
its declaration "orig := s" and the no-op "_ = orig" to clean up dead code in
the UUID validation block (the function that inspects s for "urn:uuid:", braces,
36/32 length checks and calls isHexGroup). Ensure you only delete those two
occurrences and do not change any other logic or variable names.
🤖 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.

Nitpick comments:
In `@path_test.go`:
- Around line 771-806: Update the four benchmarks Benchmark_isGUID,
Benchmark_isGUID_NoHyphens, Benchmark_isGUID_Invalid, and
Benchmark_CheckConstraint_GUID to use the modern Go benchmark loop by replacing
the manual for i := 0; i < b.N; i++ loop with for b.Loop() { ... }; keep
existing setup (val, c := &Constraint{...}, b.ReportAllocs(), b.ResetTimer())
and call isGUID or c.CheckConstraint inside the new for b.Loop() body.

In `@path.go`:
- Around line 798-820: The local variable orig is unused; remove its declaration
"orig := s" and the no-op "_ = orig" to clean up dead code in the UUID
validation block (the function that inspects s for "urn:uuid:", braces, 36/32
length checks and calls isHexGroup). Ensure you only delete those two
occurrences and do not change any other logic or variable names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: edd0a1c5-a7ae-4978-8ddc-ce743e1e58d9

📥 Commits

Reviewing files that changed from the base of the PR and between 3026a5e and 69d5737.

📒 Files selected for processing (2)
  • path.go
  • path_test.go

@gaby
Copy link
Copy Markdown
Member

gaby commented Jun 2, 2026

@pageton We are not replacing Google UUID. We already neen through this in the past.

Please discuss in an issue first, before sending PRs.

@gaby gaby closed this Jun 2, 2026
@github-project-automation github-project-automation Bot moved this to Done in v3 Jun 2, 2026
@gofiber gofiber locked and limited conversation to collaborators Jun 2, 2026
@pageton pageton deleted the perf/guid-check-zero-alloc branch June 2, 2026 12:14
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants