expiremap

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 1, 2024 License: MIT Imports: 3 Imported by: 3

README

Go expire map

GoDoc Go Report Card

Quick start

package main

import (
    "fmt"
    "github.com/nursik/go-expire-map"
    "time"
)
func main() {
    expireMap := expiremap.New()
    // You must call Close(), when you do not need the map anymore
    defer expireMap.Close()

    // GMT Wednesday, 1 January 2025, 0:00:00
    far := time.Unix(1735689600, 0)

    ttl := far.Sub(time.Now())

    // Insert
    expireMap.Set(1, 1, ttl)

    // Get value
    v, ok := expireMap.Get(1)
    fmt.Println(v, ok)
    // Output 1 true

    // Get TTL
    v = expireMap.GetTTL(1)
    // The output is equal to ~ ttl
    fmt.Println(v)

    // Update TTL
    v, ok = expireMap.SetTTL(1, time.Second)
    fmt.Println(v, ok)
    // Output 1 true

    time.Sleep(time.Second + time.Millisecond)

    // Because key is already expired, it returns nil, false
    v, ok = expireMap.SetTTL(1, ttl)
    fmt.Println(v, ok)
    // Output nil false

    // Because key is already expired, it returns nil, false
    v, ok = expireMap.Get(1)
    fmt.Println(v, ok)
    // Output nil false
}

Why/when

  • You need thread safe map with comparable keys and interface values
  • You have a lot of inserts with the same TTL. If they all expire at the same time you don't want your app freeze
  • You need both active and passive expiration
  • You need notifications about inserts, update, deletes and expirations

Documentation

Overview

Package expiremap provides a thread-safe map with expiring keys. You must pay attention for these facts:

  1. Current implementation may hold up to 1 billion keys
  2. After creating a new map (calling New()), goroutine is created for deletion expired keys. It exists until Close() method is called
  3. There are active and passive expirations. Active expiration is done during Get(), and SetTTL() calls. Passive expiration happens in background and is done by goroutine
  4. Passive expiration occurs every 100ms. It is done in two steps - first step is inspired by algorithm used in Redis and second step is sequential expiration
  5. It is guaranteed by sequential expiration, that no expired key will live more than map.Size() / 200 seconds

First step's (or random expire) algorithm is following:

  1. Check the size of the map. If it is less than 100, just iterate over all keys and stop algorithm
  2. Check 20 random keys. Remove all expired keys. If there were at least 5 deletions, do the step 2 again (step 2 is done maximum 10 times)

Second step's (or rotate expire) algorithm is following:

  1. Load to X a key on which we stopped on the previous call. If on previous call we hit the bottom of the map, load top key of the map
  2. Start from the key X and from that key expire 20 consecutive keys or stop if we hit a bottom of the map

It means that at maximum 2200 expires per second may occur (not counting active expiration). If you have a lot of insertions with unique keys, but you rarely call methods Get and SetTTL on these keys, your map will grow faster than expiration rate and you may hit 1 billion keys limit.

Example
package main

import (
	"fmt"
	"time"

	expiremap "github.com/nursik/go-expire-map"
)

func main() {
	expireMap := expiremap.New()
	// You must call Close(), when you do not need the map anymore
	defer expireMap.Close()

	// GMT Wednesday, 1 January 2025, 0:00:00
	far := time.Unix(1735689600, 0)

	ttl := time.Until(far)

	// Insert
	expireMap.Set(1, 1, ttl)

	// Get value
	v, ok := expireMap.Get(1)
	fmt.Println(v, ok)
	// Output 1 true

	// Get TTL
	v = expireMap.GetTTL(1)
	// The output is equal to ~ ttl
	fmt.Println(v)

	// Update TTL
	v, ok = expireMap.SetTTL(1, time.Second)
	fmt.Println(v, ok)
	// Output 1 true

	time.Sleep(time.Second + time.Millisecond)

	// Because key is already expired, it returns nil, false
	v, ok = expireMap.SetTTL(1, ttl)
	fmt.Println(v, ok)
	// Output nil false

	// Because key is already expired, it returns nil, false
	v, ok = expireMap.Get(1)
	fmt.Println(v, ok)
	// Output nil false
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Event added in v1.1.0

type Event struct {
	Key   interface{}
	Value interface{}
	// Time is when this event occurred (in Unix nanoseconds)
	Time int64
	// Due is when this key will expire (in Unix nanoseconds, 0 for expired)
	Due int64
	// Type of the Event. Equal to Expire, Delete, Update or Set.
	Type EventType
}

Event is used for notification channel

type EventType added in v1.1.0

type EventType uint8

EventType is used for notification channel

const (
	// Expire event is fired, when key is deleted due to expiration
	Expire EventType = 1 << iota
	// Delete event is fired, when key explicitly deleted using Delete
	// or SetTTL with non positive TTL
	Delete
	// Update event is fired, when TTL or value is updated
	Update
	// Set event is fired, when new key is inserted
	Set
	// AllEvents is a helper constant, which is the same as
	// Expire | Delete | Update | Set
	AllEvents = Expire | Delete | Update | Set
	// NoEvents is a helper constant, which is the same as 0 (no events)
	NoEvents = 0
)

type ExpireMap

type ExpireMap struct {
	// contains filtered or unexported fields
}

ExpireMap stores keys and corresponding values and TTLs.

func New

func New() *ExpireMap

New returns a new map.

func (*ExpireMap) Close

func (m *ExpireMap) Close()

Close stops goroutine and channel.

func (*ExpireMap) Curtime

func (m *ExpireMap) Curtime() int64

Curtime returns current time in Unix nanoseconds

func (*ExpireMap) Delete

func (m *ExpireMap) Delete(key interface{})

Delete removes key from the map.

func (*ExpireMap) Get

func (m *ExpireMap) Get(key interface{}) (interface{}, bool)

Get returns value for the given key. If map does not contain such key or key is expired, it returns nil and "false". If key is expired, then it waits for write lock, checks a ttl again (as during wait of write lock, value and ttl could be updated) and if it is still expired, removes the given key (otherwise it returns a value and "true").

func (*ExpireMap) GetAll

func (m *ExpireMap) GetAll() []KeyValue

GetAll returns a slice of KeyValue.

func (*ExpireMap) GetTTL

func (m *ExpireMap) GetTTL(key interface{}) int64

GetTTL returns time in nanoseconds, when key will die. If key is expired or does not exist in the map, it returns 0.

func (*ExpireMap) Notify added in v1.1.0

func (m *ExpireMap) Notify(c chan<- Event, events EventType)

Notify sets a channel and causes a map to send Event to a channel based on the given EventType. To get events X1, X2... pass X1|X2|...(for example, to get Update and Set events pass Update|Set). To receive all events use AllEvents constant (the same as Update|Set|Delete|Expire). To receive no events use NoEvents constant or set nil chan. When the map stops, no events are guaranteed to be sent to the channel. It is up to the user to close the channel.

Example
package main

import (
	"fmt"
	"time"

	expiremap "github.com/nursik/go-expire-map"
)

func main() {
	expireMap := expiremap.New()
	defer expireMap.Close()

	c := make(chan expiremap.Event, 10)
	expireMap.Notify(c, expiremap.AllEvents)
	var event expiremap.Event
	// Set
	expireMap.Set("key1", "value1", time.Second)
	event = <-c
	fmt.Println(event.Key, event.Value, event.Type == expiremap.Set)

	// Update
	expireMap.Set("key1", "value1", time.Second)
	event = <-c
	fmt.Println(event.Key, event.Value, event.Type == expiremap.Update)

	expireMap.Set("key2", "value2", time.Hour)
	<-c
	expireMap.Delete("key2")
	event = <-c
	fmt.Println(event.Key, event.Value, event.Type == expiremap.Delete)

	// Causes Expire event to be fired
	time.Sleep(time.Second)

	event = <-c
	fmt.Println(event.Key, event.Value, event.Type == expiremap.Expire)

}
Output:
key1 value1 true
key1 value1 true
key2 value2 true
key1 value1 true

func (*ExpireMap) Set

func (m *ExpireMap) Set(key interface{}, value interface{}, ttl time.Duration)

Set sets or updates value and ttl for the given key

func (*ExpireMap) SetTTL

func (m *ExpireMap) SetTTL(key interface{}, ttl time.Duration) (interface{}, bool)

SetTTL updates ttl for the given key. If ttl was successfully updated, it returns value and "true". It happens, if and only if key presents in the map and "ttl" variable is greater than timeResolution. In any other case it returns nil and "false". Also, if "ttl" variable is non positive, it just removes a key.

func (*ExpireMap) Size

func (m *ExpireMap) Size() int

Size returns a number of keys in the map, both expired and unexpired.

func (*ExpireMap) Stopped

func (m *ExpireMap) Stopped() bool

Stopped indicates that map is stopped.

type KeyValue

type KeyValue struct {
	Key   interface{}
	Value interface{}
}

KeyValue is used only for GetAll method

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL