Lazy Load with golang 🚀
Published at December 7, 2023
Have you ever encountered a situation where your server doesn’t require a particular configuration loaded into the memory until some user request happened to invoke a particular handler that need it ? Although loading all configurations during boot time ensures functionality , but make your service longer to be active. A better solution is to postponse that load until they are needed. That’s called Lazy Loading
The program beblow will show a simple structure of how lazy loading work
package main
import (
)
// the config that you need
var Dbconfig map[string]string
// DB config only needed when invoke any handler that require db access
func loadDBConfig() {
// Stimulate the delay happen to load those config
time.Sleep(time.Second)
log.Println(
"Loading Configuration"
)
Dbconfig = map[string]string {
"dsn" : "mysql://....."
}
}
func lazyLoadConfig() string{
if Dbconfig == nil {
loadDBConfig()
}
return Dbconfig["dsn"]
}
It’s that simple. If Some handler need the configuration , the lazyLoadConfig will first check if the configuration is nill. Otherwise it will invoke the loadDBConfig.
But its not happen to be that’s simple in the real world , where concurrent request can make loadDBConfig run multiple time. Becuase each request is handled by its own gorountine, which can run at any order. Our lazy logic only ensure to check Dbconfig existence. But lack of the mechanism to ensure the state of concurrent goroutine
func getDSN() {
dsn := lazyLoadConfig()
// do some thing with dsn here
}
func main() {
var wg sync.WaitGtroup
concurrentNo := 5
for i := 0 ; i < concurrentNo ; i++ {
wg.Add(1)
go func () {
getDSN()
wg.Done()
}()
}
wg.Wait()
}
Here we see that lazyLoadConfig will be called 5 times
2023/12/07 Loading configuration
2023/12/07 Loading configuration
2023/12/07 Loading configuration
2023/12/07 Loading configuration
2023/12/07 Loading configuration
In order to prevent it , we need some mechanism to ensure the lazyLoadConfig will only run once. This is where sync.Once can prevent multiple called
package main
import (
// the config that you need
var Dbconfig map[string]string
var Once sync.Once = sync.Once{}
// DB config only needed when invoke any handler that require db access
func loadDBConfig() {
// Stimulate the delay happen to load those config
time.Sleep(time.Second)
log.Println(
"Loading Configuration"
)
Dbconfig = map[string]string {
"dsn" : "mysql://....."
}
}
func lazyLoadConfig() string{
if Dbconfig == nil {
Once.Do(loadDBConfig) // Execution is now take care of by sync.Once
}
return Dbconfig["dsn"]
}
Now we can see that lazyLoadConfig only called once
2023/12/07 Loading configuration
The lazier way
If you are lazier , you can import My Helper for it, so you dont have to create sync.Once all over your code:
import (
"github.com/diontr00/go-helper/lazy"
)
func main() {
lazyfn := lazy.New(func(){
// any load require
})
// How you invoke it. The lazyfn will only run one
lazyfn.Load()
lazyfn.Load()
lazyfn.Load()
lazyfn.Load()
lazyfn.Load()
}