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()

}

Khanh Anh Trinh © 2024 ♥