Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Banco de dados e migrações

A maior parte do que esta stack persiste não está em banco de dados nenhum, e a maioria dos operadores nunca roda uma migração. Este capítulo diz quais serviços têm um banco, o que cada um guarda, e qual é o único passo de schema que existe, feito uma única vez — para que você descubra rápido se ele se aplica a você.

Qual serviço guarda o quê

O Mycelium, o gateway, é dono da identidade. Contas, tenants, subscriptions, papéis de convidado e os tokens de magic link por trás do login vivem todos aqui. Onde depende do modo de deploy. No standalone é SQLite: deploy/standalone/config.standalone.toml define [sqlite] path = "/data/mycelium.db", e docker-compose.yaml monta o volume nomeado mycelium-data em /data. No prod é Postgres: o overlay acrescenta um serviço mycelium-postgres sobre postgres:16-alpine, apoiado no volume mycelium-postgres-data, e aponta o gateway para ele por MYC_BASE_DATABASE_URL.

O chat-webapp é dono da lista de conversas. O chat-webapp-postgres, também postgres:16-alpine, no volume chat-webapp-postgres-data, guarda um conjunto pequeno de tabelas: conversations (id, e-mail do dono, agente, título, ids de workspace, arquivo de sessão, projeto), conversation_tags, e uma tabela branding de uma linha só para o nome do app e os logos. É deliberadamente um banco separado do banco do Mycelium, com um ciclo de vida diferente; o arquivo compose defende esse ponto na definição do serviço.

O crab-shell-proxy é dono de um pequeno armazenamento chave-valor. No boot ele abre model-registry.db sob a raiz de dados do seu container (crab/crab-shell-proxy/cmd/crab-shell-proxy/main.go). Isso é um arquivo bbolt, não SQL: o Open de internal/registry/registry.go cria cada bucket de que precisa se estiver faltando e carrega o seu próprio marcador schema_version com uma migração no boot. Ele não precisa de nada de você.

Tudo o que um agente produz são arquivos, não linhas de tabela. Transcrições, arquivos de memória, projetos, skills, anexos entregues e os .schedules.json/.projects.json que pertencem ao proxy vivem todos em disco sob a raiz de dados, na árvore de tenants descrita em Agentes, workspaces e projetos. É por isso que um reset é um rm -rf e não um DROP.

O deploy/prod/config.base.toml também traz um bloco [redis], com hostname = "mycelium-redis" e password = "unused-no-redis-container-in-this-stack". O formato de configuração exige que as chaves existam; nenhum container Redis roda nesta stack. Não saia procurando por ele.

Quando você precisa fazer alguma coisa

Três desses quatro não precisam de nenhuma ação.

O adaptador SQLite do Mycelium carrega migrações embutidas — o deploy/prod/config.base.toml diz isso onde explica que o de Postgres não carrega — então o standalone funciona a partir de um checkout limpo, sem nenhum passo de schema.

O chat-webapp cria o seu próprio schema de forma preguiçosa, em tempo de execução, na primeira consulta. O crab/crab-exoskeleton-webapp/lib/db.ts tem uma função ensureSchema() que emite comandos CREATE TABLE IF NOT EXISTS e ALTER TABLE ... ADD COLUMN IF NOT EXISTS uma vez por processo e memoiza a promise. Todo comando é idempotente, e colunas novas são adicionadas de forma aditiva, então as linhas que já existiam sobrevivem. Não há ferramenta de migração, nem diretório de migrações, nem comando para rodar.

O registro de modelos se inicializa sozinho, como visto acima.

Sobra exatamente um caso: o backend Postgres do Mycelium, em prod, uma vez, depois do primeiro up.

O passo de schema único, só em prod

O adaptador Postgres do Mycelium não tem migrações embutidas, diferente do de SQLite. O deploy/prod/config.base.toml registra isso logo acima do seu bloco [diesel], e o docker-compose.prod.yaml repete no cabeçalho do arquivo. Enquanto o schema não for aplicado, o gateway tem um banco que não consegue usar, então ninguém consegue entrar.

São dois passos, nesta ordem: o up.sql do upstream, e depois os scripts de migração que o up.sql não incorpora. Os dois vêm do repositório do mycelium na mesma release que este deploy fixa.

git clone --depth 1 --branch 9.0.0-rc.13 \
  https://github.com/LepistaBioinformatics/mycelium.git /tmp/myc
cd /path/to/zombie-crab-project && set -a; . ./.env; set +a

Carregar o .env é o que coloca MYC_DB_USER, MYC_DB_NAME e MYC_DB_PASSWORD no seu shell; esses são os mesmos três nomes que o docker-compose.prod.yaml entrega ao serviço mycelium-postgres, então eles vão bater com o que você definiu em deploy/prod/.env.example.

Passo 1 — o schema base.

docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec -T mycelium-postgres \
  psql -U "$MYC_DB_USER" -d postgres \
       -v db_name="$MYC_DB_NAME" -v db_user="$MYC_DB_USER" \
       -v db_password="$MYC_DB_PASSWORD" -v db_role=service-role-mycelium \
  < /tmp/myc/adapters/diesel_postgres/sql/up.sql

A conexão mira o banco de manutenção postgres porque um banco não pode ser criado de dentro de si mesmo. O README.md da raiz descreve o que o script faz em seguida — criar o banco da aplicação se ele estiver faltando, entrar nele, e criar os papéis e as tabelas — e registra que ele exige -v db_password. As flags -v são variáveis do psql que o script substitui dentro do seu próprio SQL.

O Compose já criou o banco e o papel de login quando você roda isso, porque o serviço mycelium-postgres declara POSTGRES_DB e POSTGRES_USER. Então CREATE USER ... already exists é saída esperada aqui, não uma falha: o psql imprime a mensagem e segue em frente.

Passo 2 — as migrações, em ordem de nome de arquivo.

for m in /tmp/myc/adapters/diesel_postgres/sql/migrations/*.sql; do
  docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec -T mycelium-postgres \
    psql -U "$MYC_DB_USER" -d "$MYC_DB_NAME" < "$m"
done

O glob expande em ordem lexical, que é a ordem que esses scripts esperam. Note que o -d aqui é o banco da aplicação, não postgres.

Este passo não é opcional na 9.0.0-rc.13. As notas do próprio repositório — o deploy/prod/config.base.toml acima de [diesel], e o README.md da raiz — registram que o up.sql nessa tag traz kv_artifact e o índice de claim de message_queue, mas não traz instance_settings, resource_audit_log, nem as colunas tenant.encrypted_dek e kek_version de que a criptografia por envelope precisa. Essas só existem como scripts de migração.

Para conferir o resultado, conecte e olhe:

docker compose -f docker-compose.yaml -f docker-compose.prod.yaml exec mycelium-postgres \
  psql -U "$MYC_DB_USER" -d "$MYC_DB_NAME"

\dt deve listar instance_settings e resource_audit_log; \d tenant deve mostrar encrypted_dek e kek_version.

Os dois comandos acima são os que o README.md da raiz dá, e o nome do serviço, os nomes de variável MYC_DB_* e a cadeia de arquivos compose neles batem todos com este checkout. O que este repositório não consegue verificar por conta própria é tudo o que está dentro do clone do mycelium: os caminhos adapters/diesel_postgres/sql/up.sql e adapters/diesel_postgres/sql/migrations/, o valor db_role=service-role-mycelium, o que o up.sql faz quando roda, e quais tabelas o up.sql daquela tag cria e não cria. Tudo isso vem do repositório upstream. Se um caminho mudou de lugar em uma release posterior, leia o clone em vez desta página.

Em um deploy com Postgres que não é conduzido pelo arquivo compose deste repositório, rode os mesmos dois passos com docker exec direto contra o container mycelium-postgres. Nada no SQL muda; só como você chega no psql.

O que um reset mexe e o que ele não mexe

O reset descrito em Deploy remove diretórios sob data/. Ele não toca nos volumes nomeados, então tanto as contas e os papéis do Mycelium quanto a lista de conversas do chat-webapp sobrevivem a ele, e o seu login continua funcionando. Isso é deliberado: apagar o estado dos agentes é uma ação rotineira de desenvolvimento, e perder a sua conta Staff toda vez tornaria isso bem menos rotineiro.

Acrescentar -v ao docker compose down remove os volumes também. No standalone isso apaga o mycelium.db, então você teria de refazer o bootstrap da conta Staff do zero. No prod isso apaga o diretório de dados do Postgres, então você teria de refazer também os dois passos de schema acima.

Para onde ir agora

Deploy cobre os modos a que estes bancos pertencem, e Solução de problemas cobre como um schema faltando ou um volume apagado realmente aparecem no cliente de chat.