Skip to content

golang_setup

guoling edited this page Jul 5, 2024 · 9 revisions

MMKV for Golang (on POSIX)

MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on both Android, iOS/macOS, Windows and Python/Golang (POSIX).

Getting Started

Prerequisites

  • Go 1.15 and above.
  • CMake 3.8.0 and above.
  • C++ compiler that supports C++ 20 standard.
  • Linux(Ubuntu, Arch Linux, CentOS, Gentoo), Unix(macOS, FreeBSD, OpenBSD) are supported.

Installation Via Source

  1. Getting source code from the git repository:
git clone https://github.com/Tencent/MMKV.git
  1. Build & install the MMKV native library for Golang:
cd POSIX/golang
cmake . -DCMAKE_INSTALL_PREFIX=.
make -j8 install

The MMKV package is now installed in tencent.com/mmkv under the current directory.
That's it. We are all set.

  • Note: If you want to install MMKV other than the source directory, you can set CMAKE_INSTALL_PREFIX when running cmake:
cmake . -DCMAKE_INSTALL_PREFIX=/path/to/your/project/packages
make -j8 install

Or, you can just copy the generated directory tencent.com/mmkv to your project.

  1. Test MMKV:
cd test
go run main.go
  • Note: You might need to edit the file test/go.mod to set the actual install directory of the MMKV package:
    replace tencent.com/mmkv => /path/to/tencent.com/mmkv

Tutorial

You can use MMKV as you go. All changes are saved immediately, no save, no sync calls are needed.

Configuration

  • Edit your project's go.mod file, add MMKV's local location:
    replace tencent.com/mmkv => /path/to/tencent.com/mmkv
    Run go install to make sure that location is correct.

  • Setup MMKV on App startup, say in your main() function, add these code:

    package main
    
    import "tencent.com/mmkv"
    
    func main() {
        // init MMKV with root dir
        mmkv.InitializeMMKV("/path/to/your/working/directory")
    }

CRUD Operations

  • MMKV has a global instance, that can be used directly:

     kv := mmkv.DefaultMMKV()
    
     kv.SetBool(true, "bool")
     fmt.Println("bool =", kv.GetBool("bool"))
    
     kv.SetInt32(math.MinInt32, "int32")
     fmt.Println("int32 =", kv.GetInt32("int32"))
    
     kv.SetUInt32(math.MaxUint32, "uint32")
     fmt.Println("uint32 =", kv.GetUInt32("uint32"))
    
     kv.SetInt64(math.MinInt64, "int64")
     fmt.Println("int64 =", kv.GetInt64("int64"))
    
     kv.SetUInt64(math.MaxUint64, "uint64")
     fmt.Println("uint64 =", kv.GetUInt64("uint64"))
    
     kv.SetFloat32(math.MaxFloat32, "float32")
     fmt.Println("float32 =", kv.GetFloat32("float32"))
    
     kv.SetFloat64(math.MaxFloat64, "float64")
     fmt.Println("float64 =", kv.GetFloat64("float64"))
    
     kv.SetString("Hello world, 你好 from MMKV!", "string")
     fmt.Println("string =", kv.GetString("string"))
    
     kv.SetBytes([]byte("Hello world, 你好 from MMKV 以及 bytes!"), "bytes")
     fmt.Println("bytes =", string(kv.GetBytes("bytes")))

    As you can see, MMKV is quite easy to use.

  • Deleting & Querying:

      kv := mmkv.DefaultMMKV()
      fmt.Println("keys before remove:", kv.AllKeys())
    
      kv.RemoveKey("bool")
      fmt.Println("after remove, contains \"bool\"? ", kv.Contains("bool"))
    
      kv.RemoveKeys([]string{"int32", "int64"})
      fmt.Println("keys after remove:", kv.AllKeys())
  • If different modules/logics need isolated storage, you can also create your own MMKV instance separately:

      kv = mmkv.MMKVWithID("test_golang")
      kv.SetBool(true, "bool")
  • If multi-process accessing is needed, you can set MMKV_MULTI_PROCESS on MMKV initialization:

      kv := mmkv.MMKVWithIDAndMode(mmapID, mmkv.MMKV_SINGLE_PROCESS)
      kv.SetBool(true, "bool")
  • If encryption is needed, you can set the encryption key on MMKV initialization:

      kv := mmkv.MMKVWithIDAndModeAndCryptKey(mmapID, mmkv.MMKV_SINGLE_PROCESS, "my_key")
      kv.SetBool(true, "bool")

    You can change the encryption key later by calling ReKey(). You can also change an existing MMKV instance from encrypted to unencrypted by passing an empty key, or vice versa.

Supported Types

  • Primitive Types:

    • bool, int, int32, uint32, int64, uint64, float32, float64
  • Struct & Slice:

    • string, []byte

Logs

  • MMKV prints log to stdout, which is not convenient for diagnosing online issues. You can set up MMKV log redirecting on App startup. Implement a callback function with signature func(level int, file string, line int, function string, message string), register it as log handler.

      func logHandler(level int, file string, line int, function string, message string) {
      	var levelStr string
      	switch level {
      	case mmkv.MMKVLogDebug:
      		levelStr = "[D]"
      	case mmkv.MMKVLogInfo:
      		levelStr = "[I]"
      	case mmkv.MMKVLogWarning:
      		levelStr = "[W]"
      	case mmkv.MMKVLogError:
      		levelStr = "[E]"
      	default:
      		levelStr = "[N]"
      	}
      	fmt.Printf("Redirect %v <%v:%v::%v> %v\n", levelStr, file, line, function, message)
      }
    
      func main() {
          // init MMKV with root dir and log redirecting
      	mmkv.InitializeMMKVWithLogLevelAndHandler("/path/to/your/working/directory", mmkv.MMKVLogInfo, logHandler)
      }
  • By default, MMKV print logs to stdout. You can turn off logging in initialization.
    Note: You should never turn MMKV's log off unless you have very strong evidence that it makes your App slow.

      mmkv.InitializeMMKVWithLogLevel("/path/to/your/working/directory", mmkv.MMKVLogNone)

Native Buffer

  • Typically, when a string or []byte value is getting from MMKV, there's a memory-copying from native to golang. And if that value is passed to another native library(C) immediately, another memory-copying from golang to native happens. The whole process wastes too much if that value's size is large. Here comes the Native Buffer to the rescue.
    Native Buffer is a memory buffer created in native, wrapped as MMBuffer in golang, which can be accessed as a string or byte slice shortly, or passed to other native libraries with memory address and length. Example code:

      kv.SetString("Hello world, 你好 from MMKV!", "string")
    
      // much more efficient
      buffer := kv.GetStringBuffer("string")
      fmt.Println("string(buffer) =", buffer.StringView())
      // must call Destroy() after usage
      buffer.Destroy()
    
      kv.SetBytes([]byte("Hello world, 你好 from MMKV 以及 bytes!"), "bytes")
    
      // much more efficient
      buffer = kv.GetBytesBuffer("bytes")
      fmt.Println("bytes(buffer) =", string(buffer.ByteSliceView()))
      // must call Destroy() after usage
      buffer.Destroy()

    Note: Remember to call Destroy() to free the native memory of the MMBuffer.

Recover from data corruption

  • By default, MMKV discards all data when there's a crc check fail, or file length is not correct, which might happen on accidentally shutdown. You can tell MMKV to recover as much data as possible. The repair rate is not promised, though. And you might get unexpected key values from recovery. Implement a callback with type func(mmapID string, error int) int, add some code like these:

      func errorHandler(mmapID string, error int) int {
      	var errorDesc string
      	if error == mmkv.MMKVCRCCheckFail {
      		errorDesc = "CRC-Error"
      	} else {
      		errorDesc = "File-Length-Error"
      	}
      	fmt.Println(mmapID, "has error type:", errorDesc)
      
      	// return OnErrorRecover to recover as many data as possible
      	return mmkv.OnErrorRecover
      }
    
      func main() {
      	// init MMKV with root dir
      	mmkv.InitializeMMKV("/path/to/your/working/directory")
    
      	// you can set error handler
      	mmkv.RegisterErrorHandler(errorHandler)
      }

Backup & Restore

  • You can use MMKV's backup & restore API to backup your data to somewhere else, and restore them later.

      rootDir := "/tmp/mmkv_backup"
      // backup one instance
      ret := mmkv.BackupOneToDirectory(mmapID, rootDir)
      // backup all instances
      count := mmkv.BackupAllToDirectory(rootDir)
      
      // restore one instance
      ret = mmkv.RestoreOneFromDirectory(mmapID, rootDir)
      // restore all instances
      count = mmkv.RestoreAllFromDirectory(rootDir)

Auto Expiration

  • Starting from v1.3.0, you can upgrade MMKV to auto key expiration. Note that this is a breaking change. Once upgraded to auto key expiration, the file is not valid for any older version of MMKV (<= v1.2.16) to operate correctly.

  • Global Expiration. The most simple way to do it is to turn on auto key expiration for all keys in the whole file.

    // expire in a day
    kv.EnableAutoKeyExpire(24 * 60 * 60);

    Or, if you prefer, you can enable auto key expiration without setting a global expiration duration. In this case, each key is not expired by default.

    // enable auto key expiration without global duration
    kv.EnableAutoKeyExpire(0);
  • Individual Expiration. You can set a special expiration duration for a key, regardless with the file has a global duration or not. Note that you have to enable auto key expiration first.

    // enable auto key expiration with an hour duration
    kv.EnableAutoKeyExpire(60 * 60);
    
    // set a key with the file's global expiration duration, aka 60 * 60
    kv.SetString("some value", "key_1");
    
    // set a special key that expires in two hours
    kv.SetStringExpire("some value", "key_2", 2 * 60 * 60);
    
    // set a special key that never expires
    kv.SetStringExpire("some value", "key_3", 0);

    Or, if you prefer, you can enable auto key expiration without setting a global expiration duration. In this case, each key is not expired by default.

    // enable auto key expiration without global duration
    kv.EnableAutoKeyExpire(0);
    
    // set a key that never expires
    kv.SetString("some value", "key_1");
    
    // set a special key that expires in an hour
    kv.SetStringExpire("some value", "key_2", 60 * 60);
Clone this wiki locally