Store in Go memory like localStorage Javascript?

I have a semi-static menu that should load from a database.

To make the load of menu faster I wonder if there is a way to store this menus as some type of global variables? Persistent or not.

You only need to create a struct to map your data. Also you can use a general cache library like GitHub - patrickmn/go-cache: An in-memory key:value store/cache (similar to Memcached) library for Go, suitable for single-machine applications.

I don’t think you need an external dependency for something this simple. Create a global variable of some kind, guard it with a mutex so it’s thread-safe, then update it based on some sort of interval. Something like:

// AppMenu stores the global app menu items
type AppMenu struct {
	Items []string
}

var (
	muAppMenu  = &sync.RWMutex{} // Guards `appMenu`.
	appMenu   AppMenu 
)

// GetAppMenu returns the current app menu.
func GetAppMenu() AppMenu {
	muAppMenu.RLock()
	menu := appMenu
	muAppMenu.RUnlock()
	return menu
}

// SetAppMenu sets the app menu.
func SetAppMenu(menu AppMenu) {
	muAppMenu.Lock()
	appMenu = menu
	muAppMenu.Unlock()
}

You could then somewhere in your main function spin up a goroutine to update your menu item property periodically:

// Context for our goroutine
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Spin up goroutine to update app menu items every 30 seconds
go func() {
	t := time.NewTicker(30 * time.Second)
	defer t.Stop()
	for {
		select {
		case <-ctx.Done():
			return // exit goroutine
		case <-t.C:
			menuItems := getMenuItemsFromDBOrWhatever()
			SetAppMenu(menuItems)
		}
	}
}()

Again, you COULD add a dependency, but I don’t think you need one. Also note that you would presumably need to populate the menu once before the ticker’s first tick. For more details as to why:

I do hope I can avoid dependencies. But there is no need for redraw AFIAK. The menu should be static and tailored dynamically for each user. My goal is to replace this Javascript code with Go if possible. Move this menu creation from browser to Go (Go templates) instead

Is there a good option for caching that is not +incompatible? Preferable able to store JSON as value.

Sounds simple, but I do not get this to work. And I do not need any updating. It will be a dozen of static menus I can switch dynamically between. Stored in memory hopefully.

package main

import (
	"fmt"
	"sync"
)

type AppMenu struct {
	Items []map
}

var (
	muAppMenu = &sync.RWMutex{}
	appMenu   AppMenu
)

// GetAppMenu returns the current app menu.
func GetAppMenu() AppMenu {
	muAppMenu.RLock()
	menu := appMenu
	muAppMenu.RUnlock()
	return menu
}

// SetAppMenu sets the app menu.
func SetAppMenu(menu AppMenu) {
	muAppMenu.Lock()
	appMenu = menu
	muAppMenu.Unlock()
}

// Context for our goroutine
func main() {
	menuItems := ("menu",`[{"key1":"value1"},{"key2":"value2"}]`)
	SetAppMenu(menuItems)
}

I found go2cache that seems to be one of the simplest. Is there any caveats with this code?

package main

import (
	"fmt"
	"github.com/muesli/cache2go"
)

type myStruct struct {
	text     string
}
func init(){
	fmt.Println("store ONCE")
	set_menu()
}

var cache = cache2go.Cache("menus")

func main() {
	get_menu()
	get_menu()
}

func set_menu() {
	val1 := myStruct{`[{"key1":"val1"},{"key2":"val2"}]`}
	val2 := myStruct{`[{"key3":"val3"},{"key4":"val4"}]`}
	cache.Add("menu1", 0, &val1)
	cache.Add("menu2", 0, &val2)
}

func get_menu() {
	res1, err := cache.Value("menu1")
	res2, err := cache.Value("menu2")
	if err == nil {
		fmt.Println(res1.Data().(*myStruct).text)
		fmt.Println(res2.Data().(*myStruct).text)
	}
}

The type of the Items field is invalid. Line 34 is not Go syntax. If you fix those it should work just fine. I would defer the unlocking in case a panic is recovered later and you want the code to continue working.

Like this?

func get_menu() {
	res1, err := cache.Value("menu1")
	res2, err := cache.Value("menu2")
	if err == nil {
		defer fmt.Println(res1.Data().(*myStruct).text)
		defer fmt.Println(res2.Data().(*myStruct).text)
	}
}

No, I was referring to the code in the post I replied to:

instead

But on closer inspection, there’s nothing here that could panic. I just have a knee-jerk response to undeferred unlocking.

1 Like

Read-only data can be initialized safely with sync.Once. It can also be initialized anytime before starting the web server. If the data never changes after this, is there any synchronization needed at all, for reading?

In order to use simple read-mostly memory-persistent data in a web application, you need to use low-level synchronization primitives like sync.RWMutex? This is what go-cache does.

Can this use case be implemented without mutexes?

If not, why does the sync package promote channels and communication?

My menu data will be changed maybe twice a year. So the data in cache is almost static. The simplest way I found wad go2cache. I like to be proven wrong…

If your data does not change for days at a time, you can restart the server manually whenever the data changes. So in effect the data does not change at all while the server is running. Then I’m almost sure you don’t need any synchronization, assuming that you don’t make any changes at all to the data structure that holds your menu.

You don’t need a global variable either (in all cases). Just put the data in an application object, usually in an http handler. If you create the handler and populate it with your data when the server starts, before it serves any requests, it should have no problem.

If you do need a simple synchronized map, there is one available in sync.Map.

Any example or link so I understand?

Here’s an example of an HTTP server that returns JSON data from memory.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type MenuItem struct {
	Url  string
	Name string
}

type MenuHandler struct {
	items []MenuItem
}

func NewMenuHandler() *MenuHandler {
	var h MenuHandler
	h.loadItems()
	return &h
}

func (h *MenuHandler) loadItems() {
	h.items = []MenuItem{
		MenuItem{"/home", "Home"},
		MenuItem{"/login", "Login"},
	}
}

func (h *MenuHandler) Items() []MenuItem {
	return h.items
}

func (h *MenuHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	items := h.Items()
	data, err := json.Marshal(items)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write(data)
}

func main() {
	menuHandler := NewMenuHandler()
	server := http.NewServeMux()
	server.Handle("/menu", menuHandler)
	addr := ":8080"
	fmt.Printf("listening at %s\n", addr)
	err := http.ListenAndServe(addr, server)
	if err != nil {
		fmt.Printf("%v\n", err)
	}
}

Run it as follows:

go run menu.go

And test is from another shell like this:

curl -i http://localhost:8080/menu

If you want to defer loading of the menu at the first request, you can use sync.Once:

type MenuHandler struct {
	items    []MenuItem
	loadOnce sync.Once
}

func (h *MenuHandler) Items() []MenuItem {
	h.loadOnce.Do(h.loadItems)
	return h.items
}

Any code before ListenAndServe() runs in a single goroutine, so it needs no synchronization.
Any code that is called from ServeHTTP() runs in a multi-threaded fashion, so it should worry about synchronizing access to data structures.

The original question was about storing things in memory. Another question is how to synchronize access to such data. I believe that reading a data structure, like the handler items field, does not need synchronization, if no other code makes concurrent changes to it. I don’t see this explicitly stated in the Go Language Specification. Can anyone point this out to me or disprove it? If you want to synchronize fully there are several mechanisms mentioned in this thread.

1 Like

It is certainly possible to write a fully synchronized cache using channels instead of explicit mutexes. I did so and I found it very educational for learning about channels and goroutines.

Here’s the gist of it:

  • A Get() method creates a request and sends it to a request channel. The request itself has its own temporary channel for receiving the reply. Get() waits until this reply channel is closed.
  • A background goroutine waits for requests. When it receives a request, it gets the data from memory or from anywhere else, puts the data back in the request, and closes the request’s reply channel.
  • When the reply channel closes, Get() wakes up and returns the data.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.