Клиенты RESP

Подключайте совместимые с Redis клиенты для чтения целых строк PostgreSQL по первичному ключу. На этой странице описаны кодирование ключей, аутентификация, формат ответов, ошибки и TLS.

Формат ключей и ответов

Ключи RESP указывают на подключённую таблицу и объект первичного ключа:

CRUD:<db>.<schema>.<table>:<json pk>
CRUD:app.public.items:{"id":42}
CRUD:app.public.orders:{"tenant_id":7,"id":42}

Команда MGET key [key ...] возвращает массив RESP2 в порядке запроса. Дубликаты ключей сохраняют свои позиции. Для существующих строк возвращаются bulk-строки JSON, для отсутствующих — элементы nil.

Ограничение Поведение
Не более 1 024 ключей в команде В пределах лимита байтов команда с 1 025 ключами возвращает ERR MGET accepts at most 1024 keys; при 1 026 ключах и более парсер возвращает ERR invalid argument count.
65 536 байт на закодированный запрос Если входной буфер заполнится до разбора полной команды, сервер закроет соединение.
65 536 байт на строку JSON Строку большего размера нельзя вернуть через RESP.
66 560 байт на закодированный ответ MGET Ответы большего размера возвращают ERR response exceeds limit.

Размер пакета ограничен числом ключей, размером запроса и размером ответа. Не превышайте 65 536 байт на закодированный запрос; разбивайте пакеты, если крупные ключи или строки приближаются к любому из лимитов в байтах.

AUTH

Перед остальными командами отправьте AUTH <token>. Клиенты также могут отправить AUTH <username> <token>; имя пользователя должно совпадать с pg_local_cache.role. Токен общий для RESP-клиентов и не выбирает привилегии PostgreSQL отдельно для каждого клиента. Храните его в pg_local_cache.auth_token_file с режимом 0400 или 0600; встроенный auth_token используйте только при разработке. Для listener вне loopback требуется токен длиной не менее 32 байт.

TLS

Для встроенного TLS в RESP используются параметры pg_local_cache.tls_*; они не связаны с SQL TLS в PostgreSQL. Требуются сборка PostgreSQL с поддержкой OpenSSL, сертификат сервера и закрытый ключ. Параметр tls_ca_file также делает сертификаты клиентов обязательными и проверяет их (mTLS). Для TLS с аутентификацией только сервера не задавайте параметры сертификата клиента. Минимальная версия по умолчанию — TLS 1.2.

По умолчанию listener привязан к loopback. Если TLS выключен, plaintext-listener вне loopback требует pg_local_cache.allow_plaintext_network=on. Параметры listener и безопасности описаны в техническом справочнике.

redis-cli

Задайте PGLC_RESP_TOKEN, присвоив ему настроенный токен. В локальном кратком руководстве также приведён демонстрационный токен.

export REDISCLI_AUTH="$PGLC_RESP_TOKEN"
redis-cli -2 -h 127.0.0.1 -p 56379 MGET 'CRUD:pglc_demo.public.items:{"id":42}'
redis-cli -2 --tls --cacert ./ca.crt --cert ./client.crt --key ./client.key -h cache.example -p 6380 MGET 'CRUD:app.public.items:{"id":42}'

Go

Установите github.com/redis/go-redis/v9. Задайте PGLC_RESP_TOKEN; для TLS-варианта нужны файлы CA и сертификата клиента.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/redis/go-redis/v9"
)

func main() {
	client := redis.NewClient(&redis.Options{
		Addr: "127.0.0.1:56379", Password: os.Getenv("PGLC_RESP_TOKEN"), Protocol: 2,
	})
	defer client.Close()
	rows, err := client.MGet(context.Background(), `CRUD:pglc_demo.public.items:{"id":42}`).Result()
	if err != nil {
		panic(err)
	}
	fmt.Printf("%v\n", rows)
}

Вариант с TLS:

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"os"

	"github.com/redis/go-redis/v9"
)

func main() {
	ca, err := os.ReadFile("./ca.crt")
	if err != nil { panic(err) }
	roots := x509.NewCertPool()
	if !roots.AppendCertsFromPEM(ca) { panic("invalid CA") }
	cert, err := tls.LoadX509KeyPair("./client.crt", "./client.key")
	if err != nil { panic(err) }
	client := redis.NewClient(&redis.Options{
		Addr: "cache.example:6380", Password: os.Getenv("PGLC_RESP_TOKEN"), Protocol: 2,
		TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12, ServerName: "cache.example", RootCAs: roots, Certificates: []tls.Certificate{cert}},
	})
	defer client.Close()
	rows, err := client.MGet(context.Background(), `CRUD:app.public.items:{"id":42}`).Result()
	if err != nil { panic(err) }
	fmt.Printf("%v\n", rows)
}

Node.js (redis v4)

Установите пакет командой npm install redis@^4. По умолчанию Redis v4 использует RESP2.

import { createClient } from 'redis';

const client = createClient({
  socket: { host: '127.0.0.1', port: 56379 },
  password: process.env.PGLC_RESP_TOKEN,
});
client.on('error', console.error);
await client.connect();
try {
  console.log(await client.mGet(['CRUD:pglc_demo.public.items:{"id":42}']));
} finally {
  await client.quit();
}

Вариант с TLS:

import { readFileSync } from 'node:fs';
import { createClient } from 'redis';

const client = createClient({
  socket: {
    host: 'cache.example', port: 6380, tls: true, servername: 'cache.example',
    ca: [readFileSync('./ca.crt')],
    cert: readFileSync('./client.crt'), key: readFileSync('./client.key'),
  },
  password: process.env.PGLC_RESP_TOKEN,
});
client.on('error', console.error);
await client.connect();
try {
  console.log(await client.mGet(['CRUD:app.public.items:{"id":42}']));
} finally {
  await client.quit();
}

Python (redis-py)

Установите пакет командой python -m pip install redis.

import os
import redis

client = redis.Redis(host="127.0.0.1", port=56379,
                     password=os.environ["PGLC_RESP_TOKEN"],
                     decode_responses=True, protocol=2)
print(client.execute_command('MGET', 'CRUD:pglc_demo.public.items:{"id":42}'))
client.close()

Вариант с TLS:

import os
import redis

client = redis.Redis(host="cache.example", port=6380,
                     password=os.environ["PGLC_RESP_TOKEN"],
                     decode_responses=True, protocol=2, ssl=True,
                     ssl_ca_certs="./ca.crt", ssl_certfile="./client.crt",
                     ssl_keyfile="./client.key", ssl_cert_reqs="required",
                     ssl_check_hostname=True)
print(client.execute_command('MGET', 'CRUD:app.public.items:{"id":42}'))
client.close()

Ошибки и ограничения

Ответ Причина
NOAUTH Authentication required Сначала аутентифицируйте соединение.
WRONGPASS invalid authentication token Неверный токен или необязательное имя пользователя.
ERR MGET accepts at most 1024 keys В команде 1 025 ключей; разбейте пакет.
ERR invalid argument count Парсер отклонил слишком много аргументов; MGET с 1 026 ключами и более превышает лимит аргументов.
ERR busy: relation locked, retry Очередь отложенных промахов заполнена, пока занята блокировка отношения; повторите запрос.
ERR response exceeds limit Уменьшите размер пакета или полезной нагрузки строки.
ERR PostgreSQL: … Ошибка PostgreSQL или SPI, в том числе таймаут или отмена по таймауту оператора либо блокировки PostgreSQL.
ERR MGET deadline exceeded Явный общий срок MGET истёк, в том числе при ожидании разблокировки отношения; это не таймаут оператора или блокировки PostgreSQL.
ERR KVik key targets a different database Укажите базу данных, настроенную для RESP-эндпоинта.
ERR unknown KVik table mapping Проверьте, что в ключе указаны сопоставленные схема и таблица.
ERR key must use CRUD:database.schema.table:{primary-key-json} Используйте полный формат CRUD-ключа.
ERR KVik key must end with a primary-key JSON object Завершите область таблицы JSON-объектом первичного ключа.
ERR invalid CRUD cache scope Только для INVALIDATE: указанная область не является поддерживаемой областью CRUD.

RESP использует отдельное подключение и не участвует в SQL-транзакции вызывающей стороны. Для SQL-транзакций используйте PostgreSQL напрямую; см. руководство по инвалидации.

Остановка демонстрации

docker compose -f examples/compose.yaml -f examples/compose.resp.yaml down

Функция SQL local_cache.mget(regclass, anyarray), доступная в версии 2.x, удалена в версии 3.0.0; см. руководство по обновлению.

Обновлено .