mirror of
https://github.com/gofiber/storage.git
synced 2025-09-27 04:46:08 +08:00
Compare commits
19 Commits
dependabot
...
valkey/v0.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
76c52119bf | ||
![]() |
87c2b454f8 | ||
![]() |
12ea6b4978 | ||
![]() |
cce70b9e7f | ||
![]() |
f8776d0233 | ||
![]() |
740eed9579 | ||
![]() |
baaf5c76e0 | ||
![]() |
9fa1710604 | ||
![]() |
674175ded7 | ||
![]() |
fae399ccdc | ||
![]() |
381f0c134e | ||
![]() |
9013b623ea | ||
![]() |
512c66712a | ||
![]() |
692f78abb0 | ||
![]() |
1a7044aded | ||
![]() |
88906bb82b | ||
![]() |
5d095dc16b | ||
![]() |
a18385a6ba | ||
![]() |
1da3514e71 |
50
.github/release-drafter-valkey.yml
vendored
Normal file
50
.github/release-drafter-valkey.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name-template: 'Valkey - v$RESOLVED_VERSION'
|
||||
tag-template: 'valkey/v$RESOLVED_VERSION'
|
||||
tag-prefix: valkey/v
|
||||
include-paths:
|
||||
- valkey
|
||||
categories:
|
||||
- title: '❗ Breaking Changes'
|
||||
labels:
|
||||
- '❗ BreakingChange'
|
||||
- title: '🚀 New'
|
||||
labels:
|
||||
- '✏️ Feature'
|
||||
- title: '🧹 Updates'
|
||||
labels:
|
||||
- '🧹 Updates'
|
||||
- '🤖 Dependencies'
|
||||
- title: '🐛 Fixes'
|
||||
labels:
|
||||
- '☢️ Bug'
|
||||
- title: '📚 Documentation'
|
||||
labels:
|
||||
- '📒 Documentation'
|
||||
change-template: '- $TITLE (#$NUMBER)'
|
||||
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||
exclude-contributors:
|
||||
- dependabot
|
||||
- dependabot[bot]
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
- '❗ BreakingChange'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
- '✏️ Feature'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
- '📒 Documentation'
|
||||
- '☢️ Bug'
|
||||
- '🤖 Dependencies'
|
||||
- '🧹 Updates'
|
||||
default: patch
|
||||
template: |
|
||||
$CHANGES
|
||||
|
||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...valkey/v$RESOLVED_VERSION
|
||||
|
||||
Thank you $CONTRIBUTORS for making this update possible.
|
3
.github/scripts/gen-test-certs.sh
vendored
3
.github/scripts/gen-test-certs.sh
vendored
@@ -56,7 +56,8 @@ _END_
|
||||
generate_cert server "Server-only" "-extfile ./tls/openssl.cnf -extensions server_cert"
|
||||
generate_cert client "Client-only" "-extfile ./tls/openssl.cnf -extensions client_cert"
|
||||
generate_cert redis "localhost" "-extfile ./tls/openssl.cnf -extensions server_cert"
|
||||
generate_cert valkey "localhost" "-extfile ./tls/openssl.cnf -extensions server_cert"
|
||||
|
||||
# List generated certs
|
||||
ls -la ./tls
|
||||
echo "$PWD"
|
||||
echo "$PWD"
|
||||
|
19
.github/workflows/release-drafter-valkey.yml
vendored
Normal file
19
.github/workflows/release-drafter-valkey.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Release Drafter Valkey
|
||||
on:
|
||||
push:
|
||||
# branches to consider in the event; optional, defaults to all
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- 'valkey/**'
|
||||
jobs:
|
||||
draft_release_valkey:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
config-name: release-drafter-valkey.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
63
.github/workflows/test-valkey.yml
vendored
Normal file
63
.github/workflows/test-valkey.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- 'valkey/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'valkey/**'
|
||||
- '.github/workflows/test-valkey.yml'
|
||||
name: "Tests Valkey"
|
||||
jobs:
|
||||
Tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go-version:
|
||||
- 1.23.x
|
||||
valkey:
|
||||
- '7.x'
|
||||
- '8.x'
|
||||
steps:
|
||||
- name: Fetch Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate TLS certs
|
||||
run: ./.github/scripts/gen-test-certs.sh
|
||||
|
||||
- name: Setup Valkey
|
||||
uses: shogo82148/actions-setup-redis@v1
|
||||
with:
|
||||
distribution: 'valkey'
|
||||
redis-version: ${{ matrix.valkey }}
|
||||
auto-start: 'false'
|
||||
redis-port: '6379'
|
||||
redis-tls-port: '6380'
|
||||
|
||||
- name: Run Valkey
|
||||
run: |
|
||||
valkey-server --tls-port 6380 --port 6379 \
|
||||
--tls-cert-file /home/runner/work/storage/storage/tls/valkey.crt \
|
||||
--tls-key-file /home/runner/work/storage/storage/tls/valkey.key \
|
||||
--tls-ca-cert-file /home/runner/work/storage/storage/tls/ca.crt &
|
||||
|
||||
- name: Setup Valkey Cluster
|
||||
uses: vishnudxb/redis-cluster@1.0.9
|
||||
with:
|
||||
master1-port: 7000
|
||||
master2-port: 7001
|
||||
master3-port: 7002
|
||||
slave1-port: 7003
|
||||
slave2-port: 7004
|
||||
slave3-port: 7005
|
||||
sleep-duration: 10
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '${{ matrix.go-version }}'
|
||||
|
||||
- name: Run Test
|
||||
run: cd ./valkey && go test ./... -v -race
|
@@ -75,3 +75,4 @@ type Storage interface {
|
||||
- [ScyllaDB](./scylladb/README.md) <a href="https://github.com/gofiber/storage/actions?query=workflow%3A%22Tests+scylladb%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-scylladb.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
|
||||
- [SQLite3](./sqlite3/README.md) <a href="https://github.com/gofiber/storage/actions?query=workflow%3A%22Tests+Sqlite3%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-sqlite3.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
|
||||
- [ClickHouse](./clickhouse/README.md) <a href="https://github.com/gofiber/storage/actions?query=workflow%3A%22Tests+Clickhouse%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-clickhouse.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
|
||||
- [Valkey](./valkey/README.md) <a href="https://github.com/gofiber/storage/actions?query=workflow%3A%22Tests+valkey%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-valkey.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" /> </a>
|
||||
|
222
valkey/README.md
Normal file
222
valkey/README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
---
|
||||
id: valkey
|
||||
title: Valkey
|
||||
---
|
||||
|
||||

|
||||
[](https://gofiber.io/discord)
|
||||

|
||||

|
||||

|
||||
|
||||
A fast Valkey Storage that does auto pipelining and supports client side caching. Implementation is based on [valkey-io/valkey](https://github.com/valkey-io/valkey-go).
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- [Signatures](#signatures)
|
||||
- [Installation](#installation)
|
||||
- [Examples](#examples)
|
||||
- [Config](#config)
|
||||
- [Default Config](#default-config)
|
||||
|
||||
### Signatures
|
||||
|
||||
```go
|
||||
func New(config ...Config) Storage
|
||||
func (s *Storage) Get(key string) ([]byte, error)
|
||||
func (s *Storage) Set(key string, val []byte, exp time.Duration) error
|
||||
func (s *Storage) Delete(key string) error
|
||||
func (s *Storage) Reset() error
|
||||
func (s *Storage) Close() error
|
||||
func (s *Storage) Conn() valkey.Client
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
The valkey driver is tested on the latest two [Go version](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet:
|
||||
|
||||
```bash
|
||||
go mod init github.com/<user>/<repo>
|
||||
```
|
||||
|
||||
And then install the valkey implementation:
|
||||
|
||||
```bash
|
||||
go get github.com/gofiber/storage/valkey
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Import the storage package.
|
||||
|
||||
```go
|
||||
import "github.com/gofiber/storage/valkey"
|
||||
```
|
||||
|
||||
You can use the one of the following options to create a Valkey Storage:
|
||||
|
||||
```go
|
||||
// Initialize default config (localhost:6379)
|
||||
store := valkey.New()
|
||||
|
||||
// Initialize custom config
|
||||
store := valkey.New(valkey.Config{
|
||||
InitAddress: []string{"localhost:6380"},
|
||||
Username: "",
|
||||
Password: "",
|
||||
Database: 0,
|
||||
Reset: false,
|
||||
TLSConfig: nil,
|
||||
})
|
||||
|
||||
// Initialize using Redis-style URL
|
||||
store := valkey.New(valkey.Config{
|
||||
URL: "redis://localhost:6379",
|
||||
})
|
||||
|
||||
// Initialize Valkey Cluster Client
|
||||
store := valkey.New(valkey.Config{
|
||||
InitAddress: []string{":6379", ":6380"},
|
||||
})
|
||||
|
||||
// Create a client with support for TLS
|
||||
cer, err := tls.LoadX509KeyPair("./client.crt", "./client.key")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{cer},
|
||||
}
|
||||
store = valkey.New(valkey.Config{
|
||||
InitAddress: []string{"localhost:6380"},
|
||||
Username: "<user>",
|
||||
Password: "<password>",
|
||||
SelectDB: 0,
|
||||
TLSConfig: tlsCfg,
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
// Server username
|
||||
//
|
||||
// Optional. Default is ""
|
||||
Username string
|
||||
|
||||
// Server password
|
||||
//
|
||||
// Optional. Default is ""
|
||||
Password string
|
||||
|
||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
||||
//
|
||||
// Optional. Default is ""
|
||||
ClientName string
|
||||
|
||||
// URL standard format Redis-style URL. If this is set all other config options, InitAddress, Username, Password, ClientName, and SelectDB have no effect.
|
||||
//
|
||||
// Example: redis://<user>:<pass>@localhost:6379/<db>
|
||||
// Optional. Default is ""
|
||||
URL string
|
||||
|
||||
// SelectDB to be selected after connecting to the server.
|
||||
//
|
||||
// Optional. Default is 0
|
||||
SelectDB int
|
||||
|
||||
// Either a single address or a seed list of host:port addresses, this enables FailoverClient and ClusterClient
|
||||
//
|
||||
// Optional. Default is []string{"127.0.0.1:6379"}
|
||||
InitAddress []string
|
||||
|
||||
// TLS Config to use. When set TLS will be negotiated.
|
||||
//
|
||||
// Optional. Default is nil
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// CacheSizeEachConn is valkey client side cache size that bind to each TCP connection to a single valkey instance.
|
||||
//
|
||||
// Optional. The default is DefaultCacheBytes: 128 * (1 << 20)
|
||||
CacheSizeEachConn int
|
||||
|
||||
// RingScaleEachConn sets the size of the ring buffer in each connection to (2 ^ RingScaleEachConn).
|
||||
//
|
||||
// Optional. The default is RingScaleEachConn, which results into having a ring of size 2^10 for each connection.
|
||||
RingScaleEachConn int
|
||||
|
||||
// ReadBufferEachConn is the size of the bufio.NewReaderSize for each connection, default to DefaultReadBuffer (0.5 MiB).
|
||||
//
|
||||
// Optional. The default is DefaultReadBuffer: 1 << 19
|
||||
ReadBufferEachConn int
|
||||
|
||||
// WriteBufferEachConn is the size of the bufio.NewWriterSize for each connection, default to DefaultWriteBuffer (0.5 MiB).
|
||||
//
|
||||
// Optional. The default is DefaultWriteBuffer: 1 << 19
|
||||
WriteBufferEachConn int
|
||||
|
||||
// BlockingPoolSize is the size of the connection pool shared by blocking commands (ex BLPOP, XREAD with BLOCK).
|
||||
//
|
||||
// Optional. The default is DefaultPoolSize: 1000
|
||||
BlockingPoolSize int
|
||||
|
||||
// PipelineMultiplex determines how many tcp connections used to pipeline commands to one valkey instance.
|
||||
//
|
||||
// Optional. The default for single and sentinel clients is 2, which means 4 connections (2^2).
|
||||
PipelineMultiplex int
|
||||
|
||||
// DisableRetry disables retrying read-only commands under network errors
|
||||
//
|
||||
// Optional. The default is False
|
||||
DisableRetry bool
|
||||
|
||||
// DisableCache falls back Client.DoCache/Client.DoMultiCache to Client.Do/Client.DoMulti
|
||||
//
|
||||
// Optional. The default is false
|
||||
DisableCache bool
|
||||
|
||||
// AlwaysPipelining makes valkey.Client always pipeline valkey commands even if they are not issued concurrently.
|
||||
//
|
||||
// Optional. The default is true
|
||||
AlwaysPipelining bool
|
||||
|
||||
// Reset clears any existing keys in existing Collection
|
||||
//
|
||||
// Optional. Default is false
|
||||
Reset bool
|
||||
|
||||
// CacheTTL TTL
|
||||
//
|
||||
// Optional. Default is time.Minute
|
||||
CacheTTL time.Duration
|
||||
}
|
||||
```
|
||||
|
||||
### Default Config
|
||||
|
||||
```go
|
||||
var ConfigDefault = Config{
|
||||
Username: "",
|
||||
Password: "",
|
||||
ClientName: "",
|
||||
SelectDB: 0,
|
||||
InitAddress: []string{"127.0.0.1:6379"},
|
||||
TLSConfig: nil,
|
||||
CacheSizeEachConn: valkey.DefaultCacheBytes,
|
||||
RingScaleEachConn: valkey.DefaultRingScale,
|
||||
ReadBufferEachConn: valkey.DefaultReadBuffer,
|
||||
WriteBufferEachConn: valkey.DefaultWriteBuffer,
|
||||
BlockingPoolSize: valkey.DefaultPoolSize,
|
||||
PipelineMultiplex: 2,
|
||||
DisableRetry: false,
|
||||
DisableCache: false,
|
||||
AlwaysPipelining: true,
|
||||
Reset: false,
|
||||
CacheTTL: time.Minute,
|
||||
}
|
||||
```
|
198
valkey/config.go
Normal file
198
valkey/config.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/valkey-io/valkey-go"
|
||||
)
|
||||
|
||||
// Config defines the config for storage.
|
||||
type Config struct {
|
||||
// Server username
|
||||
//
|
||||
// Optional. Default is ""
|
||||
Username string
|
||||
|
||||
// Server password
|
||||
//
|
||||
// Optional. Default is ""
|
||||
Password string
|
||||
|
||||
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
|
||||
//
|
||||
// Optional. Default is ""
|
||||
ClientName string
|
||||
|
||||
// URL standard format Redis URL. If this is set all other config options, InitAddress, Username, Password, ClientName, and SelectDB have no effect.
|
||||
//
|
||||
// Example: redis://<user>:<pass>@localhost:6379/<db>
|
||||
// Optional. Default is ""
|
||||
URL string
|
||||
|
||||
// SelectDB to be selected after connecting to the server.
|
||||
//
|
||||
// Optional. Default is 0
|
||||
SelectDB int
|
||||
|
||||
// Either a single address or a seed list of host:port addresses, this enables FailoverClient and ClusterClient
|
||||
//
|
||||
// Optional. Default is []string{"127.0.0.1:6379"}
|
||||
InitAddress []string
|
||||
|
||||
// TLS Config to use. When set TLS will be negotiated.
|
||||
//
|
||||
// Optional. Default is nil
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// CacheSizeEachConn is redis client side cache size that bind to each TCP connection to a single redis instance.
|
||||
//
|
||||
// Optional. The default is DefaultCacheBytes: 128 * (1 << 20)
|
||||
CacheSizeEachConn int
|
||||
|
||||
// RingScaleEachConn sets the size of the ring buffer in each connection to (2 ^ RingScaleEachConn).
|
||||
//
|
||||
// Optional. The default is RingScaleEachConn, which results into having a ring of size 2^10 for each connection.
|
||||
RingScaleEachConn int
|
||||
|
||||
// ReadBufferEachConn is the size of the bufio.NewReaderSize for each connection, default to DefaultReadBuffer (0.5 MiB).
|
||||
//
|
||||
// Optional. The default is DefaultReadBuffer: 1 << 19
|
||||
ReadBufferEachConn int
|
||||
|
||||
// WriteBufferEachConn is the size of the bufio.NewWriterSize for each connection, default to DefaultWriteBuffer (0.5 MiB).
|
||||
//
|
||||
// Optional. The default is DefaultWriteBuffer: 1 << 19
|
||||
WriteBufferEachConn int
|
||||
|
||||
// BlockingPoolSize is the size of the connection pool shared by blocking commands (ex BLPOP, XREAD with BLOCK).
|
||||
//
|
||||
// Optional. The default is DefaultPoolSize: 1000
|
||||
BlockingPoolSize int
|
||||
|
||||
// PipelineMultiplex determines how many tcp connections used to pipeline commands to one redis instance.
|
||||
//
|
||||
// Optional. The default for single and sentinel clients is 2, which means 4 connections (2^2).
|
||||
PipelineMultiplex int
|
||||
|
||||
// DisableRetry disables retrying read-only commands under network errors
|
||||
//
|
||||
// Optional. The default is False
|
||||
DisableRetry bool
|
||||
|
||||
// DisableCache falls back Client.DoCache/Client.DoMultiCache to Client.Do/Client.DoMulti
|
||||
//
|
||||
// Optional. The default is false
|
||||
DisableCache bool
|
||||
|
||||
// AlwaysPipelining makes valkey.Client always pipeline redis commands even if they are not issued concurrently.
|
||||
//
|
||||
// Optional. The default is true
|
||||
AlwaysPipelining bool
|
||||
|
||||
// Reset clears any existing keys in existing Collection
|
||||
//
|
||||
// Optional. Default is false
|
||||
Reset bool
|
||||
|
||||
// CacheTTL TTL
|
||||
//
|
||||
// Optional. Default is time.Minute
|
||||
CacheTTL time.Duration
|
||||
}
|
||||
|
||||
// ConfigDefault is the default config
|
||||
var ConfigDefault = Config{
|
||||
Username: "",
|
||||
Password: "",
|
||||
ClientName: "",
|
||||
URL: "",
|
||||
SelectDB: 0,
|
||||
InitAddress: []string{"127.0.0.1:6379"},
|
||||
TLSConfig: nil,
|
||||
CacheSizeEachConn: valkey.DefaultCacheBytes,
|
||||
RingScaleEachConn: valkey.DefaultRingScale,
|
||||
ReadBufferEachConn: valkey.DefaultReadBuffer,
|
||||
WriteBufferEachConn: valkey.DefaultWriteBuffer,
|
||||
BlockingPoolSize: valkey.DefaultPoolSize,
|
||||
PipelineMultiplex: 2,
|
||||
DisableRetry: false,
|
||||
DisableCache: false,
|
||||
AlwaysPipelining: true,
|
||||
Reset: false,
|
||||
CacheTTL: time.Minute,
|
||||
}
|
||||
|
||||
// Helper function to set default values
|
||||
func configDefault(config ...Config) Config {
|
||||
// Return default config if nothing provided
|
||||
if len(config) < 1 {
|
||||
return ConfigDefault
|
||||
}
|
||||
|
||||
// Start with the default configuration
|
||||
cfg := ConfigDefault
|
||||
|
||||
// Override default config with values from provided config
|
||||
userConfig := config[0]
|
||||
|
||||
if userConfig.Username != "" {
|
||||
cfg.Username = userConfig.Username
|
||||
}
|
||||
if userConfig.Password != "" {
|
||||
cfg.Password = userConfig.Password
|
||||
}
|
||||
if userConfig.ClientName != "" {
|
||||
cfg.ClientName = userConfig.ClientName
|
||||
}
|
||||
if userConfig.URL != "" {
|
||||
cfg.URL = userConfig.URL
|
||||
}
|
||||
if userConfig.SelectDB != 0 {
|
||||
cfg.SelectDB = userConfig.SelectDB
|
||||
}
|
||||
if len(userConfig.InitAddress) > 0 {
|
||||
cfg.InitAddress = userConfig.InitAddress
|
||||
}
|
||||
if userConfig.TLSConfig != nil {
|
||||
cfg.TLSConfig = userConfig.TLSConfig
|
||||
}
|
||||
if userConfig.CacheSizeEachConn != 0 {
|
||||
cfg.CacheSizeEachConn = userConfig.CacheSizeEachConn
|
||||
}
|
||||
if userConfig.RingScaleEachConn != 0 {
|
||||
cfg.RingScaleEachConn = userConfig.RingScaleEachConn
|
||||
}
|
||||
if userConfig.ReadBufferEachConn != 0 {
|
||||
cfg.ReadBufferEachConn = userConfig.ReadBufferEachConn
|
||||
}
|
||||
if userConfig.WriteBufferEachConn != 0 {
|
||||
cfg.WriteBufferEachConn = userConfig.WriteBufferEachConn
|
||||
}
|
||||
if userConfig.BlockingPoolSize != 0 {
|
||||
cfg.BlockingPoolSize = userConfig.BlockingPoolSize
|
||||
}
|
||||
if userConfig.PipelineMultiplex != 0 {
|
||||
cfg.PipelineMultiplex = userConfig.PipelineMultiplex
|
||||
}
|
||||
if userConfig.CacheTTL != time.Second {
|
||||
cfg.CacheTTL = userConfig.CacheTTL
|
||||
}
|
||||
if userConfig.DisableRetry {
|
||||
cfg.DisableRetry = true
|
||||
}
|
||||
|
||||
if userConfig.DisableCache {
|
||||
cfg.DisableCache = true
|
||||
}
|
||||
|
||||
if userConfig.AlwaysPipelining {
|
||||
cfg.AlwaysPipelining = true
|
||||
}
|
||||
|
||||
if userConfig.Reset {
|
||||
cfg.Reset = true
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
18
valkey/go.mod
Normal file
18
valkey/go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module github.com/gofiber/storage/valkey
|
||||
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/valkey-io/valkey-go v1.0.52
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
33
valkey/go.sum
Normal file
33
valkey/go.sum
Normal file
@@ -0,0 +1,33 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/valkey-io/valkey-go v1.0.52 h1:ojrR736satGucqpllYzal8fUrNNROc11V10zokAyIYg=
|
||||
github.com/valkey-io/valkey-go v1.0.52/go.mod h1:BXlVAPIL9rFQinSFM+N32JfWzfCaUAqBpZkc4vPY6fM=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
135
valkey/valkey.go
Normal file
135
valkey/valkey.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/utils/v2"
|
||||
"github.com/valkey-io/valkey-go"
|
||||
)
|
||||
|
||||
var cacheTTL = time.Second
|
||||
|
||||
// Storage interface that is implemented by storage providers
|
||||
type Storage struct {
|
||||
db valkey.Client
|
||||
}
|
||||
|
||||
// New creates a new valkey storage
|
||||
func New(config ...Config) *Storage {
|
||||
// Set default config
|
||||
cfg := configDefault(config...)
|
||||
|
||||
// Create new valkey client
|
||||
var db valkey.Client
|
||||
cacheTTL = cfg.CacheTTL
|
||||
|
||||
// Parse the URL and update config values accordingly
|
||||
if cfg.URL != "" {
|
||||
// This will panic if parsing URL fails
|
||||
options := valkey.MustParseURL(cfg.URL)
|
||||
|
||||
// Update the config values with the parsed URL values
|
||||
cfg.InitAddress = options.InitAddress
|
||||
cfg.Username = options.Username
|
||||
cfg.Password = options.Password
|
||||
cfg.SelectDB = options.SelectDB
|
||||
|
||||
// Update ClientName if returned
|
||||
if cfg.ClientName == "" && options.ClientName != "" {
|
||||
cfg.ClientName = options.ClientName
|
||||
}
|
||||
|
||||
// Update TLSConfig if returned
|
||||
if cfg.TLSConfig == nil && options.TLSConfig != nil {
|
||||
cfg.TLSConfig = options.TLSConfig
|
||||
}
|
||||
}
|
||||
|
||||
// Update config values accordingly and start new Client
|
||||
db, err := valkey.NewClient(valkey.ClientOption{
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
ClientName: cfg.ClientName,
|
||||
SelectDB: cfg.SelectDB,
|
||||
InitAddress: cfg.InitAddress,
|
||||
TLSConfig: cfg.TLSConfig,
|
||||
CacheSizeEachConn: cfg.CacheSizeEachConn,
|
||||
RingScaleEachConn: cfg.RingScaleEachConn,
|
||||
ReadBufferEachConn: cfg.ReadBufferEachConn,
|
||||
WriteBufferEachConn: cfg.WriteBufferEachConn,
|
||||
BlockingPoolSize: cfg.BlockingPoolSize,
|
||||
PipelineMultiplex: cfg.PipelineMultiplex,
|
||||
DisableRetry: cfg.DisableRetry,
|
||||
DisableCache: cfg.DisableCache,
|
||||
AlwaysPipelining: cfg.AlwaysPipelining,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Test connection
|
||||
if err := db.Do(context.Background(), db.B().Ping().Build()).Error(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Empty collection if Clear is true
|
||||
if cfg.Reset {
|
||||
if err := db.Do(context.Background(), db.B().Flushdb().Build()).Error(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create new store
|
||||
return &Storage{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Get value by key
|
||||
func (s *Storage) Get(key string) ([]byte, error) {
|
||||
if len(key) <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
val, err := s.db.DoCache(context.Background(), s.db.B().Get().Key(key).Cache(), cacheTTL).AsBytes()
|
||||
if err != nil && valkey.IsValkeyNil(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return val, err
|
||||
}
|
||||
|
||||
// Set key with value
|
||||
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
|
||||
if len(key) <= 0 || len(val) <= 0 {
|
||||
return nil
|
||||
}
|
||||
if exp > 0 {
|
||||
return s.db.Do(context.Background(), s.db.B().Set().Key(key).Value(utils.ToString(val)).Ex(exp).Build()).Error()
|
||||
} else {
|
||||
return s.db.Do(context.Background(), s.db.B().Set().Key(key).Value(utils.ToString(val)).Build()).Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete key by key
|
||||
func (s *Storage) Delete(key string) error {
|
||||
if len(key) <= 0 {
|
||||
return nil
|
||||
}
|
||||
return s.db.Do(context.Background(), s.db.B().Del().Key(key).Build()).Error()
|
||||
}
|
||||
|
||||
// Reset all keys
|
||||
func (s *Storage) Reset() error {
|
||||
return s.db.Do(context.Background(), s.db.B().Flushdb().Build()).Error()
|
||||
}
|
||||
|
||||
// Close the database
|
||||
func (s *Storage) Close() error {
|
||||
s.db.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return database client
|
||||
func (s *Storage) Conn() valkey.Client {
|
||||
return s.db
|
||||
}
|
277
valkey/valkey_test.go
Normal file
277
valkey/valkey_test.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testStore = New(Config{
|
||||
Reset: true,
|
||||
})
|
||||
|
||||
func Test_Valkey_Set(t *testing.T) {
|
||||
var (
|
||||
key = "john"
|
||||
val = []byte("doe")
|
||||
)
|
||||
|
||||
err := testStore.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_Valkey_Set_Override(t *testing.T) {
|
||||
var (
|
||||
key = "john"
|
||||
val = []byte("doe")
|
||||
)
|
||||
|
||||
err := testStore.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = testStore.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_Valkey_Get(t *testing.T) {
|
||||
var (
|
||||
key = "john"
|
||||
val = []byte("doe")
|
||||
)
|
||||
|
||||
err := testStore.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := testStore.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val, result)
|
||||
}
|
||||
|
||||
func Test_Valkey_Set_Expiration(t *testing.T) {
|
||||
var (
|
||||
key = "john"
|
||||
val = []byte("doe")
|
||||
exp = 1 * time.Second
|
||||
)
|
||||
|
||||
err := testStore.Set(key, val, exp)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func Test_Valkey_Get_Expired(t *testing.T) {
|
||||
key := "john"
|
||||
|
||||
result, err := testStore.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, len(result))
|
||||
}
|
||||
|
||||
func Test_Valkey_Get_NotExist(t *testing.T) {
|
||||
result, err := testStore.Get("notexist")
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, len(result))
|
||||
}
|
||||
|
||||
func Test_Valkey_Delete(t *testing.T) {
|
||||
var (
|
||||
key = "john"
|
||||
val = []byte("doe")
|
||||
)
|
||||
|
||||
err := testStore.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = testStore.Delete(key)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := testStore.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, len(result))
|
||||
}
|
||||
|
||||
func Test_Valkey_Reset(t *testing.T) {
|
||||
val := []byte("doe")
|
||||
|
||||
err := testStore.Set("john1", val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = testStore.Set("john2", val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = testStore.Reset()
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := testStore.Get("john1")
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, len(result))
|
||||
|
||||
result, err = testStore.Get("john2")
|
||||
require.NoError(t, err)
|
||||
require.Zero(t, len(result))
|
||||
}
|
||||
|
||||
func Test_Valkey_Close(t *testing.T) {
|
||||
require.Nil(t, testStore.Close())
|
||||
}
|
||||
|
||||
func Test_Valkey_Conn(t *testing.T) {
|
||||
require.True(t, testStore.Conn() != nil)
|
||||
}
|
||||
|
||||
func Test_Valkey_WithTLS(t *testing.T) {
|
||||
cer, err := tls.LoadX509KeyPair("/home/runner/work/storage/storage/tls/client.crt", "/home/runner/work/storage/storage/tls/client.key")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
InsecureSkipVerify: true,
|
||||
Certificates: []tls.Certificate{cer},
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
|
||||
storeTLS := New(Config{
|
||||
InitAddress: []string{"localhost:6380"},
|
||||
TLSConfig: tlsCfg,
|
||||
})
|
||||
|
||||
var (
|
||||
key = "clark"
|
||||
val = []byte("kent")
|
||||
)
|
||||
|
||||
err = storeTLS.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := storeTLS.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val, result)
|
||||
|
||||
err = storeTLS.Delete(key)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, storeTLS.Close())
|
||||
}
|
||||
|
||||
func Test_Valkey_With_HostPort(t *testing.T) {
|
||||
store := New(Config{
|
||||
InitAddress: []string{"localhost:6379"},
|
||||
})
|
||||
|
||||
var (
|
||||
key = "bruce"
|
||||
val = []byte("wayne")
|
||||
)
|
||||
|
||||
err := store.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val, result)
|
||||
|
||||
err = store.Delete(key)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, store.Close())
|
||||
}
|
||||
|
||||
func Test_Valkey_With_URL(t *testing.T) {
|
||||
store := New(Config{
|
||||
URL: "redis://localhost:6379",
|
||||
})
|
||||
|
||||
var (
|
||||
key = "bruce"
|
||||
val = []byte("wayne")
|
||||
)
|
||||
|
||||
err := store.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val, result)
|
||||
|
||||
err = store.Delete(key)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, store.Close())
|
||||
}
|
||||
|
||||
func Test_Valkey_Cluster(t *testing.T) {
|
||||
store := New(Config{
|
||||
InitAddress: []string{
|
||||
"localhost:7000",
|
||||
"localhost:7001",
|
||||
"localhost:7002",
|
||||
"localhost:7003",
|
||||
"localhost:7004",
|
||||
"localhost:7005",
|
||||
},
|
||||
})
|
||||
|
||||
var (
|
||||
key = "bruce"
|
||||
val = []byte("wayne")
|
||||
)
|
||||
|
||||
err := store.Set(key, val, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := store.Get(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val, result)
|
||||
|
||||
err = store.Delete(key)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, store.Close())
|
||||
}
|
||||
|
||||
func Benchmark_Valkey_Set(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
err = testStore.Set("john", []byte("doe"), 0)
|
||||
}
|
||||
|
||||
require.NoError(b, err)
|
||||
}
|
||||
|
||||
func Benchmark_Valkey_Get(b *testing.B) {
|
||||
err := testStore.Set("john", []byte("doe"), 0)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = testStore.Get("john")
|
||||
}
|
||||
|
||||
require.NoError(b, err)
|
||||
}
|
||||
|
||||
func Benchmark_Valkey_SetAndDelete(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = testStore.Set("john", []byte("doe"), 0)
|
||||
err = testStore.Delete("john")
|
||||
}
|
||||
|
||||
require.NoError(b, err)
|
||||
}
|
Reference in New Issue
Block a user