diff options
Diffstat (limited to 'docs/beep-beep/kv.md')
-rw-r--r-- | docs/beep-beep/kv.md | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/docs/beep-beep/kv.md b/docs/beep-beep/kv.md new file mode 100644 index 00000000..0ea41034 --- /dev/null +++ b/docs/beep-beep/kv.md @@ -0,0 +1,679 @@ +# KV (Key-Value) Plugin + +The Key-Value plugin provides the ability to store arbitrary data inside the +RoadRunner between different requests (in case of HTTP application) or different +types of applications. Thus, using [Temporal](https://docs.temporal.io/docs/php/introduction), +for example, you can transfer data inside the [HTTP application](/php/worker.md) +and vice versa. + +As a permanent source of data, the RoadRunner allows you to use popular solutions, +such as [Redis Server](https://redis.io/) or [Memcached](https://memcached.org/), +but in addition it provides others that do not require a separate server, such +as [BoltDB](https://github.com/boltdb/bolt), and also allows you to replace +permanent storage with temporary that stores data in RAM. + +![kv-general-info](https://user-images.githubusercontent.com/2461257/128436785-3dadbf0d-13c3-4e0c-859c-4fd9668558c8.png) + +## Installation + +> **Requirements** +> - PHP >= 7.4 +> - RoadRunner >= 2.3 +> - *ext-protobuf (optional)* + +To get access from the PHP code, you should put the corresponding dependency +using [the Composer](https://getcomposer.org/). + +```sh +$ composer require spiral/roadrunner-kv +``` + +## Configuration + +After installing all the required dependencies, you need to configure this +plugin. To enable it add `kv` section to your configuration: + +```yaml +rpc: + listen: tcp://127.0.0.1:6001 + +kv: + example: + driver: memory +``` + +Please note that to interact with the KV, you will also need the RPC defined +in `rpc` configuration section. You can read more about the configuration and +methods of creating the RPC connection on the [documentation page here](/beep-beep/rpc.md). + +This configuration initializes this plugin with one storage named "`example`". +In addition, each storage must have a `driver` that indicates the type of +connection used by those storages. In total, at the moment, 4 different types of +drivers are available with their own characteristics and additional settings: +`boltdb`, `redis`, `memcached` and `memory`. + +The `memory` and `boltdb` drivers do not require additional binaries and are +available immediately, while the rest require additional setup. Please see +the appropriate documentation for installing [Redis Server](https://redis.io/) +and/or [Memcached Server](https://memcached.org/). + +### Memory Driver Configuration + +This type of driver is already supported by the RoadRunner and does not require +any additional installations. + +Please note that using this type of storage, all +data is contained in memory and will be destroyed when the RoadRunner Server +is restarted. If you need persistent storage without additional dependencies, +then it is recommended to use the boltdb driver. + +The complete memory driver configuration looks like this: + +```yaml +kv: + # User defined name of the storage. + memory: + # Required section. + # Should be "memory" for the memory driver. + driver: memory + + # Optional section. + # Default: 60 + interval: 60 +``` + +Below is a more detailed description of each of the memory-specific options: + +- `interval` - The interval (in seconds) between checks for the lifetime of the + value in the cache. For large values of the interval, the cache item will be + checked less often for expiration of its lifetime. It is recommended to use + large values only in cases when the cache is used without expiration values, + or in cases when this value is not critical to the architecture of your + application. Note that the lower this value, the higher the load on the + system. + +### Boltdb Driver Configuration + +This type of driver is already supported by the RoadRunner and does not require +any additional installations. + +The complete boltdb driver configuration looks like this: + +```yaml +kv: + # User defined name of the storage. + boltdb: + # Required section. + # Should be "boltdb" for the boltdb driver. + driver: boltdb + + # Optional section. + # Default: "rr.db" + file: "rr.db" + + # Optional section. + # Default: "." + dir: "." + + # Optional section. + # Default: 0777 + permissions: 0777 + + # Optional section. + # Default: "rr" + bucket: "rr" + + # Optional section. + # Default: 60 + interval: 60 +``` + +Below is a more detailed description of each of the boltdb-specific options: + +- `file` - Database file pathname name. In the case that such a file does not + exist in, RoadRunner will create this file on its own at startup. Note that this + must be an existing directory, otherwise a "The system cannot find the path + specified" error will be occurred, indicating that the full database pathname is + invalid. + +- `dir` - The directory prefix string where the database file will be located. + When forming the path to the file, this prefix will be added before the pathname + defined in `file` section. + +- `permissions` - The file permissions in UNIX format of the database file, set + at the time of its creation. If the file already exists, the permissions will + not be changed. + +- `bucket` - The bucket name. You can create several boltdb connections by + specifying different buckets and in this case the data stored in one bucket will + not intersect with the data stored in the other, even if the database file and + other settings are completely identical. + +- `interval` - The interval (in seconds) between checks for the lifetime of the + value in the cache. The meaning and behavior is similar to that used in the + case of the memory driver. + + +### Redis Driver Configuration + +Before configuring the Redis driver, please make sure that the Redis Server is +installed and running. You can read more about this [in the documentation](https://redis.io/). + +In the simplest case, when a full-fledged cluster or a fault-tolerant system is +not required, we have one connection to the Redis Server. The configuration of +such a connection will look like this. + +```yaml +kv: + # User defined name of the storage. + redis: + # Required section. + # Should be "redis" for the redis driver. + driver: redis + + # Optional section. + # By default, one connection will be specified with the + # "localhost:6379" value. + addrs: + - "localhost:6379" + + # Optional section. + # Default: "" + username: "" + + # Optional section. + # Default: "" + password: "" + + # Optional section. + # Default: 0 + db: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 5 seconds) + dial_timeout: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 3 retries) + max_retries: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 8ms) + min_retry_backoff: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 512ms) + max_retry_backoff: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 10 connections per CPU). + pool_size: 0 + + # Optional section. + # Default: 0 (do not use idle connections) + min_idle_conns: 0 + + # Optional section. + # Default: 0 (do not close aged connections) + max_conn_age: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 3s) + read_timeout: 0 + + # Optional section. + # Default: 0 (equivalent to the value specified in the "read_timeout" section) + write_timeout: 0 + + # Optional section. + # Default: 0 (equivalent to the value specified in the "read_timeout" + 1s) + pool_timeout: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 5m) + idle_timeout: 0 + + # Optional section. + # Default: 0 (equivalent to the default value of 1m) + idle_check_freq: 0 + + # Optional section. + # Default: false + read_only: false +``` + +Below is a more detailed description of each of the Redis-specific options: + +- `addrs` - An array of strings of connections to the Redis Server. Must + contain at least one value of an existing connection in the format of host or + IP address and port, separated by a colon (`:`) character. + +- `username` - Optional value containing the username credentials of the Redis + connection. You can omit this field, or specify an empty string if the + username of the connection is not specified. + +- `password` - Optional value containing the password credentials of the Redis + connection. You can omit this field, or specify an empty string if the + password of the connection is not specified. + +- `db` - An optional identifier for the database used in this connection to the + Redis Server. Read more about databases section on the documentation page for + the description of the [select command](https://redis.io/commands/select). + +- `dial_timeout` - Server connection timeout. A value of `0` is equivalent to a + timeout of 5 seconds (`5s`). After the specified time has elapsed, if the + connection has not been established, a connection error will occur. + + Must be in the format of a "numeric value" + "time format suffix", like "`2h`" where + suffixes means: + - `h` - the number of hours. For example `1h` means 1 hour. + - `m` - the number of minutes. For example `2m` means 2 minutes. + - `s` - the number of seconds. For example `3s` means 3 seconds. + - `ms` - the number of milliseconds. For example `4ms` means 4 milliseconds. + - If no suffix is specified, the value will be interpreted as specified in + nanoseconds. In most cases, this accuracy is redundant and may not be true. + For example `5` means 5 nanoseconds. + + Please note that all time intervals can be suffixed. + +- `max_retries` - Maximum number of retries before giving up. Specifying `0` is + equivalent to the default (`3` attempts). If you need to specify an infinite + number of connection attempts, specify the value `-1`. + +- `min_retry_backoff` - Minimum backoff between each retry. Must be in the format + of a "numeric value" + "time format suffix". A value of `0` is equivalent to a + timeout of 8 milliseconds (`8ms`). A value of `-1` disables backoff. + +- `max_retry_backoff` - Maximum backoff between each retry. Must be in the format + of a "numeric value" + "time format suffix". A value of `0` is equivalent to a + timeout of 512 milliseconds (`512ms`). A value of `-1` disables backoff. + +- `pool_size` - Maximum number of RoadRunner socket connections. A value of `0` + is equivalent to a `10` connections per every CPU. Please note that specifying + the value corresponds to the number of connections **per core**, so if you + have 8 cores in your system, then setting the option to 2 you will get 16 + connections. + +- `min_idle_conns` - Minimum number of idle connections which is useful when + establishing new connection is slow. A value of 0 means no such idle + connections. More details about the problem requiring the presence of this + option available in the [corresponding issue](https://github.com/go-redis/redis/issues/772). + +- `max_conn_age` - Connection age at which client retires (closes) the connection. + A value of `0` is equivalent to a disabling this option. In this case, aged + connections will not be closed. + +- `read_timeout` - Timeout for socket reads. If reached, commands will fail with + a timeout instead of blocking. Must be in the format of a "numeric value" + + "time format suffix". A value of `0` is equivalent to a timeout of 3 seconds + (`3s`). A value of `-1` disables timeout. + +- `write_timeout` - Timeout for socket writes. If reached, commands will fail + with a timeout instead of blocking. A value of `0` is equivalent of the value + specified in the `read_timeout` section. If `read_timeout` value is not + specified, a value of 3 seconds (`3s`) will be used. + +- `pool_timeout` - Amount of time client waits for connection if all connections + are busy before returning an error. A value of `0` is equivalent of the value + specified in the `read_timeout` + `1s`. If `read_timeout` value is not + specified, a value of 4 seconds (`4s`) will be used. + +- `idle_timeout` - Amount of time after which client closes idle connections. + Must be in the format of a "numeric value" + "time format suffix". A value of + `0` is equivalent to a timeout of 5 minutes (`5m`). A value of `-1` disables + idle timeout check. + +- `idle_check_freq` - Frequency of idle checks made by idle connections reaper. + Must be in the format of a "numeric value" + "time format suffix". A value of + `0` is equivalent to a timeout of 1 minute (`1m`). A value of `-1` disables + idle connections reaper. Note, that idle connections are still discarded by + the client if `idle_timeout` is set. + +- `read_only` - An optional boolean value that enables or disables read-only + mode. If `true` value is specified, the writing will be unavailable. + Note that this option **not allowed** when working with Redis Sentinel. + +These are all options available for all Redis connection types. + +#### Redis Cluster + +In the case that you want to configure a [Redis Cluster](https://redis.io/topics/cluster-tutorial), +then you can specify additional options required only if you are organizing this +type of server. + +When creating a cluster, multiple connections are available to you. For example, +you call such a command `redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380`, +you should specify the appropriate set of connections. In addition, when +organizing a cluster, two additional options with algorithms for working with +connections will be available to you: `route_by_latency` and `route_randomly`. + +```yaml +kv: + redis: + driver: redis + addrs: + - "127.0.0.1:6379" + - "127.0.0.1:6380" + + # Optional section. + # Default: false + route_by_latency: false + + # Optional section. + # Default: false + route_randomly: false +``` + +Where new options means: + +- `route_by_latency` - Allows routing read-only commands to the closest master + or slave node. If this option is specified, the `read_only` configuration value + will be automatically set to `true`. + +- `route_randomly` - Allows routing read-only commands to the random master or + slave node. If this option is specified, the `read_only` configuration value + will be automatically set to `true`. + +#### Redis Sentinel + +Redis Sentinel provides high availability for Redis. You can find more +information about [Sentinel on the documentation page](https://redis.io/topics/sentinel). + +There are two additional options available for the Sentinel configuration: +`master_name` and `sentinel_password`. + +```yaml +kv: + redis: + driver: redis + + # Required section. + master_name: "" + + # Optional section. + # Default: "" (no password) + sentinel_password: "" +``` + +Where Sentinel's options means: + +- `master_name` - The name of the Sentinel's master in string format. + +- `sentinel_password` - Sentinel password from "requirepass <password>" + (if enabled) in Sentinel configuration. + + +### Memcached Driver Configuration + +Before configuring the Memcached driver, please make sure that the Memcached +Server is installed and running. You can read more about this [in the documentation](https://memcached.org/). + +The complete memcached driver configuration looks like this: + +```yaml +kv: + # User defined name of the storage. + memcached: + # Required section. + # Should be "memcached" for the memcached driver. + driver: memcached + + # Optional section. + # Default: "localhost:11211" + addr: "localhost:11211" +``` + +Below is a more detailed description of each of the memcached-specific options: + +- `addr` - String of memcached connection in format "`[HOST]:[PORT]`". In the case + that there are several memcached servers, then the list of connections can be + listed in an array format, for example: `addr: [ "localhost:11211", "localhost:11222" ]`. + +## Usage + +First, you need to create the RPC connection to the RoadRunner server. You can +specify an address with a connection by hands or use automatic detection if +you run the php code as a [RoadRunner Worker](/php/worker.md). + +```php +use Spiral\RoadRunner\Environment; +use Spiral\Goridge\RPC\RPC; + +// Manual configuration +$rpc = RPC::create('tcp://127.0.0.1:6001'); + +// Autodetection +$env = Environment::fromGlobals(); +$rpc = RPC::create($env->getRPCAddress()); +``` + +After creating the RPC connection, you should create the +`Spiral\RoadRunner\KeyValue\Factory` object for working with storages of KV +RoadRunner plugin. + +The factory object provides two methods for working with the plugin. + +- Method `Factory::isAvailable(): bool` returns boolean `true` value if the + plugin is available and `false` otherwise. + +- Method `Factory::select(string): CacheInterface` receives the name of the + storage as the first argument and returns the implementation of the + [PSR-16](https://www.php-fig.org/psr/psr-16/) `Psr\SimpleCache\CacheInterface` + for interact with the key-value RoadRunner storage. + +```php +use Spiral\Goridge\RPC\RPC; +use Spiral\RoadRunner\KeyValue\Factory; + +$factory = new Factory(RPC::create('tcp://127.0.0.1:6001')); + +if (!$factory->isAvailable()) { + throw new \LogicException('The "kv" RoadRunner plugin not available'); +} + +$storage = $factory->select('storage-name'); +// Expected: +// An instance of Psr\SimpleCache\CacheInterface interface + +$storage->set('key', 'value'); + +echo $storage->get('key'); +// Expected: +// string(5) "string" +``` + +> The `clear()` method available since [RoadRunner v2.3.1](https://github.com/spiral/roadrunner/releases/tag/v2.3.1). + +Apart from this, RoadRunner Key-Value API provides several additional methods: +You can use `getTtl(string): ?\DateTimeInterface` and +`getMultipleTtl(string): iterable<\DateTimeInterface|null>` methods to get +information about the expiration of an item stored in a key-value storage. + +> Please note that the `memcached` driver +> [**does not support**](https://github.com/memcached/memcached/issues/239) +> these methods. + +```php +$ttl = $factory->select('memory') + ->getTtl('key'); +// Expected: +// - An instance of \DateTimeInterface if "key" expiration time is available +// - Or null otherwise + +$ttl = $factory->select('memcached') + ->getTtl('key'); +// Expected: +// Spiral\RoadRunner\KeyValue\Exception\KeyValueException: Storage "memcached" +// does not support kv.TTL RPC method execution. Please use another driver for +// the storage if you require this functionality. +``` + +### Value Serialization + +To save and receive data from the key-value store, the data serialization +mechanism is used. This way you can store and receive arbitrary serializable +objects. + +```php +$storage->set('test', (object)['key' => 'value']); + +$item = $storage->set('test'); +// Expected: +// object(StdClass)#399 (1) { +// ["key"] => string(5) "value" +// } +``` + +To specify your custom serializer, you will need to specify it in the key-value +factory constructor as a second argument, or use the +`Factory::withSerializer(SerializerInterface): self` method. + +```php +use Spiral\Goridge\RPC\RPC; +use Spiral\RoadRunner\KeyValue\Factory; + +$connection = RPC::create('tcp://127.0.0.1:6001'); + +$storage = (new Factory($connection)) + ->withSerializer(new CustomSerializer()) + ->select('storage'); +``` + +In the case that you need a specific serializer for a specific value from the +storage, then you can use a similar method `withSerializer()` for a specific +storage. + +```php +// Using default serializer +$storage->set('key', 'value'); + +// Using custom serializer +$storage + ->withSerializer(new CustomSerializer()) + ->set('key', 'value'); +``` + + +#### Igbinary Value Serialization + +As you know, the serialization mechanism in PHP is not always productive. To +increase the speed of work, it is recommended to use the +[ignbinary extension](https://github.com/igbinary/igbinary). + +- For the Windows OS, you can download it from the + [PECL website](https://windows.php.net/downloads/pecl/releases/igbinary/). + +- In a Linux and MacOS environment, it may be installed with a simple command: +```sh +$ pecl install igbinary +``` + +More detailed installation instructions are [available here](https://github.com/igbinary/igbinary#installing). + +After installing the extension, you just need to install the desired igbinary +serializer in the factory instance. + +```php +use Spiral\Goridge\RPC\RPC; +use Spiral\RoadRunner\KeyValue\Factory; +use Spiral\RoadRunner\KeyValue\Serializer\IgbinarySerializer; + +$storage = (new Factory(RPC::create('tcp://127.0.0.1:6001'))) + ->withSerializer(new IgbinarySerializer()) + ->select('storage'); +// +// Now this $storage is using igbinary serializer. +// +``` + +#### End-to-End Value Encryption + +Some data may contain sensitive information, such as personal data of the user. +In these cases, it is recommended to use data encryption. + +To use encryption, you need to install the +[Sodium extension](https://www.php.net/manual/en/book.sodium.php). + +Next, you should have an encryption key generated using +[sodium_crypto_box_keypair()](https://www.php.net/manual/en/function.sodium-crypto-box-keypair.php) +function. You can do this using the following command: +```sh +$ php -r "echo sodium_crypto_box_keypair();" > keypair.key +``` + +> Do not store security keys in a control versioning systems (like GIT)! + +After generating the keypair, you can use it to encrypt and decrypt the data. + +```php +use Spiral\Goridge\RPC\RPC; +use Spiral\RoadRunner\KeyValue\Factory; +use Spiral\RoadRunner\KeyValue\Serializer\SodiumSerializer; +use Spiral\RoadRunner\KeyValue\Serializer\DefaultSerializer; + +$storage = new Factory(RPC::create('tcp://127.0.0.1:6001')); + ->select('storage'); + +// Encrypted serializer +$key = file_get_contents(__DIR__ . '/path/to/keypair.key'); +$encrypted = new SodiumSerializer($storage->getSerializer(), $key); + +// Storing public data +$storage->set('user.login', 'test'); + +// Storing private data +$storage->withSerializer($encrypted) + ->set('user.email', '[email protected]'); +``` + +## RPC Interface + +All communication between PHP and GO made by the RPC calls with protobuf payloads. +You can find versioned proto-payloads here: [Proto](https://github.com/spiral/roadrunner/tree/v2.3.0/pkg/proto/kv/v1beta). + +- `Has(in *kvv1.Request, out *kvv1.Response)` - The arguments: the first argument +is a `Request` , which declares a `storage` and an array of `Items` ; the second +argument is a `Response`, it will contain `Items` with keys which are present in +the provided via `Request` storage. Item value and timeout are not present in +the response. The error returned if the request fails. + +- `Set(in *kvv1.Request, _ *kvv1.Response)` - The arguments: the first argument +is a `Request` with the `Items` to set; return value isn't used and present here +only because GO's RPC calling convention. The error returned if request fails. + +- `MGet(in *kvv1.Request, out *kvv1.Response)` - The arguments: the first +argument is a `Request` with `Items` which should contain only keys (server +doesn't check other fields); the second argument is `Response` with the `Items`. +Every item will have `key` and `value` set, but without timeout (See: `TTL`). +The error returned if request fails. + +- `MExpire(in *kvv1.Request, _ *kvv1.Response)` - The arguments: the first +argument is a `Request` with `Items` which should contain keys and timeouts set; +return value isn't used and present here only because GO's RPC calling convention. +The error returned if request fails. + +- `TTL(in *kvv1.Request, out *kvv1.Response)` - The arguments: the first argument +is a `Request` with `Items` which should contain keys; return value will contain +keys with their timeouts. The error returned if request fails. + +- `Delete(in *kvv1.Request, _ *kvv1.Response)` - The arguments: the first +argument is a `Request` with `Items` which should contain keys to delete; return +value isn't used and present here only because GO's RPC calling convention. +The error returned if request fails. + +- `Clear(in *kvv1.Request, _ *kvv1.Response)` - The arguments: the first +argument is a `Request` with `storage` which should contain the storage to be +cleaned up; return value isn't used and present here only because GO's RPC +calling convention. The error returned if request fails. + +From the PHP point of view, such requests (`MGet` for example) are as follows: +```php +use Spiral\Goridge\RPC\RPC; +use Spiral\Goridge\RPC\Codec\ProtobufCodec; +use Spiral\RoadRunner\KeyValue\DTO\V1\{Request, Response}; + +$response = RPC::create('tcp://127.0.0.1:6001') + ->withServicePrefix('kv') + ->withCodec(new ProtobufCodec()) + ->call('MGet', new Request([ ... ]), Response::class); +``` |