Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs #17

Merged
merged 1 commit into from
Jul 8, 2024
Merged

Docs #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 10 additions & 44 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,16 @@ linters:
- asciicheck # checks that your code does not contain non-ASCII identifiers
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
# - cyclop # checks function and package cyclomatic complexity
- cyclop # checks function and package cyclomatic complexity
- dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
# - gochecknoglobals # checks that no global variables exist
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types"
- gocognit # computes and checks the cognitive complexity of functions
Expand Down Expand Up @@ -98,36 +96,28 @@ linters:
- decorder # checks declaration order and count of types, constants, variables and functions
- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- paralleltest # detects missing usage of t.Parallel() method in your Go test
- tagalign # checks that struct tags are well aligned

## you may want to enable
## may want to enable
# - wrapcheck # checks that errors returned from external packages are wrapped
#- gci # controls golang package import order and makes it always deterministic
#- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega
#- godox # detects FIXME, TODO and other comment keywords
#- goheader # checks is file header matches to pattern
#- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters
#- interfacebloat # checks the number of methods inside an interface
#- ireturn # accept interfaces, return concrete types
#- tagalign # checks that struct tags are well aligned
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
#- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event
# - ireturn # accept interfaces, return concrete types
# - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
# - nlreturn # checks for a new line before return and branch statements to increase code clarity

## disabled
# - lll # reports long lines
# - exhaustruct # [highly recommend to enable] checks if all structure fields are initialized
#- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
#- dupword # [useless without config] checks for duplicate words in the source code
#- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
#- forcetypeassert # [replaced by errcheck] finds forced type assertions
# - exhaustruct # checks if all structure fields are initialized
#- goerr113 # [too strict] checks the errors handling expressions
#- gofmt # [replaced by goimports] checks whether code was gofmt-ed
#- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
#- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase
#- grouper # analyzes expression groups
#- importas # enforces consistent import aliases
#- maintidx # measures the maintainability index of each function
#- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
#- tagliatelle # checks the struct tags
#- contextcheck # Check whether the function uses a non-inherited context.

Expand Down Expand Up @@ -163,33 +153,9 @@ linters-settings:
nlreturn:
block-size: 8

exhaustruct:
# Default: []
exclude:
- ".*_test.go$"
# std libs
- "^net/http.Client$"
- "^net/http.Cookie$"
- "^net/http.Request$"
- "^net/http.Response$"
- "^net/http.Server$"
- "^net/http.Transport$"
- "^net/url.URL$"
- "^os/exec.Cmd$"
- "^reflect.StructField$"
# public libs
- "^github.com/Shopify/sarama.Config$"
- "^github.com/Shopify/sarama.ProducerMessage$"
- "^github.com/mitchellh/mapstructure.DecoderConfig$"
- "^github.com/prometheus/client_golang/.+Opts$"
- "^github.com/spf13/cobra.Command$"
- "^github.com/spf13/cobra.CompletionOptions$"
- "^github.com/stretchr/testify/mock.Mock$"
- "^github.com/testcontainers/testcontainers-go.+Request$"
- "^github.com/testcontainers/testcontainers-go.FromDockerfile$"
- "^golang.org/x/tools/go/analysis.Analyzer$"
- "^google.golang.org/protobuf/.+Options$"
- "^gopkg.in/yaml.v3.Node$"
gosec:
excludes:
- G404 # Ignore insecure random number source.

funlen:
# Checks the number of lines in a function.
Expand Down
81 changes: 74 additions & 7 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ func (c *Client[T]) getWithState(key string) (value T, exists, markedAsMissing,
}

// Get retrieves a single value from the cache.
//
// Parameters:
//
// key - The key to be retrieved.
//
// Returns:
//
// The value corresponding to the key and a boolean indicating if the value was found.
func (c *Client[T]) Get(key string) (T, bool) {
shard := c.getShard(key)
val, ok, markedAsMissing, refresh := shard.get(key)
Expand All @@ -143,6 +151,14 @@ func (c *Client[T]) Get(key string) (T, bool) {
}

// GetMany retrieves multiple values from the cache.
//
// Parameters:
//
// keys - The list of keys to be retrieved.
//
// Returns:
//
// A map of keys to their corresponding values.
func (c *Client[T]) GetMany(keys []string) map[string]T {
records := make(map[string]T, len(keys))
for _, key := range keys {
Expand All @@ -155,9 +171,18 @@ func (c *Client[T]) GetMany(keys []string) map[string]T {

// GetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
// You provide it with a slice of IDs and a keyFn, which is applied to create
// the cache key. The returned map uses the IDs as keys instead of the cache key.
// If you've used ScanKeys to retrieve the actual keys, you can retrieve the records
// using GetMany instead.
// the cache key. The returned map uses the IDs as keys instead of the cache
// key. If you've used ScanKeys to retrieve the actual keys, you can retrieve
// the records using GetMany instead.
//
// Parameters:
//
// ids - The list of IDs to be retrieved.
// keyFn - A function that generates the cache key for each ID.
//
// Returns:
//
// A map of IDs to their corresponding values.
func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
records := make(map[string]T, len(ids))
for _, id := range ids {
Expand All @@ -168,7 +193,16 @@ func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
return records
}

// Set writes a single value to the cache. Returns true if it triggered an eviction.
// Set writes a single value to the cache.
//
// Parameters:
//
// key - The key to be set.
// value - The value to be associated with the key.
//
// Returns:
//
// A boolean indicating if the set operation triggered an eviction.
func (c *Client[T]) Set(key string, value T) bool {
shard := c.getShard(key)
return shard.set(key, value, false)
Expand All @@ -181,7 +215,15 @@ func (c *Client[T]) StoreMissingRecord(key string) bool {
return shard.set(key, zero, true)
}

// SetMany writes a map of key value pairs to the cache.
// SetMany writes a map of key-value pairs to the cache.
//
// Parameters:
//
// records - A map of keys to values to be set in the cache.
//
// Returns:
//
// A boolean indicating if any of the set operations triggered an eviction.
func (c *Client[T]) SetMany(records map[string]T) bool {
var triggeredEviction bool
for key, value := range records {
Expand All @@ -193,9 +235,18 @@ func (c *Client[T]) SetMany(records map[string]T) bool {
return triggeredEviction
}

// SetManyKeyFn follows the same API as GetOrFetchBatch and PassThroughBatch. It
// takes a map of records where the keyFn is applied to each key in the map
// SetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
// It takes a map of records where the keyFn is applied to each key in the map
// before it's stored in the cache.
//
// Parameters:
//
// records - A map of IDs to values to be set in the cache.
// cacheKeyFn - A function that generates the cache key for each ID.
//
// Returns:
//
// A boolean indicating if any of the set operations triggered an eviction.
func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
var triggeredEviction bool
for id, value := range records {
Expand All @@ -208,6 +259,10 @@ func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
}

// ScanKeys returns a list of all keys in the cache.
//
// Returns:
//
// A slice of strings representing all the keys in the cache.
func (c *Client[T]) ScanKeys() []string {
keys := make([]string, 0, c.Size())
for _, shard := range c.shards {
Expand All @@ -217,6 +272,10 @@ func (c *Client[T]) ScanKeys() []string {
}

// Size returns the number of entries in the cache.
//
// Returns:
//
// An integer representing the total number of entries in the cache.
func (c *Client[T]) Size() int {
var sum int
for _, shard := range c.shards {
Expand All @@ -226,12 +285,20 @@ func (c *Client[T]) Size() int {
}

// Delete removes a single entry from the cache.
//
// Parameters:
//
// key: The key of the entry to be removed.
func (c *Client[T]) Delete(key string) {
shard := c.getShard(key)
shard.delete(key)
}

// NumKeysInflight returns the number of keys that are currently being fetched.
//
// Returns:
//
// An integer representing the total number of keys that are currently being fetched.
func (c *Client[T]) NumKeysInflight() int {
c.inFlightMutex.Lock()
defer c.inFlightMutex.Unlock()
Expand Down
8 changes: 4 additions & 4 deletions distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
}

return func(ctx context.Context, ids []string) (map[string]V, error) {
// We need to be able to lookup the ID of the record based on the key.
// We need to be able to look up the ID of the record based on the key.
keyIDMap := make(map[string]string, len(ids))
keys := make([]string, 0, len(ids))
for _, id := range ids {
Expand Down Expand Up @@ -193,7 +193,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet

// If distributedStaleStorage isn't enabled it means all records are fresh, otherwise checked the CreatedAt time.
if !c.distributedEarlyRefreshes || c.clock.Since(record.CreatedAt) < c.distributedRefreshAfterDuration {
// We never wan't to return missing records.
// We never want to return missing records.
if !record.IsMissingRecord {
fresh[id] = record.Value
} else {
Expand All @@ -205,7 +205,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
idsToRefresh = append(idsToRefresh, id)
c.reportDistributedRefresh()

// We never wan't to return missing records.
// We never want to return missing records.
if !record.IsMissingRecord {
stale[id] = record.Value
} else {
Expand All @@ -218,7 +218,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
}

dataSourceResponses, err := fetchFn(ctx, idsToRefresh)
// Incase of an error, we'll proceed with the ones we got from the distributed storage.
// In case of an error, we'll proceed with the ones we got from the distributed storage.
if err != nil {
for i := 0; i < len(stale); i++ {
c.reportDistributedStaleFallback()
Expand Down
73 changes: 64 additions & 9 deletions fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,40 @@ func getFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn F
}

// GetOrFetch attempts to retrieve the specified key from the cache. If the value
// is absent, it invokes the "fetchFn" function to obtain it and then stores
// the result. Additionally, when background refreshes is enabled, GetOrFetch
// determines if the record needs refreshing and, if necessary, schedules this
// task for background execution.
// is absent, it invokes the fetchFn function to obtain it and then stores the result.
// Additionally, when background refreshes are enabled, GetOrFetch determines if the record
// needs refreshing and, if necessary, schedules this task for background execution.
//
// Parameters:
//
// ctx - The context to be used for the request.
// key - The key to be fetched.
// fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
//
// Returns:
//
// The value corresponding to the key and an error if one occurred.
func (c *Client[T]) GetOrFetch(ctx context.Context, key string, fetchFn FetchFn[T]) (T, error) {
return getFetch[T, T](ctx, c, key, fetchFn)
}

// GetOrFetch is a convenience function that performs type assertion on the result of client.GetOrFetch.
//
// Parameters:
//
// ctx - The context to be used for the request.
// c - The cache client.
// key - The key to be fetched.
// fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
//
// Returns:
//
// The value corresponding to the key and an error if one occurred.
//
// Type Parameters:
//
// V - The type returned by the fetchFn. Must be assignable to T.
// T - The type stored in the cache.
func GetOrFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn FetchFn[V]) (V, error) {
res, err := getFetch[V, T](ctx, c, key, fetchFn)
return unwrap[V](res, err)
Expand Down Expand Up @@ -104,16 +129,46 @@ func getFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, ke
return cachedRecords, nil
}

// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If any
// of the values are absent, it invokes the fetchFn function to obtain them and
// then stores the result. Additionally, when background refreshes is enabled,
// GetOrFetch determines if any of the records needs refreshing and, if
// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If
// any of the values are absent, it invokes the fetchFn function to obtain them
// and then stores the result. Additionally, when background refreshes are
// enabled, GetOrFetch determines if any of the records need refreshing and, if
// necessary, schedules this to be performed in the background.
//
// Parameters:
//
// ctx - The context to be used for the request.
// ids - The list of IDs to be fetched.
// keyFn - Used to generate the cache key for each ID.
// fetchFn - Used to retrieve the data from the underlying data source if any IDs are not found in the cache.
//
// Returns:
//
// A map of IDs to their corresponding values and an error if one occurred.
func (c *Client[T]) GetOrFetchBatch(ctx context.Context, ids []string, keyFn KeyFn, fetchFn BatchFetchFn[T]) (map[string]T, error) {
return getFetchBatch[T, T](ctx, c, ids, keyFn, fetchFn)
}

// GetOrFetchBatch is a convenience function that performs type assertion on the result of client.GetOrFetchBatch.
// GetOrFetchBatch is a convenience function that performs type assertion on the
// result of client.GetOrFetchBatch.
//
// Parameters:
//
// ctx - The context to be used for the request.
// c - The cache client.
// ids - The list of IDs to be fetched.
// keyFn - Used to prefix each ID in order to create a unique cache key.
// fetchFn - Used to retrieve the data from the underlying data source.
//
// Returns:
//
// A map of ids to their corresponding values and an error if one occurred.
//
// Type Parameters:
//
// V - The type returned by the fetchFn. Must be assignable to T.
// T - The type stored in the cache.

func GetOrFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, keyFn KeyFn, fetchFn BatchFetchFn[V]) (map[string]V, error) {
res, err := getFetchBatch[V, T](ctx, c, ids, keyFn, fetchFn)
return unwrapBatch[V](res, err)
Expand Down
Loading
Loading