Ir para o conteúdo
decodecodeveloper docs
Studio → Deploy e hospedagem própria do Studio

Monitoramento

Como persistir e consultar dados de monitoramento em deployments self-hosted

Como o monitoramento funciona

O deco Studio inclui um exporter do OpenTelemetry que grava arquivos NDJSON no diretório DATA_DIR. Os dados são organizados em três subdiretórios:

  • /metrics — métricas de sistema e da aplicação
  • /logs — entradas de log estruturadas para cada invocação de ferramenta
  • /traces — traces distribuídos das operações MCP

Os arquivos são particionados por organização com a seguinte estrutura de caminho:

{DATA_DIR}/{type}/{org_id}/YYYY/MM/DD/HH/{uuid}.ndjson

Uma política de retenção de 30 dias com limpeza automática mantém o uso de disco sob controle.

Persistindo dados de monitoramento (sidecar S3)

Por padrão, os dados de monitoramento ficam no sistema de arquivos local. Para sobreviver a reinicializações de container e habilitar armazenamento centralizado, configure um processo sidecar que sincronize periodicamente o DATA_DIR com o S3:

# Example: sync monitoring data to S3 every 5 minutes
aws s3 sync $DATA_DIR/metrics s3://your-bucket/metrics
aws s3 sync $DATA_DIR/logs s3://your-bucket/logs
aws s3 sync $DATA_DIR/traces s3://your-bucket/traces

No Kubernetes, execute isso como um container sidecar compartilhando um volume com o container principal do deco Studio.

Opção A: ClickHouse (recomendado)

Para deployments em produção, o ClickHouse fornece agregações rápidas e escaláveis sobre os dados de monitoramento.

  1. Aponte um collector OTLP para o ClickHouse. O Studio emite a telemetria de monitoramento como log records OpenTelemetry padrão com atributos studio.monitoring.*, que o exporter grava na tabela otel_logs — criada por ele na primeira escrita.
  2. Crie a view studio_monitoring_logs sobre otel_logs. O dashboard lê essa view, não uma tabela, e provisioná-la é uma etapa única que a aplicação nunca executa sozinha. O DDL e as ressalvas de nomes de coluna estão em apps/api/src/monitoring/clickhouse-setup.md.
  3. Defina CLICKHOUSE_URL com o endpoint HTTP do seu ClickHouse. Acesso de leitura basta — o Studio apenas consulta.

Faça o deploy antes de provisionar a view, não o contrário: ela é definida sobre uma tabela que o collector cria na primeira escrita. Até a view existir, o dashboard falha em toda consulta — esperado, e resolve assim que você a cria. Não existe tabela separada de métricas: contagens, médias e percentis derivam das mesmas linhas de log.

CLICKHOUSE_URL=https://your-clickhouse-instance:8123

ClickHouse é a melhor escolha para produção: lida com grandes volumes de forma eficiente, suporta agregações rápidas e possui integração nativa com S3 para carregar arquivos NDJSON.

Opção B: DuckDB + S3

Para deployments menores que querem evitar executar um banco de dados separado, você pode usar DuckDB com armazenamento montado via S3.

  1. Monte seu bucket S3 como um sistema de arquivos local usando uma ferramenta como s3fs, goofys ou mountpoint-s3.
  2. Defina DATA_DIR para o caminho montado.
  3. O deco Studio grava arquivos NDJSON diretamente no mount, e o engine DuckDB embutido lê do mesmo caminho.
# Mount S3 bucket
mountpoint-s3 your-bucket /mnt/monitoring
 
# Point DATA_DIR to the mount
DATA_DIR=/mnt/monitoring

Não é necessário definir CLICKHOUSE_URL — o DuckDB consulta os arquivos NDJSON em disco.

Opção C: Google Cloud Storage (OTLP via collector)

Para deployments self-hosted no GCP que querem sem ClickHouse e sem disco/sidecar, o Studio pode ler seus dados de monitoramento diretamente de um bucket no GCS. O Studio já emite os dados de monitoramento como logs OTLP padrão pela rede; você os aponta para um OpenTelemetry Collector que os grava no GCS com o exporter google_cloud_storage, e o engine DuckDB embutido os lê de volta pelo endpoint compatível com S3 do GCS.

Studio ──logs OTLP──▶ OTel Collector ──exporter google_cloud_storage──▶ gs://bucket/<prefix>/...
                              (OTLP JSON, cliente nativo GCS)                  ▲
                                            DuckDB embutido lê + achata na consulta

O collector escreve com uma service account do Google (cliente nativo do GCS — sem assinatura S3). O Studio lê via httpfs do DuckDB, que fala a API compatível com S3 do GCS, então precisa de uma chave HMAC. Ambos podem usar a mesma service account.

Não use o exporter awss3 para GCS. Os checksums de request default do AWS SDK v2 são rejeitados pelo GCS (SignatureDoesNotMatch), e o workaround via env não tem efeito dentro desse exporter. Use o google_cloud_storage.

Começando do zero (ainda sem bucket)? Os passos abaixo usam a CLI gcloud — defina PROJECT_ID e um BUCKET globalmente único primeiro.

1. Crie o bucket. (Pré-crie — o exporter reaproveita ele, veja o passo 5.)

gcloud storage buckets create "gs://${BUCKET}" \
  --project="${PROJECT_ID}" --location=us --uniform-bucket-level-access

2. Crie uma service account e conceda acesso ao bucket. O storage.admin no escopo do bucket cobre o que o exporter precisa (escrita de objetos e storage.buckets.get, que o reuse_if_exists chama). Não precisa de buckets.create no projeto.

gcloud iam service-accounts create studio-monitoring --project="${PROJECT_ID}"
SA="studio-monitoring@${PROJECT_ID}.iam.gserviceaccount.com"
 
gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \
  --member="serviceAccount:${SA}" --role="roles/storage.admin"

No GKE, vincule essa SA à Kubernetes SA do collector via Workload Identity (sem arquivo de key). Fora do GKE, crie uma key (gcloud iam service-accounts keys create key.json --iam-account="${SA}") e monte com GOOGLE_APPLICATION_CREDENTIALS.

3. Crie uma chave HMAC para a leitura do Studio. O DuckDB lê via API compatível com S3, que precisa de uma chave HMAC na mesma SA:

gcloud storage hmac keys create "${SA}" --project="${PROJECT_ID}"
#  → accessId  = MONITORING_S3_ACCESS_KEY_ID  (GOOG1E...)
#  → secret    = MONITORING_S3_SECRET_ACCESS_KEY   (exibido só uma vez)

4. Envie os logs de monitoramento do Studio para o seu collector. Defina MONITORING_OTLP_ENDPOINT (ou habilite o collector no cluster) para que o Studio exporte os logs OTLP para ele.

5. Configure o collector para gravar OTLP-JSON no GCS. Adicione o exporter google_cloud_storage ao pipeline de logs do collector. Ele serializa em OTLP JSON por padrão — exatamente o que o dashboard lê.

processors:
  batch:
    # Um objeto é gravado por flush. Mantenha os batches limitados para cada
    # arquivo ficar bem abaixo do limite de 32 MiB por arquivo do leitor, e para
    # limitar quantos objetos cada query do dashboard varre. (send_batch_max_size
    # precisa ser >= send_batch_size.)
    send_batch_size: 2048
    send_batch_max_size: 2048
    timeout: 60s
exporters:
  google_cloud_storage:
    bucket:
      name: your-bucket
      project_id: your-project   # auto-detectado no GKE; obrigatório fora do GCP
      region: us
      reuse_if_exists: true      # usa o bucket pré-criado; necessário p/ sobreviver a restart
      file_prefix: logs
      partition:
        prefix: logs             # o prefixo de leitura (precisa bater com MONITORING_S3_PREFIX)
        format: "year=%Y/month=%m/day=%d/hour=%H"
service:
  pipelines:
    logs:
      processors: [batch]
      exporters: [google_cloud_storage]

reuse_if_exists: true é obrigatório. Com o default (false) o exporter tenta criar o bucket a cada startup e falha com 409 Conflict quando ele já existe — então o collector não reinicia.

O leitor limita um arquivo a 32 MiB; arquivos maiores são ignorados. Os ajustes de batch acima mantêm cada objeto bem abaixo disso — limite o send_batch_max_size se os inputs das suas ferramentas forem grandes. (Arquivos menores e em menor quantidade também deixam cada query do dashboard mais barata.)

6. Aponte o leitor do Studio para o mesmo bucket:

MONITORING_S3_BUCKET=your-bucket
MONITORING_S3_PREFIX=logs       # bate com o partition.prefix do collector
MONITORING_S3_ENDPOINT=https://storage.googleapis.com
MONITORING_S3_ACCESS_KEY_ID=<hmac-key>
MONITORING_S3_SECRET_ACCESS_KEY=<hmac-secret>

Quando MONITORING_S3_BUCKET está definida (e CLICKHOUSE_URL não), o dashboard lê os arquivos OTLP-JSON do bucket. As métricas (chamadas, erros, percentis de latência) são derivadas dessas mesmas linhas de log, então não há um armazenamento de métricas separado. A extensão httpfs que o DuckDB precisa já vem embutida na imagem oficial, então isso funciona mesmo com políticas restritas de rede de saída.

7. Verifique. Faça algumas chamadas de ferramenta pelo Studio, depois confirme que os arquivos chegaram (o collector faz flush no timeout do batch) e que o dashboard popula:

gcloud storage ls --recursive "gs://${BUCKET}/logs/" | head

Retenção (recomendado). Quando o bucket usa o layout de partição year=/month=/day= acima, cada query do dashboard poda a leitura para apenas as partições de dia cobertas pelo intervalo de datas selecionado — não varre mais o prefixo inteiro. A detecção de schema ainda lista os objetos sob o prefixo, então uma regra de ciclo de vida no bucket continua sendo a forma prática de limitar esse custo de listagem conforme o histórico cresce. O Studio não aplica retenção sozinho — adicione uma regra pra deletar objetos automaticamente, ex. após 30 dias:

echo '{"rule":[{"action":{"type":"Delete"},"condition":{"age":30}}]}' > /tmp/lifecycle.json
gcloud storage buckets update "gs://${BUCKET}" --lifecycle-file=/tmp/lifecycle.json

A exportação OTLP limita o payload de output de cada chamada de ferramenta em 8 KB (igual ao caminho do ClickHouse hospedado). Os inputs das ferramentas e todas as análises (contagens, taxa de erro, latência) não são afetados; apenas corpos de resposta muito grandes são cortados no inspetor de chamadas.

Solução de problemas

  • Collector não inicia / 409 Conflict no bucket: defina bucket.reuse_if_exists: true e pré-crie o bucket (passo 1).
  • 403 storage.buckets.get denied no startup: a service account do collector precisa de storage.admin no escopo do bucket (ou ao menos storage.buckets.get + escrita de objetos) — veja o passo 2.
  • Dashboard vazio / "Monitoring stats unavailable": confirme que os objetos existem com gcloud storage ls "gs://${BUCKET}/logs/"; e confirme que MONITORING_S3_PREFIX bate exatamente com o partition.prefix do collector.
  • Studio falha ao iniciar com erro de config: quando MONITORING_S3_BUCKET está definida, a access key e o secret HMAC são obrigatórios (o diretório de extensão do DuckDB já vem embutido na imagem oficial).
  • Malformed JSON in file …: um objeto truncado/corrompido sob as partições year=/… falha a leitura inteira — o DuckDB não consegue ignorar erros de parse em JSON de objeto único. Delete o objeto citado no erro (normalmente uma escrita parcial de um collector que caiu); uma regra de retenção evita acúmulo. Objetos fora do layout year=/month=/day= (ex. dumps antigos na raiz do prefixo, ou um esquema de partição antigo) são ignorados automaticamente — a query só lê year=*/….
  • Out of Memory Error em queryMetricTimeseries / queryLlmUsageStats: o OOM vem de achatar o prefixo inteiro numa query. Com o layout de partição year=/month=/day= acima, as queries podam o scan de dados para as partições de dia do intervalo do dashboard, o que evita o problema — então primeiro confirme que o collector escreve esse layout (gcloud storage ls deve mostrar caminhos year=…/month=…/day=…/) e estreite o intervalo de datas. Se ainda estourar num container pequeno, reduza o paralelismo com DUCKDB_THREADS (ex. 2) — menos threads reduz o pico de memória. Note que DUCKDB_MEMORY_LIMIT usa ~80% da RAM do container por padrão e não pode exceder a memória física, então aumentá-lo além do que o container tem não ajuda (e reduzi-lo só faz estourar mais cedo) — dê mais memória ao container. Defina também uma regra de retenção/lifecycle no bucket.

Variáveis de ambiente

VariávelPadrãoDescrição
DATA_DIR~/decoDiretório base para os arquivos NDJSON de monitoramento
CLICKHOUSE_URL(não definida)Endpoint HTTP do ClickHouse. Quando definida, a UI de monitoramento usa ClickHouse em vez de DuckDB
MONITORING_OTLP_ENDPOINT(usa OTEL_EXPORTER_OTLP_ENDPOINT como fallback)Endpoint OTLP para onde o Studio exporta os logs de monitoramento (seu collector)
MONITORING_S3_BUCKET(não definida)Bucket no GCS com os logs OTLP-JSON. Quando definida (e CLICKHOUSE_URL não), o dashboard lê deste bucket via DuckDB
MONITORING_S3_PREFIX(nenhum)Prefixo de chave dentro do bucket (igual ao s3_prefix do collector)
MONITORING_S3_ENDPOINTusa S3_ENDPOINT como fallbackEndpoint compatível com S3, ex. https://storage.googleapis.com
MONITORING_S3_REGIONusa S3_REGION como fallbackRegião para assinatura SigV4 (auto para GCS)
MONITORING_S3_ACCESS_KEY_IDusa S3_ACCESS_KEY_ID como fallbackChave HMAC do GCS
MONITORING_S3_SECRET_ACCESS_KEYusa S3_SECRET_ACCESS_KEY como fallbackSegredo HMAC do GCS
DUCKDB_MEMORY_LIMIT(80% da RAM)Limite de memória do engine DuckDB embutido, ex. 2GB. Reduza em containers com pouca memória
DUCKDB_THREADS(todas as CPUs)Número de threads do engine DuckDB embutido. Menos threads reduz o pico de memória