Usa ColibriDB desde tu código.
Todas las formas de hablar con un clúster: llamadas HTTP, comandos Redis y las bibliotecas cliente, con ejemplos listos para copiar. La página Connect de tu consola Admin muestra las direcciones y credenciales exactas de tu clúster.
Conexión
Las aplicaciones hablan con los proxies, que envían cada clave al nodo que la tiene y siguen los failovers por ti. Elige la vía que mejor encaje con tu stack; todas ven los mismos datos.
| Punto de acceso | Puerto por defecto | Para qué | Autenticación |
|---|---|---|---|
Proxy HTTP | 5006 | curl, cualquier lenguaje, los clientes Python y TypeScript | Token Bearer, Basic, JWT (OIDC) o un usuario ACL |
Proxy binario | 6400 | El cliente .NET: la menor latencia | API key o un usuario ACL |
RESP en los nodos | 6379 | redis-cli y cualquier cliente Redis (StackExchange.Redis, redis-py, ioredis, go-redis) | AUTH con la clave del clúster o un usuario ACL |
# De Admin → Connect: la dirección del proxy HTTP y un token
export COLIBRI_URL=https://db.example.com:5006
export COLIBRI_TOKEN=your-proxy-tokenClaves por HTTP
El valor es el cuerpo de la petición tal cual, cualquier byte hasta el límite configurado. La clave es un solo segmento de la ruta: escribe una / dentro de una clave como %2F.
# Escribe un valor que expira en una hora
curl -X PUT "$COLIBRI_URL/kv/user:42?ttlSeconds=3600" \
-H "Authorization: Bearer $COLIBRI_TOKEN" \
--data-binary '{"name":"Ada","plan":"pro"}'
# Léelo (404 si la clave no existe)
curl "$COLIBRI_URL/kv/user:42" -H "Authorization: Bearer $COLIBRI_TOKEN"
# Escribe solo si la clave no existe: un candado simple (409 si alguien ya lo tiene)
curl -X PUT "$COLIBRI_URL/kv/lock:report?nx=true&ttlSeconds=30" \
-H "Authorization: Bearer $COLIBRI_TOKEN" --data-binary "worker-7"
# Lista claves por prefijo, de 100 en 100 (los valores vienen en base64; pasa nextCursor para seguir)
curl "$COLIBRI_URL/kv?prefix=user:&limit=100" -H "Authorization: Bearer $COLIBRI_TOKEN"
# Bórrala
curl -X DELETE "$COLIBRI_URL/kv/user:42" -H "Authorization: Bearer $COLIBRI_TOKEN"| Llamada | Qué hace | Respuestas |
|---|---|---|
GET /kv/{key} | Lee el valor | 200 con los bytes · 404 |
PUT /kv/{key} | Escribe el valor. ?ttlSeconds=N lo hace expirar, ?nx=true escribe solo si no existe, ?xx=true solo si existe | 204 · 409 (nx) · 404 (xx) · 413 demasiado grande |
DELETE /kv/{key} | Borra la clave | 204 · 404 |
GET /kv?prefix=&limit=&cursor= | Recorre las claves por páginas | { items:[{key, value}], nextCursor } |
GET /health | Estado y rol, sin credenciales | 200 |
Hashes, sets, sorted sets y listas
Las mismas operaciones atómicas que los comandos Redis, con cuerpos JSON (Content-Type: application/json). El texto viaja como texto; agrega ?encoding=base64 para enviar y recibir valores binarios.
# Un hash: escribe campos, incrementa uno, léelos todos
curl -X PUT "$COLIBRI_URL/hash/cart:7" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"fields":{"sku-1":"2","sku-9":"1"}}'
curl -X POST "$COLIBRI_URL/hash/cart:7/incr" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"field":"sku-1","by":3}'
curl "$COLIBRI_URL/hash/cart:7" -H "Authorization: Bearer $COLIBRI_TOKEN"
# → {"key":"cart:7","count":2,"fields":{"sku-1":"5","sku-9":"1"}}
# Un sorted set: agrega puntajes, lee el top 10
curl -X PUT "$COLIBRI_URL/zset/leaderboard" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"members":[{"member":"ada","score":120},{"member":"bob","score":95}]}'
curl "$COLIBRI_URL/zset/leaderboard?start=0&stop=9&rev=true" -H "Authorization: Bearer $COLIBRI_TOKEN"
# Una lista como cola: agrega al final, saca del inicio
curl -X POST "$COLIBRI_URL/list/jobs/push" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"side":"right","values":["job-1","job-2"]}'
curl -X POST "$COLIBRI_URL/list/jobs/pop" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"side":"left","count":1}'
# Un set: agrega miembros, comprueba uno
curl -X PUT "$COLIBRI_URL/set/tags:42" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"members":["vip","beta"]}'
curl "$COLIBRI_URL/set/tags:42?member=vip" -H "Authorization: Bearer $COLIBRI_TOKEN"| Tipo | Llamadas | Equivale a |
|---|---|---|
/hash/{key} | GET (?field=…), PUT { fields }, DELETE ?field=…, POST /incr { field, by } | HGETALL, HSET, HDEL, HINCRBY |
/set/{key} | GET (?member=…), PUT { members }, DELETE ?member=…, POST /pop { count } | SMEMBERS, SADD, SREM, SPOP |
/zset/{key} | GET ?start&stop&rev o ?min&max, PUT { members:[{member, score}] }, POST /incr, POST /pop | ZRANGE, ZADD, ZINCRBY, ZPOPMIN |
/list/{key} | GET ?start&stop, POST /push { side, values }, POST /pop { side, count }, POST /trim | LRANGE, LPUSH/RPUSH, LPOP/RPOP, LTRIM |
Comandos Redis
Activa RESP en los nodos (Admin → Configuration) y usa redis-cli o el cliente Redis que ya tienes. Con TLS activo, conéctate con --tls o una URL rediss://.
redis-cli -h db.example.com -p 6379 --tls -a "$COLIBRI_PASSWORD"
SET user:42 '{"name":"Ada"}' EX 3600
GET user:42
HSET cart:7 sku-1 2 sku-9 1
ZADD leaderboard 120 ada 95 bob
ZREVRANGE leaderboard 0 9 WITHSCORES
MULTI
INCR visits:today
EXPIRE visits:today 86400
EXECLa compatibilidad está verificada con una suite de pruebas y con los clientes StackExchange.Redis, redis-py e ioredis, sobre RESP2 y RESP3:
| Área | Estado | Notas |
|---|---|---|
| Conexión, AUTH, PING, HELLO 2 y 3, pipelining | Soportado | Las escrituras de un pipeline comparten un group commit; vea Escrituras en pipeline más abajo |
| Strings, TTL, opciones de SET, INCR, APPEND, GETRANGE y SETRANGE | Soportado | La expiración es perezosa más un barrido periódico |
| Claves: KEYS, SCAN, RENAME, COPY y TOUCH | Soportado | Los comandos de varias claves requieren claves en un mismo shard; use un {hash tag} |
| Hashes | Soportado | Se reescribe toda la colección en cada escritura, tope de 4 MiB |
| Sets, sorted sets y listas | Parcial | Con uniones, intersecciones, variantes STORE, LMOVE y rangos léxicos; aún sin operaciones bloqueantes |
| Bitmaps | Soportado | SETBIT, GETBIT, BITCOUNT, BITPOS y BITOP; sin BITFIELD |
| MULTI, EXEC y WATCH | Parcial | Las claves deben estar en un mismo shard; use {hash tags}. EXEC es atómico y seguro ante caídas |
| Pub/sub | Soportado | De todo el clúster con el relay configurado; si no, local al nodo |
| Notificaciones de keyspace | Soportado | Eventos para escrituras de todos los protocolos |
| RESP3 | Soportado | Mapas, sets, doubles y push tras HELLO 3 |
| Caché del lado del cliente (CLIENT TRACKING) | No soportado | |
| Streams, Lua y EVAL, HyperLogLog, geo | No soportado | |
| FLUSHDB, FLUSHALL, SELECT mayor que 0, CLUSTER | No soportado | Rechazados |
Bibliotecas cliente
Los paquetes cliente vienen con tu licencia. Siguen los cambios de líder, reintentan lecturas de forma segura y nunca repiten una escritura que pudo haberse aplicado.
using ColibriDB.Client;
await using var db = new ColibriClient(new ColibriClientOptions
{
Host = "db.example.com",
Port = 6400,
ApiKey = Environment.GetEnvironmentVariable("COLIBRI_KEY"),
});
await db.PutAsync("user:42", "{\"name\":\"Ada\"}");
string? user = await db.GetStringAsync("user:42");
var many = await db.GetManyAsync(["user:1", "user:2", "user:3"]);import os
from colibridb import ColibriClient
with ColibriClient(os.environ["COLIBRI_URL"], bearer=os.environ["COLIBRI_TOKEN"]) as db:
db.put("user:42", '{"name":"Ada"}', ttl_seconds=3600)
db.get_text("user:42") # '{"name":"Ada"}'
db.put("lock:report", "worker-7", nx=True) # False if the key exists
db.hash_set("cart:7", {"sku-1": "2"})
db.zset_add("leaderboard", {"ada": 120, "bob": 95})
for key, value in db.iter_scan("user:"):
print(key, value)import { ColibriClient } from "@colibridb/client";
const db = new ColibriClient({
baseUrl: process.env.COLIBRI_URL!,
bearer: process.env.COLIBRI_TOKEN!,
});
await db.put("user:42", JSON.stringify({ name: "Ada" }), { ttlSeconds: 3600 });
const user = await db.getText("user:42");
await db.put("lock:report", "worker-7", { nx: true }); // false if the key exists
await db.zsetAdd("leaderboard", new Map([["ada", 120], ["bob", 95]]));
for await (const [key, value] of db.iterScan("user:")) {
console.log(key, value);
}| Cliente | Protocolo | GET en loopback | Notas |
|---|---|---|---|
.NET | Binario, puerto 6400 | unos 37 µs | Conexiones en pool y con pipelining; TLS y mTLS. Este protocolo no tiene TTL ni scan: usa HTTP para eso |
Python | HTTP, puerto 5006 | unos 75 µs | Python 3.9+, una conexión keep-alive por hilo; hashes, sets, sorted sets, listas y vectores |
TypeScript | HTTP, puerto 5006 | unos 65 µs | Node, Deno, Bun y navegadores; los mismos métodos tipados que Python |
Errores y reintentos
Los errores llegan como JSON Problem Details (type, title, detail). Los clientes manejan las redirecciones y los reintentos seguros por ti; con HTTP directo, esto significa cada código y qué hacer.
| Código | Significado | Qué hacer |
|---|---|---|
401 · 403 | Faltan credenciales o son incorrectas, o la ACL niega esa clave | Revisa el token o los patrones de claves del usuario |
404 | La clave no existe (o xx=true no encontró nada) | Trátalo como ausencia |
409 | nx=true encontró la clave, o la clave guarda otro tipo | Lee la clave o usa otro nombre |
413 | El valor o la colección supera el límite | Divide el valor; no se escribió nada |
421 | El nodo no es el líder del grupo de esa clave | Repite en X-Colibri-Leader-Url; no se aplicó nada |
429 | Límite de peticiones del usuario ACL | Espera y reintenta |
503 | Elección, migración o sin quórum (Retry-After) | Reintenta lecturas; escrituras solo con X-Colibri-Fenced: raft-not-applied |
504 | No se sabe si la escritura se aplicó | Lee la clave antes de repetir la escritura |
507 | La cuota del tenant está llena | Lecturas y borrados siguen funcionando; libera espacio o sube la cuota |
Escrituras en pipeline y durabilidad
Un cliente que envía escrituras seguidas por una conexión las ve confirmadas juntas, y las respuestas siguen el orden de las peticiones. Estos son los ajustes y lo que garantizan.
| Ajuste | Por defecto | Rango | Notas |
|---|---|---|---|
| Node__PipelinedWrites | 128 | 0 a 4096 | Escrituras SET y PUT simples en vuelo por conexión, en RESP y en el protocolo binario. 0 lo desactiva. |
| Node__PipelinedWriteBytes | 1 MiB | 1 B a 256 MiB | Bytes de clave y valor en vuelo por conexión. Una sola escritura mayor no se difiere. |
| ProxyBinary__PipelinedFrames | 128 | 0 a 1024 | Frames GET, PUT y DELETE que el proxy binario reenvía a un nodo antes de leer las respuestas. 0 o 1 lo desactiva. |
| ProxyBinary__PipelinedBytes | 1 MiB | 0 a 1 GiB | Bytes de petición en un lote reenviado. 0 lo desactiva. |
| ProxyBinary__PipelinedResponseBytes | 2 MiB | 64 KiB a 1 GiB | Respuestas que un lote puede guardar fuera de orden. Si se supera, el proxy deja de leer esa conexión del nodo hasta que le toque. |
| ProxyBinary__MaxIdleBackendConnectionsPerNode | 1024 | 0 a 10000 | Conexiones ociosas que se mantienen abiertas por nodo. Manténgalo igual o por encima del número real de clientes concurrentes. |
| ProxyBinary__BackendIdleTimeoutSeconds | 240 | 1 a 299 | Segundos de inactividad tras los cuales se cierra una conexión a un nodo. Queda por debajo del idle timeout de 300 s del nodo. |
- Cualquier otro comando, una clave repetida o un error de protocolo cierra primero la tanda, así que un comando que sigue a una escritura en el mismo pipeline la ve.
- Con WalPolicy Always, una tanda cuesta un fsync en vez de uno por escritura. Los grupos Raft siempre usan Always.
- Con WalPolicy Interval, el fsync periódico corre en una tarea en segundo plano y las escrituras no lo esperan. La ventana de pérdida sigue siendo como máximo WalFlushIntervalMs.
- Si falla el fsync del log de replicación de archivo, el shard sigue atendiendo y la replicación se degrada: la salud del nodo informa Degraded, la métrica colibri.engine.replication_faulted_shards cuenta el shard y queda un marcador .gap junto al log. La reproducción rechaza un log con un hueco, así que hay que resincronizar la réplica.
- La API de log, la replicación y el índice vectorial nunca ven una posición del log antes de que sus datos sean legibles. Si aplicar una escritura al índice falla tras el commit, el shard se detiene en vez de responder con un error engañoso.
- Un fsync fallido durante la rotación de un segmento nunca se informa como duradero.
La arquitectura, la seguridad, la operación y la referencia completa de configuración vienen con tu licencia. Pídenoslas