Skip to content
nsega.dev
Go back

Introduction to the singleflight package of Go

Updated:
Edit page

This post was originally published on Medium on March 26, 2020, and migrated here with light edits — mostly refreshing links that pointed to the now-retired godoc.org.

This time, I’d like to introduce the package singleflight. If you haven’t used this package before, I hope you will consider it once.

What is singleflight?

Here’s what singleflight is below:

Package singleflight provides a duplicate function call suppression mechanism.

I consider singleflight to be a very useful package if your application is ever going to get multiple requests for the same resource. For example, RDB (master data), image files, IP address lookups, client certificates, and etc, are cases that need to refer to such resources that do not usually change.

Also, the singleflight method avoids the Thundering herd problem.

As an objective measure of how many packages actually use it: at the time of the original writing (March 26, 2020), it was imported by 154 packages — and that number has only grown since.

Especially, if you look at what kind of products use it, you can find Consul, Fabio, and use cases in the process of acquiring certificates among them.

There are three functions provided by singleflight:

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result
func (g *Group) Forget(key string)

The actual source file is available at singleflight.go.

A recent trend of singleflight

If you look at the Go original code, singleflight is also found in Go’s internal package, and net/lookup.go is an example of a use case called inside Go. There are two flavors of the package:

It is maintained in a separate repository called Go Sync, which provides supplemental concurrency primitives in addition to the language and those provided by the “sync” and “sync/atomic” packages.

Introduction to use cases

Here, I’d like to introduce two use cases using the singleflight package.

Use case 1 — net/lookup

lookupGroup merges LookupIPAddr calls together for lookups for the same host. Then, LookupIPAddr uses that lookupGroup via a local resolver and refers to the host.

Here is the relevant code for the process (as of the original writing; line positions in net/lookup.go have shifted since):

// lookupGroup merges LookupIPAddr calls together for lookups for the same
// host. The lookupGroup key is the LookupIPAddr.host argument.
// The return values are ([]IPAddr, error).
lookupGroup singleflight.Group
func (r *Resolver) getLookupGroup() *singleflight.Group {
	if r == nil {
		return &DefaultResolver.lookupGroup
	}
	return &r.lookupGroup
}
// We don't want a cancellation of ctx to affect the
// lookupGroup operation. Otherwise if our context gets
// canceled it might cause an error to be returned to a lookup
// using a completely different context. However we need to preserve
// only the values in context. See Issue 28600.
lookupGroupCtx, lookupGroupCancel := context.WithCancel(withUnexpiredValuesPreserved(ctx))
lookupKey := network + "\000" + host
dnsWaitGroup.Add(1)
ch, called := r.getLookupGroup().DoChan(lookupKey, func() (interface{}, error) {
	defer dnsWaitGroup.Done()
	return testHookLookupIP(lookupGroupCtx, resolverFunc, network, host)
})
if !called {
	dnsWaitGroup.Done()
}

Use case 2 — DataLayer and BFF in microservices

When we were developing with microservices, I think it is applicable to the following specifications.

Client

Microservice A — BFF layer

Microservice B — Database layer

When illustrated, the connection is as follows.

Client calling Microservice A (BFF), which fans out duplicate calls to Microservice B (database layer) — the calls singleflight can consolidate

As described above, in this use case, the master information referred to in the API provided by Microservice B is considered to be a resource that does not change much, and by using singleflight on the Microservice A side of the caller, it is possible to consolidate duplicate API calls.

Conclusion

That’s all there is to it. I’m glad if this article gave you the opportunity to give the singleflight package a try.


Edit page
Share this post:

Previous Post
I Didn't Write the Code, I Wrote the Grader
Next Post
Hello, world