BigQuery tem três camadas de recurso Terraform: dataset (container lógico), table (dados) e reservation (slots de compute). IAM é aplicado no nível do dataset ou da tabela individualmente.
Dataset
resource "google_bigquery_dataset" "raw" {
dataset_id = "${replace(local.prefixo, "-", "_")}_raw"
location = var.regiao
description = "Dados brutos ingeridos sem transformação"
labels = local.labels
delete_contents_on_destroy = var.ambiente == "dev" ? true : false
default_table_expiration_ms = var.ambiente == "dev" ? 2592000000 : null # 30d em dev
depends_on = [google_project_service.apis]
}dataset_id aceita apenas letras, números e underscores. Hífens no prefixo precisam ser substituídos com replace.
delete_contents_on_destroy: quando false, o terraform destroy falha se o dataset tiver tabelas. É um guard contra destruição acidental. Use true em dev para facilitar o ciclo de testes e false em prod.
default_table_expiration_ms: tabelas criadas no dataset expiram automaticamente após N milissegundos. Útil em dev para limpeza automática; nunca use em prod para dados persistentes.
Tabela com schema
resource "google_bigquery_table" "pedidos" {
dataset_id = google_bigquery_dataset.raw.dataset_id
table_id = "pedidos"
labels = local.labels
schema = file("${path.module}/schemas/pedidos.json")
time_partitioning {
type = "DAY" # DAY, HOUR, MONTH, YEAR
field = "data_pedido" # coluna DATE ou TIMESTAMP; null = _PARTITIONTIME (ingest time)
}
clustering = ["regiao", "status"] # até 4 colunas; ordem importa para pruning
deletion_protection = var.ambiente == "prod" ? true : false
}// schemas/pedidos.json
[
{ "name": "id", "type": "STRING", "mode": "REQUIRED" },
{ "name": "data_pedido", "type": "TIMESTAMP", "mode": "REQUIRED" },
{ "name": "regiao", "type": "STRING", "mode": "NULLABLE" },
{ "name": "status", "type": "STRING", "mode": "NULLABLE" },
{ "name": "valor", "type": "NUMERIC", "mode": "NULLABLE" }
]Tipos de coluna
| Tipo | Equivalente SQL | Observação |
|---|---|---|
STRING | VARCHAR | UTF-8, sem limite de tamanho |
INTEGER | BIGINT | 64 bits com sinal |
FLOAT | FLOAT64 | Ponto flutuante; use NUMERIC para finanças |
NUMERIC | DECIMAL(29,9) | Precisão fixa; ideal para valores monetários |
BIGNUMERIC | DECIMAL(76,38) | Para cálculos de alta precisão |
BOOLEAN | BOOL | |
TIMESTAMP | TIMESTAMP WITH TIME ZONE | UTC internamente |
DATE | DATE | Sem hora |
RECORD | STRUCT | Tipo aninhado (objeto JSON) |
Modos: REQUIRED (NOT NULL), NULLABLE (padrão), REPEATED (equivalente a array).
time_partitioning.type
| Valor | Partição por | Quando usar |
|---|---|---|
HOUR | Hora | Streams de alta frequência (Pub/Sub, Dataflow) |
DAY | Dia | Maioria dos casos (ingestão batch, eventos diários) |
MONTH | Mês | Tabelas consultadas sempre com filtro mensal |
YEAR | Ano | Histórico com acesso muito esparso |
Sempre filtre pela coluna de partição no WHERE. Queries sem filtro de partição varrem a tabela inteira, gerando custo e lentidão proporcionais ao volume total.
deletion_protection
Impede que terraform destroy delete a tabela. Para destruir intencionalmente, é necessário setar deletion_protection = false, aplicar, e então destruir. Use true em prod como padrão.
IAM no dataset
resource "google_bigquery_dataset_iam_binding" "readers" {
dataset_id = google_bigquery_dataset.raw.dataset_id
role = "roles/bigquery.dataViewer"
members = [
"serviceAccount:${google_service_account.pipeline.email}",
"group:[email protected]",
]
}Roles principais do BigQuery:
| Role | O que permite |
|---|---|
roles/bigquery.dataViewer | SELECT em tabelas do dataset |
roles/bigquery.dataEditor | SELECT + INSERT + UPDATE + DELETE |
roles/bigquery.dataOwner | Controle total do dataset (incluindo delete) |
roles/bigquery.jobUser | Executar jobs (queries) no projeto |
roles/bigquery.user | jobUser + acesso a metadados do projeto |
jobUser e dataViewer são complementares e precisam ser concedidos separadamente. Uma SA que lê dados de outro projeto precisa de dataViewer no projeto do dataset e jobUser no projeto que executa a query.
Reserva de slots (Capacity Edition)
Em prod com carga previsível, comprometer slots via reservation reduz custo em relação ao on-demand.
# Commitment: compra slots por um período (lock-in com desconto)
resource "google_bigquery_capacity_commitment" "slots" {
count = var.ambiente == "prod" ? 1 : 0
location = var.regiao
plan = "MONTHLY" # FLEX (60s), MONTHLY (30d), ANNUAL (365d)
slot_count = 100
edition = var.bigquery_edition # STANDARD, ENTERPRISE, ENTERPRISE_PLUS
}
# Reservation: agrupa slots para alocar a projetos específicos
resource "google_bigquery_reservation" "principal" {
count = var.ambiente == "prod" ? 1 : 0
name = "${local.prefixo}-reserva"
location = var.regiao
slot_capacity = 100
edition = var.bigquery_edition
}
# Assignment: vincula a reservation a um projeto
resource "google_bigquery_reservation_assignment" "projeto" {
count = var.ambiente == "prod" ? 1 : 0
assignee = "projects/${var.projeto_id}"
job_type = "QUERY" # QUERY, PIPELINE (Dataflow), ML_EXTERNAL
reservation = google_bigquery_reservation.principal[0].id
}Edições e diferenças
| Edição | Destaques | Mínimo de slots |
|---|---|---|
STANDARD | Sem BI Engine incluso, sem CMEK multi-region | 100 |
ENTERPRISE | BI Engine incluso, CMEK, time travel até 7d | 100 |
ENTERPRISE_PLUS | Tudo do Enterprise + failsafe (14d de recuperação) | 100 |
Planos de commitment
| Plano | Lock-in | Desconto aproximado vs on-demand |
|---|---|---|
FLEX | 60 segundos | Nenhum |
MONTHLY | 30 dias | ~20% |
ANNUAL | 365 dias | ~40% |
Em dev nunca use commitment. Em prod com carga estável, ANNUAL paga em ~7 meses o custo equivalente em on-demand.
Habilitando as APIs necessárias
resource "google_project_service" "bigquery_apis" {
for_each = toset([
"bigquery.googleapis.com",
"bigqueryreservation.googleapis.com", # para slots e reservations
"bigqueryconnection.googleapis.com", # para conexões externas (Cloud SQL, GCS via BQML)
"bigquerystorage.googleapis.com", # para Storage Read API (leitura rápida via gRPC)
])
service = each.value
disable_on_destroy = false
}Quotas e limites relevantes
- Queries simultâneas por projeto (on-demand): 300
- Queries simultâneas com reservation: limitado pelos slots contratados (1 slot sustenta ~1 query simples; queries complexas usam centenas)
- DML (UPDATE/DELETE/MERGE) por tabela por dia: 1.500 para tabelas particionadas, 48 para não-particionadas
- Ingestão via streaming: 1 GB/s por projeto; 10 MB por linha; 50.000 linhas por segundo por tabela
CREATE TABLEpor dia por projeto: 10.000- Datasets por projeto: 10.000
- Tabelas por dataset: 1.000.000 (listagem fica lenta acima de alguns milhares)
- Colunas por tabela: 10.000
- Retenção de metadados (INFORMATION_SCHEMA.JOBS): 180 dias
Tabelas não-particionadas com DML intenso atingem o limite de 48 operações/dia rapidamente. Toda tabela que recebe UPDATE, DELETE ou MERGE recorrente deve ser particionada.
Conexões
Ver também: gcp-bigquery | gcp-bigquery-cobranca | gcp-bigquery-otimizacao | terraform-gcp-ambientes | terraform-gcp-buckets | terraform-gcp-looker | terraform-cloud-gcp