The kv
vmod allows key-value storage with optional TTLs and optionally
sharing of the stored information between servers.
Implementing session tracking, forcing a user to not do any requests for 5 minutes before being allowed to change IP addresses globally in the store while tracking the total number of operations done during the session. This may take up to 10s to asynchronously detect that a user uses the same session with different IPs from different instances if requests happens at the same time.
vcl 4.1;
import kv;
sub vcl_init {
new store = kv.init(grace = 10s);
store.add_redis_server("1.2.3.4:6379");
}
sub vcl_recv {
set req.http.client-ip = client.ip;
store.set_key_group("sess:" + req.http.X-Session-Id);
if (store.get_real("created_at", -1.0) < 0.0) {
store.set_real("created_at", std.time2real(now, -1.0));
store.set("ip", req.http.client-ip, ttl = 5m);
}
if (req.http.client-ip != store.get("ip", "")) {
store.incr_int("rejected", 1, ttl = 5m);
return (synth(403, "Go away for now"));
}
# this refreshes the TTL of the entire session
store.incr_int("count", 1, ttl = 5m);
# this simply counts requests globally
store.incr_int("count", 1, key_group = "");
}
In this example, if X-Session-Id
is equal to 1234
then the following is
observable in the Redis server CLI after some requests:
redis> HGETALL sess:1234
1) "createdat"
2) "1722603018"
3) "ip"
4) "4.3.2.1"
5) "rejected"
6) "1"
7) "count"
8) "2"
redis> TTL sess:1234
(integer) 283
redis> GET count
"2"
Using a prefix for key groups that cannot conflict with regular keys is highly recommended (see below).
OBJECT init(INT buckets = 293, ENUM {VCL, STATIC} scope = VCL, DURATION grace = 60, DURATION keep = 10, INT max_retries = 2, DURATION idle_delay = 5, ENUM {SYNC, TRY_SYNC, ASYNC} mode_read = TRY_SYNC, ENUM {SYNC, TRY_SYNC, ASYNC} mode_write = ASYNC, ENUM {SYNC, TRY_SYNC, ASYNC} mode_del = ASYNC)
Creates a key-value store able to store in-memory data, the number of buckets can be tuned to avoid contention on data access.
By default, the store is created with VCL
scope making it specific to the
VCL it was created in. This means that it is not shared between VCLs and its
lifetime is bound to the lifetime of the VCL it was created in.
A STATIC
scope store is shared to all VCLs referencing it with the same
name, mode_read
, mode_write
and mode_del
and the store is only
destroyed when no loaded VCL references it anymore. Note that if different VCLs
have different configurations for a STATIC
store, the last initializer will
apply its values with the exception of buckets
which cannot be changed. In
all cases, access is shared across request transactions.
When the data is not shared between multiple instances the other parameters are ignored.
When the data is shared between multiple instances, the in-memory state is
considered as a cache of what is stored in a central hub. As synchronous
access to the central hub may be costly, each type of operation has
an access pattern governed by the mode_read
, mode_write
and
mode_del
. If the mode is set to SYNC
then VCL operation blocks further
processing until the corresponding commands against the central hub have either
succeeded or the max number of tries are exhausted.
If the mode is set to TRY_SYNC
then VCL operation blocks further processing
until the corresponding commands against the central hub have either succeeded
or at least one try was attempted (other retries continue asynchronously).
If the mode is set to ASYNC
then VCL operation does not block and all
commands against the central hub occur asynchronously.
The grace
value is used to indicate how long the in-memory state is
considered fresh for without requiring an update from central hub. The keep
indicates how long the in-memory state should require an update from the central
hub in an asynchonous way regardless of the mode_read
. Any read past the
grace
plus keep
will rely on mode_read
as if it was a first access.
A local set will position the grace
and keep
as if the value was
retrieved from the central hub.
Any failure to retrieve or propagate data to the central hub is retried up to
max_retries
times. A negative value will retry until success. Once the
maximum retries are exhausted the operations are considered in success.
While synchronous operations are performed as fast as possible, access to the
central hub may benefit from some delay before being executed, the value of
idle_delay
indicates how long can be spent without taking action when
no synchronous operations are waiting to process asynchronous operations.
Arguments:
buckets
accepts type INT with a default value of 293
optional
grace
accepts type DURATION with a default value of 60
optional
keep
accepts type DURATION with a default value of 10
optional
max_retries
accepts type INT with a default value of 2
optional
idle_delay
accepts type DURATION with a default value of 5
optional
scope
is an ENUM that accepts values of VCL
, and STATIC
with a default value of VCL
optional
mode_read
is an ENUM that accepts values of SYNC
, TRY_SYNC
, and ASYNC
with a default value of TRY_SYNC
optional
mode_write
is an ENUM that accepts values of SYNC
, TRY_SYNC
, and ASYNC
with a default value of ASYNC
optional
mode_del
is an ENUM that accepts values of SYNC
, TRY_SYNC
, and ASYNC
with a default value of ASYNC
optional
Type: Object
Returns: Object.
VOID .set_key_group(STRING key_group)
This sets the key_group
for the request being evaluated. Note that setting a
key_group
in the client request will not affect backend requests nor would
setting a key_group
in the backend request change the key_group
of the
client request.
The empty string can be used to clear the currently set key_group
.
Having a key_group
changes the semantics of further data access:
Without a key_group
, the key
provided in each access call is the sole
identifier of the data inside both the in-memory storage and the Redis server.
The TTL is specific to each key
.
With a key_group
, the key
is a field within the key_group
, the
key_group
is the effective key in the Redis server storing a Redis hash. In
such a case the TTL is specific to the key_group
not to the individual keys.
Arguments:
key_group
accepts type STRINGType: Method
Returns: None
Restricted to: client
, backend
STRING .get(STRING key, STRING fallback = 0, [STRING key_group])
This method returns the value associated with key
in the store, or
fallback
if key
was not found.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
Arguments:
key
accepts type STRING
fallback
accepts type STRING with a default value of 0
optional
key_group
accepts type STRING
Type: Method
Returns: String
VOID .set(STRING key, STRING value, [DURATION ttl], [STRING key_group])
This method sets the value associated with key
in the store with
an optional TTL.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
The TTL is key_group
wide if one is set and key
specific if no
key_group
was specified. Not passing a ttl
value or a negative ttl
value will not apply an expiration time persisting the entry.
Arguments:
key
accepts type STRING
value
accepts type STRING
ttl
accepts type DURATION
key_group
accepts type STRING
Type: Method
Returns: None
INT .get_int(STRING key, INT fallback = 0, [STRING key_group])
This method returns the value associated with key
in the store, or
fallback
if key
was not found or if the key is not of INT
type.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
Arguments:
key
accepts type STRING
fallback
accepts type INT with a default value of 0
optional
key_group
accepts type STRING
Type: Method
Returns: Int
VOID .set_int(STRING key, INT value, [DURATION ttl], [STRING key_group])
This method sets the value associated with key
in the store with
an optional TTL.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
The TTL is key_group
wide if one is set and key
specific if no
key_group
was specified. Not passing a ttl
value or a negative ttl
value will not apply an expiration time persisting the entry.
Arguments:
key
accepts type STRING
value
accepts type INT
ttl
accepts type DURATION
key_group
accepts type STRING
Type: Method
Returns: None
INT .incr_int(STRING key, INT by, [DURATION ttl], [STRING key_group])
This method increments the value associated with key
in the store with
an optional TTL.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
The TTL is key_group
wide if one is set and key
specific if no
key_group
was specified. Not passing a ttl
value or a negative ttl
value will not apply an expiration time persisting the entry.
Arguments:
key
accepts type STRING
by
accepts type INT
ttl
accepts type DURATION
key_group
accepts type STRING
Type: Method
Returns: Int
REAL .get_real(STRING key, REAL fallback = 0, [STRING key_group])
This method returns the value associated with key
in the store, or
fallback
if key
was not found or if the key is not of REAL
type.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
Arguments:
key
accepts type STRING
fallback
accepts type REAL with a default value of 0
optional
key_group
accepts type STRING
Type: Method
Returns: Real
VOID .set_real(STRING key, REAL value, [DURATION ttl], [STRING key_group])
This method sets the value associated with key
in the store with
an optional TTL.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
The TTL is key_group
wide if one is set and key
specific if no
key_group
was specified. Not passing a ttl
value or a negative ttl
value will not apply an expiration time persisting the entry.
Arguments:
key
accepts type STRING
value
accepts type REAL
ttl
accepts type DURATION
key_group
accepts type STRING
Type: Method
Returns: None
VOID .delete(STRING key, [STRING key_group])
This method deletes any value associated with key
in the store.
If key_group
is specified, it takes precedence over a key_group
set with
.set_key_group()
. The empty string being an invalid key_group
, using
this as an argument will simply ignore any key_group
set using
set_key_group()
.
Arguments:
key
accepts type STRING
key_group
accepts type STRING
Type: Method
Returns: None
VOID .add_redis_server(STRING connection_string, DURATION connect_timeout = 5, DURATION command_timeout = 10, DURATION max_reconnect_timeout = 10, INT select_db = 0, [STRING user], [STRING password])
Add a Redis server to synchronize from and to as a central hub. Only one Redis server is used at a time, adding multiple Redis servers is allowed as a fallback in case of failure communicating with one.
A Redis server is identified in connection_string
in the form of
ip:port
, dns_name:port
or a path to a UNIX socket.
The connect_timeout
value indicates how long to wait for a successful
connection to the Redis server. A negative value waits forever.
The command_timeout
value indicates how long before a command should be
considered as failed. A negative value waits forever.
The max_reconnect_timeout
value indicates the maximum time to wait before
reconnecting to this server. The first retry is always as fast as possible
followed with an exponential back-off up to this value as a maximum. A value
of 0 always retries as fast as possible and a negative value is not valid.
The value of select_db
is used to select a Redis database to work on using
the Redis SELECT
command.
The user
and password
values are used for authentication against the
Redis server using the semantics of the Redis AUTH
command. To authenticate
against a Redis global requirepass
directive the user
field must not be
set.
All client connections to the Redis server are named using the CLIENT SETNAME
command in the form of vmod_kv:{name}
, where {name}
is substituted
with the name of the store.
key_group
A key
without a key_group
is a normal Redis key.
For example the following VCL:
vcl 4.1;
import kv;
sub vcl_init {
new store = kv.init();
store.add_redis_server("1.2.3.4:6379");
}
sub vcl_recv {
store.set("mykey", "mystr", ttl = 100);
}
will result in the following in Redis:
redis> GET mykey
"mystr"
redis> TTL mykey
(integer) 98
key_group
A key group is materialized by a single key in the Redis server of type
hash
, each sub-key within it being a field of the hash
.
Note that the TTL value is always applied to the entire key_group
which
means that successive updates to individual keys within a key_group
can be
used to prolong the entire key_group
.
The following VCL:
vcl 4.1;
import kv;
sub vcl_init {
new store = kv.store();
store.add_redis_server("1.2.3.4:6379");
}
sub vcl_recv {
store.set_key_group("mygroup");
store.set("mykey1", "mystr1");
store.set_int("mykey2", 123);
}
will result in the following in Redis:
redis> HGETALL mygroup
1) "mykey1"
2) "mystr1"
3) "mykey2"
4) "123"
Type conflicts may happen in which case the system may issue deletes during retries to resolve the issue. Adequately making key names unique from each other in VCL negates this risk.
Type conflicts may happen for example when a key_group
is accessed as if
it was a key
or if an increment is performed on a key not containing an
integer.
Arguments:
connection_string
accepts type STRING
connect_timeout
accepts type DURATION with a default value of 5
optional
command_timeout
accepts type DURATION with a default value of 10
optional
max_reconnect_timeout
accepts type DURATION with a default value of 10
optional
select_db
accepts type INT with a default value of 0
optional
user
accepts type STRING
password
accepts type STRING
Type: Method
Returns: None
Restricted to: vcl_init
The kv
VMOD is available in Varnish Enterprise version 6.0.14r6
and
later as a feature add-on.