Pular para o conteúdo

Armazenamento

Inventário canônico de onde o runtime e os plugins persistem dados. O SQL durável é fornecido por @buntime/plugin-turso, apoiado pelo Turso Database. O Helm chart do runtime expõe valores gerados plugins.turso.*. O sistema de arquivos (com PVCs no Helm) carrega o código (aplicações + plugins) e um único store em arquivo (chaves de API). A seção tabelas do KeyVal abaixo documenta o schema que @buntime/plugin-keyval cria através do plugin-turso.

  • SQL durável apenas via Turso. O Buntime converge para o Turso Database como o único driver de SQL durável. Referências anteriores a adaptadores LibSQL/SQLite/Postgres/MySQL são detalhes legados de implementação destinados à remoção, não a superfície desejada de longo prazo.
  • Turso para estado de plugin gravável e concorrente. Estado operacional de plugin que pode receber escritas concorrentes de admin/API usa o engine do Turso Database, não bun:sqlite, porque o Turso suporta MVCC e BEGIN CONCURRENT. bun:sqlite é excelente para acesso local rápido ao SQLite e o WAL melhora os leitores concorrentes, mas o WAL do SQLite ainda permite apenas um escritor por vez.
  • Provedor Turso compartilhado para SQL durável. Plugins que precisam de SQL durável dependem de @buntime/plugin-turso. O plugin consumidor é dono do seu schema e migrações, enquanto o plugin-turso é dono da conexão, sincronização, configuração de MVCC e política de retry.
  • Gateway/proxy não devem depender do KeyVal, e o KeyVal não deve depender de infraestrutura não relacionada. plugin-gateway, plugin-proxy e plugin-keyval usam, cada um, @buntime/plugin-turso diretamente para seu armazenamento durável. Isso mantém o gateway/proxy habilitáveis de forma independente e mantém o KeyVal como um plugin de funcionalidade KV, não como infraestrutura obrigatória para plugins de borda não relacionados.
  • Alvo Kubernetes = Turso Sync. Arquivos locais de banco Turso são aceitáveis para testes locais e deployments de pod único. Deployments Kubernetes são projetados em torno do Turso Sync, de modo que cada pod possua seu próprio arquivo de banco local e sincronize com um servidor de sync remoto em vez de compartilhar o mesmo arquivo de banco através de um volume RWX.
  • Sem novo trabalho multi-adaptador. Não expanda nenhuma abstração de adaptador. O alvo do runtime é um único driver de SQL durável: Turso.
  • Store em arquivo apenas onde a sessão/processo exige. O único store crítico em arquivo é o store de chaves de API do runtime, precisamente porque ele precisa existir antes de qualquer plugin ser carregado (bootstrap de admin/CLI).
  • Sistema de arquivos persistente = PVC. No Helm chart, /data/apps e /data/plugins são montados como PVCs separados; perder qualquer um resulta em um runtime sem aplicações ou sem plugins customizados.
StoreBackendDonoCaminho / URLConteúdo
plugin-tursoProvedor Turso Database local/sync@buntime/plugin-tursoCaminho do BD local mais URL/token de sync opcionalCiclo de vida compartilhado de conexão/sync para consumidores de SQL durável
plugin-keyval@buntime/plugin-turso@buntime/plugin-keyvalTabelas kv_entries e kv_* relacionadas através do plugin-turso (veja tabelas do KeyVal)KV genérico (chaves compostas, TTL, versionstamps); serviço opcional para consumidores que explicitamente precisam de KV
filas do plugin-keyval@buntime/plugin-turso@buntime/plugin-keyvalTabelas kv_queue + kv_dlqFilas FIFO com locking, retry/backoff, DLQ
busca do plugin-keyval@buntime/plugin-turso@buntime/plugin-keyvalTabela kv_indexes + tabelas de busca regulares (kv_fts_<prefix>)Índices de busca por prefixo
métricas do plugin-keyval@buntime/plugin-turso@buntime/plugin-keyvalTabela kv_metrics quando metrics.persistent: trueContadores operations/errors/latency_sum
estado operacional do plugin-gateway@buntime/plugin-turso quando disponível@buntime/plugin-gatewayTabelas gateway_metrics_history e gateway_shell_excludes de propriedade do pluginHistórico de métricas e shell excludes dinâmicos. O Gateway continua funcionando sem estado durável quando o Turso está desabilitado
regras do plugin-proxy@buntime/plugin-turso@buntime/plugin-proxyTabela proxy_rules de propriedade do pluginRegras dinâmicas de redirect/proxy (regras estáticas residem em manifest.yaml). O Proxy mantém as regras estáticas disponíveis quando o Turso está desabilitado
plugin-vhosts@buntime/plugin-turso@buntime/plugin-vhostsArmazenamento de propriedade do pluginMapeamentos dinâmicos de host → aplicação/plugin
store de chaves de APITurso DB (@tursodatabase/database / @tursodatabase/sync) em disco@buntime/runtime${RUNTIME_STATE_DIR}/api-keys.db (Helm: /data/state/api-keys.db em um PVC RWO por pod). mode=local: arquivo standalone; mode=sync: réplica embutida sincronizada contra um primário de servidor TursoChaves com hash SHA-256 + papel + permissões; faz bootstrap do admin antes de qualquer plugin estar disponível; arquivos legados JSON e bun:sqlite migrados de forma transparente
Cache de configuração de workerEm memória (TTL configurável)worker pool do @buntime/runtimeRAM do processo do runtimeManifesto + configuração do worker; evita reler app.yaml a cada requisição
Cache do resolver de workerEm memória (TTL configurável)worker pool do @buntime/runtimeRAM do processo do runtimeResolução do diretório da aplicação (qual workerDir contém name@version)
Sistema de arquivos de aplicações (PVC)Sistema de arquivosRuntime + app install da CLI/cpanel/data/apps (Helm; workerDirs: /data/.apps:/data/apps)Bundles de aplicações enviados (workers): dist/, app.yaml, assets
Sistema de arquivos de plugins (PVC)Sistema de arquivosRuntime + plugin install da CLI/cpanel/data/plugins (Helm; pluginDirs: /data/.plugins:/data/plugins)Plugins enviados (os built-ins somente leitura permanecem em /data/.plugins da imagem; uploads graváveis permanecem em /data/plugins)

@buntime/plugin-turso é o provedor de SQL durável: um plugin de infraestrutura central que centraliza a configuração de conexão Turso, o ciclo de vida de sync, a configuração de MVCC e os helpers de retry de conflito de escrita. Os consumidores são donos de suas tabelas e fronteiras de schema:

ConsumidorÉ dono deUsa o plugin-turso para
plugin-keyvalschema kv_* e semântica KVConexão SQL durável, modo local/sync, helpers de transação/retry
plugin-gatewayschema gateway_* para histórico de métricas e shell excludes dinâmicosConexão SQL durável, modo local/sync, helpers de transação/retry
plugin-proxyschema proxy_rules para regras dinâmicasConexão SQL durável, modo local/sync, helpers de transação/retry

A razão é a independência de ciclo de vida: os operadores devem ser capazes de habilitar o gateway/proxy enquanto desabilitam o plugin KeyVal em ambientes menores ou especializados. plugin-turso não é um plugin de funcionalidade voltado ao usuário; é o provedor compartilhado de SQL durável. Os consumidores o obtêm através da API padrão de compartilhamento de serviços — o provedor expõe um serviço via provides, e os consumidores o recuperam com ctx.getPlugin<TursoService>("@buntime/plugin-turso").

Os modos recomendados do provedor são apenas Turso:

ModoDurabilidadeCaso de uso
localArquivo local durávelTestes locais e deployments de pod único
syncArquivo local durável mais sincronização remotaKubernetes e qualquer deployment com múltiplos pods ou risco de reinício/realocação
remoteSQL remoto sobre HTTPModo opcional futuro apenas se agregar valor

O Turso é preferido em relação ao bun:sqlite para o driver durável porque o Turso Database suporta MVCC e BEGIN CONCURRENT, permitindo que múltiplos escritores prossigam em paralelo com retry de conflito. Em contraste, o driver SQLite embutido do Bun encapsula o SQLite; o WAL do SQLite é bom para muitos leitores concorrentes mais um escritor, mas ainda serializa os escritores.

Não monte um único arquivo de banco compartilhado em múltiplos pods. As escritas concorrentes do Turso resolvem a concorrência de escritores no nível do engine; o Kubernetes ainda adiciona semântica de sistema de arquivos e de lock que depende do backend de armazenamento. Para Kubernetes, cada pod deve ter seu próprio arquivo de banco local e sincronizar através do Turso Sync.

Para Kubernetes auto-hospedado, sync e remote ambos requerem um endpoint Turso. Esse endpoint pode ser o Turso Cloud externo ou um pod/serviço Turso dentro do cluster.

Orientações de implementação:

  • Declare @buntime/plugin-turso como a dependência de armazenamento para plugin-keyval, plugin-gateway e plugin-proxy.
  • Mantenha os manifestos de plugin-gateway e plugin-proxy livres de dependências do KeyVal para seu próprio estado. Ambos os consumidores de borda usam o Turso diretamente.
  • Mantenha as APIs de domínio dentro de cada plugin consumidor. plugin-turso expõe primitivas de banco/transação/sync, não APIs de negócio de proxy/gateway/keyval.
  • Faça retry de conflitos de escrita do Turso em torno de transações BEGIN CONCURRENT.

O ApiKeyStore não é um store apoiado em plugin, porque ele precisa funcionar antes de qualquer plugin ser carregado — a chave raiz do runtime autentica worker install / plugin install antes de qualquer plugin (incluindo o plugin-turso) sequer ser carregado. Ele deve permanecer autocontido no bootstrap.

Backend: Turso DB (via @tursodatabase/database para modo local e @tursodatabase/sync para modo de réplica embutida/multi-pod). Os arquivos do Turso DB são binariamente compatíveis com SQLite — qualquer .db pré-existente (de deployments anteriores com bun:sqlite ou libsql) abre de forma transparente.

Schema: uma única tabela api_keys com dois índices parciais (idx_api_keys_lookup em key_hash e idx_api_keys_expiry em expires_at, ambos WHERE revoked_at IS NULL). As permissões são codificadas em JSON.

AspectoValor
BackendTurso DB (Rust, journal MVCC). Drivers: @tursodatabase/database (local), @tursodatabase/sync (réplica embutida).
Modoslocal (arquivo standalone, pod único, padrão). sync (réplica embutida sincronizada com um primário de servidor Turso, multi-pod).
HashSHA-256 do segredo completo
Caminho${RUNTIME_STATE_DIR}/api-keys.db (Helm: /data/state/api-keys.db em um PVC RWO por pod via os volumeClaimTemplates do StatefulSet).
GranularidadePapéis admin / editor / viewer / custom (veja o Runtime)
Chave raizVariável de ambiente RUNTIME_ROOT_KEY (Secret do Helm buntime.rootKey); principal sintético root; ignora CSRF e hooks de plugin; não reside no BD.
Multi-podVeja Deployment multi-pod. Quando tursoPrimary.enabled=true, o chart provisiona um StatefulSet de primário de servidor Turso e aponta o ApiKeyStore (e opcionalmente o plugin-turso) para ele.
LegadoAntes de 2026-05-20 o store usava JSON, depois brevemente bun:sqlite. Ambos são automigrados. O JSON é renomeado para *.migrated (backup defensivo).

Estes não são “stores” no sentido durável — eles desaparecem ao reiniciar. Mas governam o comportamento em produção e são ajustáveis via variáveis de ambiente:

CacheVariável de ambientePadrãoQuando desabilitar
Cache de configuração de workerRUNTIME_WORKER_CONFIG_CACHE_TTL_MS1000 msAplicações mutáveis em dev (defina como 0)
Cache do resolver de workerRUNTIME_WORKER_RESOLVER_CACHE_TTL_MS1000 msAplicações sendo (re)instaladas em loop
Concorrência efêmeraRUNTIME_EPHEMERAL_CONCURRENCY2Não é um cache, mas afeta workers ttl: 0 — veja performance
Limite da fila efêmeraRUNTIME_EPHEMERAL_QUEUE_LIMIT100Requisições em excesso recebem 503

TTL de cache 0 = sempre reler do disco, útil em dev. Em produção, o padrão de 1000 ms absorve picos sem manter dados obsoletos por muito tempo.

VolumeMountOrigemRW
/data/appsworkerDirs (segundo)PVCRW
/data/.appsworkerDirs (primeiro)Imagem DockerRO
/data/pluginspluginDirs (segundo)PVCRW
/data/.pluginspluginDirs (primeiro)Imagem DockerRO
/data/state/api-keys.dbstore de chaves de API (Turso DB)PVCRW

Quando o mesmo código roda localmente (sem Helm) e no Rancher/k3s, os caminhos dos stores diferem — útil para entender por que o bun dev vê um estado diferente do pod.

ConceitoDev local (bun dev)Helm (Rancher/k3s)
Plugins externos (RW)./plugins/ ou RUNTIME_PLUGIN_DIRS/data/plugins (PVC)
Plugins core (RO)Repositório (packages/plugin-* ou bundle)/data/.plugins (imagem)
Aplicações (RW)./apps-data/ ou RUNTIME_WORKER_DIRS/data/apps (PVC)
Aplicações embutidas (RO)/data/.apps (imagem, raramente usado)
Store de chaves de API./.buntime/api-keys.db ou ${RUNTIME_STATE_DIR}/api-keys.db/data/state/api-keys.db
Driver SQLTurso Database através do @buntime/plugin-tursoO chart do runtime expõe plugins.turso.*; o Kubernetes usa Turso Sync em vez de um arquivo de BD compartilhado

Veja charts/values.base.yaml (runtime.pluginDirs, runtime.workerDirs) para a fonte canônica dos caminhos de produção. Veja Helm e Kubernetes para os PVCs.

Ordem de prioridade para planejamento de DR:

  1. Estado SQL. O SQL durável usa Turso Database via @buntime/plugin-turso. Faça backup pelo mecanismo compatível com Turso do seu deployment (snapshot do arquivo local ou backup do servidor de Turso Sync).
  2. /data/state/api-keys.db. Sem isso, o acesso do operador é perdido. Em configurações multi-pod, use o modo sync (réplica embutida contra um primário de servidor Turso) em vez de compartilhar um único arquivo entre pods.
  3. /data/apps e /data/plugins. Podem ser reconstruídos via app install / plugin install se um registry/artefato estiver disponível; sem um, a perda significa recriar do zero.
  4. Caches em memória. Nenhum backup necessário — eles se reconstroem sob demanda.

Esta seção é a referência atual de schema para as tabelas que @buntime/plugin-keyval cria através do @buntime/plugin-turso. Comportamento, API REST e semântica de operações residem em o plugin KeyVal — esta seção foca em DDL e codificação.

initSchema(adapter) é chamado no onInit do plugin (plugins/plugin-keyval/server/lib/schema.ts) como um único adapter.batch([...]), criando seis tabelas mais índices auxiliares. Todas usam CREATE TABLE IF NOT EXISTS, então reinícios são idempotentes. O adaptador é TursoKeyValAdapter, uma camada de compatibilidade de propriedade do KeyVal sobre o TursoService.

TabelaPropósitoPersistenteNotas
kv_entriesEntradas KV (key/value/versionstamp/expires_at)SempreNúcleo do store
kv_queueFila FIFO ativa (pending/processing)SempreTravada por locked_until
kv_dlqDead-letter queueSempreSem limpeza automática
kv_metricsContadores agregadosQuando metrics.persistent: trueFlush periódico
kv_indexesMetadados de índice de buscaSempre que a busca estiver presentePrefixo, lista de campos, metadados do tokenizer
kv_fts_<prefix>Tabela de busca por prefixoQuando POST /api/indexes é chamadoTabela regular com doc_key e texto document normalizado
CREATE TABLE IF NOT EXISTS kv_entries (
key BLOB PRIMARY KEY,
value BLOB NOT NULL,
versionstamp TEXT NOT NULL,
expires_at INTEGER
);
CREATE INDEX IF NOT EXISTS idx_kv_expires
ON kv_entries(expires_at)
WHERE expires_at IS NOT NULL;
ColunaTipoConteúdo
keyBLOB (PK)Chave codificada em binário com prefixo de tipo, garantindo ordem lexicográfica Uint8Array < string < number < bigint < boolean
valueBLOBValor serializado (tipicamente JSON; pode ser binário)
versionstampTEXTHex monotônico — incrementa a cada set/atomic. Base para OCC
expires_atINTEGER nullableUnix epoch (s) quando a entrada expira; NULL = sem TTL

O índice parcial idx_kv_expires é o que torna a limpeza de TTL eficiente sem um full table scan.

Valores KvKey (arrays de KvKeyPart) são codificados em um único BLOB via codificação binária com prefixos de tipo:

["users", "123"] → BLOB(<str-tag>users<sep><str-tag>123)
["users", 42, "profile"] → BLOB(<str-tag>users<sep><num-tag>42<sep><str-tag>profile)

Isso possibilita:

  1. PRIMARY KEY direta — sem joins ou tabelas auxiliares.
  2. Range scans por prefixoWHERE key >= prefix AND key < prefix_upper_bound ordena lexicograficamente.
  3. Ordenação estável entre tipos (números antes de strings, etc.).

A função where-to-sql.ts traduz filtros como { "field": { "$eq": "value" } } para SQL usando json_extract(value, '$.field') — índices em nível de coluna existem apenas para expires_at.

CREATE TABLE IF NOT EXISTS kv_queue (
id TEXT PRIMARY KEY,
value BLOB NOT NULL,
ready_at INTEGER NOT NULL,
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 5,
backoff_schedule TEXT,
keys_if_undelivered TEXT,
status TEXT DEFAULT 'pending',
locked_until INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_queue_ready
ON kv_queue(status, ready_at) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_queue_locked
ON kv_queue(locked_until) WHERE status = 'processing';
ColunaConteúdo
idUUIDv7 da mensagem
valuePayload (BLOB / JSON serializado)
ready_atQuando a mensagem fica disponível (suporta delay)
attempts / max_attemptsContagem atual e teto (move para a DLQ quando atingido)
backoff_scheduleArray JSON [1000, 5000, 10000] (ms)
keys_if_undeliveredArray JSON de KvKey[] para fallback da DLQ
statuspending | processing
locked_untilUnix epoch (s) — quando o lock de dequeue expira

Os dois índices parciais cobrem os caminhos quentes: dequeue (status='pending' AND ready_at <= now) e limpeza de locks obsoletos (status='processing' AND locked_until < now).

CREATE TABLE IF NOT EXISTS kv_dlq (
id TEXT PRIMARY KEY,
original_id TEXT NOT NULL,
value BLOB NOT NULL,
error_message TEXT,
attempts INTEGER NOT NULL,
original_created_at INTEGER NOT NULL,
failed_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_dlq_failed_at ON kv_dlq(failed_at);

A DLQ é append-only. requeue move uma entrada de volta para kv_queue (com status='pending'); delete/purge a remove. Limpeza automática não existe — os operadores precisam de seu próprio job (veja troubleshooting em o plugin KeyVal).

CREATE TABLE IF NOT EXISTS kv_metrics (
id TEXT PRIMARY KEY,
operation TEXT NOT NULL,
count INTEGER NOT NULL DEFAULT 0,
errors INTEGER NOT NULL DEFAULT 0,
latency_sum REAL NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_metrics_operation ON kv_metrics(operation);

A tabela é sempre criada (DDL em initSchema), mas só populada quando metrics.persistent: true. A cadência de flush é controlada por metrics.flushInterval (padrão 30000 ms). Para deployments efêmeros, deixar isto como false e expor métricas via /api/metrics ou /api/metrics/prometheus (em memória) é suficiente.

CREATE TABLE IF NOT EXISTS kv_indexes (
prefix BLOB PRIMARY KEY,
fields TEXT NOT NULL,
tokenize TEXT DEFAULT 'unicode61',
created_at INTEGER NOT NULL
);

Cada linha em kv_indexes corresponde a uma tabela de busca regular criada dinamicamente quando o usuário chama POST /api/indexes:

CREATE TABLE IF NOT EXISTS kv_fts_<hash-of-prefix> (
doc_key TEXT PRIMARY KEY,
document TEXT NOT NULL
);

A coluna document armazena texto normalizado extraído dos campos configurados. A sincronização é automática para set/delete/atomic — nenhum reindex manual é necessário, a menos que o índice seja recriado.

TokenizerImplementação SQLite
unicode61Tokenizer padrão (multilíngue)
porterStemming em inglês
asciiASCII puro

plugin-proxy não armazena mais regras dinâmicas no KeyVal. O antigo prefixo ["proxy", "rules"] foi substituído pela tabela proxy_rules de propriedade do proxy através do plugin-turso.

As regras estáticas ainda residem em manifest.yaml e nunca tocam o KeyVal. As regras dinâmicas agora recebem UUIDs gerados e estão documentadas em o plugin Proxy.

  • plugin-turso — Provedor Turso Database para SQL durável.
  • plugin-keyval — Semântica KV (versionstamps, atomic, filas, FTS).
  • O Runtime — endpoints /api/keys/*, papéis, permissões, chave raiz.
  • Performance — ajustando os caches em memória.
  • Servidor Turso — executando um servidor de Turso Sync dentro do cluster.