Skip to content

feat: Support Monotonic UUIDv7 Batch Generation#191

Closed
ashwingopalsamy wants to merge 2 commits into
gofrs:masterfrom
ashwingopalsamy:master
Closed

feat: Support Monotonic UUIDv7 Batch Generation#191
ashwingopalsamy wants to merge 2 commits into
gofrs:masterfrom
ashwingopalsamy:master

Conversation

@ashwingopalsamy

Copy link
Copy Markdown

Changes

This PR introduces a new feature for monotonic UUIDv7 batch generation including;

  • GenerateBatchV7 method in a new MonotonicGen struct for batch generation.
  • WithCustomPRNG option for deterministic UUID generation.
  • Monotonic counter handling with rollover support and timestamp management.
  • Unit tests for monotonicity, deterministic behavior and edge cases; including fuzz tests.

BY aligning with the existing library structure; this PR proposes enhanced functionality for users requiring batch UUID generation while maintaining compatibility with RFC Spec. Feedback welcome!

@dylan-bourque dylan-bourque left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels like a lot of added complexity to be able to generate more than one V7 UUID at a time.

Comment thread codec.go
//
// Returns:
// - GenOption: A function to configure the generator.
func WithCustomPRNG(seed int64) GenOption {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Assuming we move forward I think this function should be called WithExplicitRandSeed(), or something similar, to better reflect what it's actually doing.

Comment thread generator.go
//
// MonotonicGen ensures the generation of strictly monotonic UUIDs within a
// batch by utilizing a counter in conjunction with timestamps. This is
// particularly useful for applications requiring ordered identifiers, such

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

for applications requiring ordered identifiers

v6 and v7 UUIDs are already ordered so none of this is strictly necessary except to enable batch generation, and I'm not sold on the utility there. I have used this library to generate UUIDs on the order of tens of millions per second sustained over 1000s of nodes in a distributed system without ever running into a scenario where I wanted/needed to pre-allocate a block of values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree with @dylan-bourque - I'm really not sure how useful this is relative to the increased complexity. I haven't generated millions per second, but I can't see any reason that the existing implementation doesn't do what you want. Moreover, I'm concerned that having two separate implementations of generating a new UUIDv7 (the existing public method for generating one at a time and your new private method that is called in the loop) will lead to maintenance headaches.

Is there a specific situation you have encountered in which the existing implementation fails to provide monotonically increasing values? If there is, please explain in more detail to help us understand the need.

@nathanmcgarvey-modopayments

Copy link
Copy Markdown
Contributor

I did some benchmarking (but not independent testing for validation):

Looks like the batch generation is about 3.777 milliseconds faster for one million UUIDs, but about 16 megabytes more memory usage and one more alloc. (Of course that's my environment, YMMV.)

Unless my benchmarks have bugs (please validate), this is a lot of code maintenance for that small of an improvement IMHO. Batch sizes of 100 UUIDs give the batch method proposed about 177 nanoseconds of an advantage, with a similar doubling of memory usage.

go test -benchmem -run=^$ -bench ^BenchmarkGenerateBatchV7$ github.com/gofrs/uuid/v5 -count=1
BenchmarkGenerateBatchV7-16    	       9	 113330741 ns/op	32007189 B/op	 1000001 allocs/op

go test -benchmem -run=^$ -bench ^BenchmarkGenerateWithoutBatchV7$ github.com/gofrs/uuid/v5 -count=1
BenchmarkGenerateWithoutBatchV7-16    	       9	 117107931 ns/op	16000014 B/op	 1000000 allocs/op

Benchmark code used:

var uuidsBenchmarkSink []UUID

func BenchmarkGenerateBatchV7(b *testing.B) {
	b.ReportAllocs()

	gen := NewMonotonicGen()
	batchSize := 1000000

	var err error

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		uuidsBenchmarkSink, err = gen.GenerateBatchV7(batchSize)
		if err != nil {
			b.Fatalf("Error generating batch: %v", err)
		}
	}
}

func BenchmarkGenerateWithoutBatchV7(b *testing.B) {
	b.ReportAllocs()

	batchSize := 1000000
	uuidsBenchmarkSink = make([]UUID, batchSize, batchSize)

	var err error

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < batchSize; j++ {
			uuidsBenchmarkSink[j], err = NewV7()
			if err != nil {
				b.Fatalf("error generating UUID: %v", err)
			}
		}
	}
}

@cameracker

Copy link
Copy Markdown
Collaborator

Hi, it's been a while since this was opened. We do appreciate the contribution, but the consensus among the maintainers seems to be that this isn't something we want to include in the library.

Thank you. I hope this doesn't dissuade you from contributing another change.

@cameracker cameracker closed this May 15, 2025
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.

5 participants