Firestore é o banco de dados de documentos NoSQL serverless do GCP. Organiza dados em coleções e documentos JSON, com sincronização em tempo real, escalabilidade automática e sem servidor para gerenciar.

É o sucessor do Cloud Datastore e serve aplicações mobile, web e backend que precisam de esquema flexível ou sincronização ao vivo.

Modos de operação

ModoQuando usar
Native modeNovos projetos: real-time listeners, SDKs Firebase, subcoleções, richer queries
Datastore modeMigração de projetos legados que usavam Cloud Datastore

Os dois modos são mutuamente exclusivos por banco. Para projetos novos, use sempre Native mode.

Modelo de dados

Database
  └── Collection ("usuarios")
        └── Document ("user-123")
              ├── Fields: string, number, boolean, timestamp, geopoint, bytes
              ├── Fields: map (objeto aninhado), array
              └── Subcollection ("pedidos")
                    └── Document ("pedido-456")

Documentos têm limite de 1 MB. Subcoleções permitem hierarquia sem limite de profundidade.

{
  "nome": "Logan",
  "email": "[email protected]",
  "criadoEm": "2026-01-15T10:00:00Z",
  "endereco": {
    "cidade": "São Paulo",
    "estado": "SP"
  },
  "tags": ["admin", "ativo"],
  "score": 4.8
}

Operações básicas

from google.cloud import firestore
 
db = firestore.Client(project="meu-projeto")
 
# Criar / sobrescrever documento completo
db.collection("usuarios").document("user-123").set({
    "nome": "Logan",
    "email": "[email protected]",
    "criadoEm": firestore.SERVER_TIMESTAMP
})
 
# Atualizar campos específicos (sem sobrescrever o resto)
db.collection("usuarios").document("user-123").update({
    "nome": "Logan M.",
    "endereco.cidade": "Campinas"  # notação de ponto para campos aninhados
})
 
# Adicionar documento com ID gerado automaticamente
ref = db.collection("eventos").add({"tipo": "login", "ts": firestore.SERVER_TIMESTAMP})
print(ref[1].id)  # ID gerado
 
# Ler documento
doc = db.collection("usuarios").document("user-123").get()
if doc.exists:
    dados = doc.to_dict()
 
# Deletar
db.collection("usuarios").document("user-123").delete()

Queries

Firestore não suporta JOINs. Queries operam em uma única coleção ou em um collection group.

# Filtros simples (requires index para combinações)
usuarios = db.collection("usuarios") \
    .where(filter=firestore.FieldFilter("endereco.estado", "==", "SP")) \
    .where(filter=firestore.FieldFilter("ativo", "==", True)) \
    .order_by("nome") \
    .limit(50) \
    .stream()
 
for u in usuarios:
    print(u.id, u.to_dict()["nome"])
 
# Collection group: busca em todas as subcoleções com o mesmo nome
todos_pedidos = db.collection_group("pedidos") \
    .where(filter=firestore.FieldFilter("status", "==", "PENDENTE")) \
    .stream()
 
# Operadores de array
db.collection("usuarios") \
    .where(filter=firestore.FieldFilter("tags", "array_contains", "admin")) \
    .stream()
 
# Paginação com cursor
ultimo = None  # guarda o último snapshot da página anterior
pagina = db.collection("usuarios") \
    .order_by("nome") \
    .start_after(ultimo) \
    .limit(20) \
    .stream()

Índices

Firestore cria índices single-field automaticamente. Para queries com múltiplos filtros ou order_by em campos diferentes, são necessários índices compostos:

# Criar índice composto via gcloud
gcloud firestore indexes composite create \
  --collection-group=usuarios \
  --field-config=field-path=estado,order=ASCENDING \
  --field-config=field-path=criadoEm,order=DESCENDING \
  --database="(default)"

Ou declarar em firestore.indexes.json e fazer deploy via Firebase CLI:

{
  "indexes": [
    {
      "collectionGroup": "usuarios",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "estado", "order": "ASCENDING" },
        { "fieldPath": "criadoEm", "order": "DESCENDING" }
      ]
    }
  ]
}

Transações e batch writes

# Transação (leitura + escrita atômica, com retry automático)
transaction = db.transaction()
 
@firestore.transactional
def transferir(transaction, origem_ref, destino_ref, valor):
    origem = origem_ref.get(transaction=transaction).to_dict()
    destino = destino_ref.get(transaction=transaction).to_dict()
    if origem["saldo"] < valor:
        raise ValueError("Saldo insuficiente")
    transaction.update(origem_ref, {"saldo": firestore.Increment(-valor)})
    transaction.update(destino_ref, {"saldo": firestore.Increment(valor)})
 
transferir(transaction,
           db.document("contas/A"),
           db.document("contas/B"),
           100)
 
# Batch write (escritas atômicas sem leitura, até 500 operações)
batch = db.batch()
batch.set(db.collection("logs").document(), {
    "evento": "login",
    "ts": firestore.SERVER_TIMESTAMP
})
batch.update(db.document("usuarios/user-123"), {
    "ultimoLogin": firestore.SERVER_TIMESTAMP,
    "loginCount": firestore.Increment(1)
})
batch.commit()

Real-time listeners (Native mode)

# Escutar mudanças em tempo real em uma coleção
def on_snapshot(col_snapshot, changes, read_time):
    for change in changes:
        if change.type.name == "ADDED":
            print(f"Novo documento: {change.document.id}")
        elif change.type.name == "MODIFIED":
            print(f"Modificado: {change.document.id}")
        elif change.type.name == "REMOVED":
            print(f"Removido: {change.document.id}")
 
# Ouvir apenas pedidos pendentes
unsubscribe = db.collection("pedidos") \
    .where(filter=firestore.FieldFilter("status", "==", "NOVO")) \
    .on_snapshot(on_snapshot)
 
# Para parar de ouvir
unsubscribe()

Precificação

Sem custo por instância; paga apenas pelo uso:

OperaçãoPreço aproximado
Leitura de documento$0.06 por 100k
Escrita de documento$0.18 por 100k
Exclusão de documento$0.02 por 100k
Storage$0.18/GB/mês
Quota gratuita50k leituras + 20k escritas + 20k exclusões por dia

Limitações importantes

  • 1 escrita/segundo por documento: contadores de alta frequência precisam de sharding ou usar Increment() com cuidado
  • Máximo 1 MB por documento
  • Sem JOINs entre coleções
  • Sem queries de “full-text search” (use Algolia, Elasticsearch ou BigQuery para isso)
  • Máximo 500 operações por batch/transação

Emulador local

# Instalar e iniciar emulador
gcloud emulators firestore start --host-port=localhost:8080
 
# Configurar SDK para usar emulador
export FIRESTORE_EMULATOR_HOST="localhost:8080"
 
# Python usa automaticamente o emulador quando a variável está definida
db = firestore.Client(project="projeto-local")

Ver também: gcp | gcp-cloud-functions | gcp-pubsub | gcp-bigtable | db-tipos-de-bancos-de-dados | db-nosql