Tabelas com histórico, ACID e time travel com o pacote deltaR
Esta aula apresenta o Delta Lake de forma conceitual e prática. Todo o código está disponível para execução, mas requer a instalação prévia do toolchain Rust e do pacote
deltaR. O foco aqui é compreender o que cada operação faz e quando usá-la.
Que cada gole desperte uma nova ideia.
Que cada script abra uma nova conversa.
Que o Café com R se torne um ponto de encontro nosso.
SITE OFICIAL > https://jenniferlopes.github.io/meu_site/
deltaR depende deledeltaRwrite_deltalake()arrow e duckdbO problema: com Parquet puro, cada escrita substitui o estado anterior sem deixar registro. Se você sobrescreve um arquivo com dados novos e depois descobre um erro nos dados anteriores, o histórico se perdeu.
Delta Lake é um formato de código aberto que adiciona uma camada de gerenciamento de transações e histórico sobre arquivos Parquet existentes. Desenvolvido pela Databricks e cedido à Linux Foundation em 2019.
O Delta Lake não substitui o Parquet, ele estende o Parquet com:
| Característica | Parquet puro | Delta Lake |
|---|---|---|
| Formato dos dados | .parquet |
.parquet + _delta_log/ |
| Histórico de versões | Nenhum | Completo |
| Transações ACID | Não | Sim |
| Time travel | Não | Sim |
| Evolução de schema | Manual | Nativa |
| Operação MERGE | Não existe | Nativa e atômica |
| Rollback | Não | Sim |
Leitura com arrow |
Sim | Via get_files() |
ACID: Atomicidade, Consistência, Isolamento e Durabilidade. Garantem que operações concorrentes não corrompam os dados.
Time travel: acesso a qualquer versão anterior da tabela, por número de versão ou por data e hora. Essencial para auditoria e reprodução de análises.
Schema evolution: adicionar, remover ou modificar colunas sem recriar os dados existentes. O Delta Lake controla qual versão do schema cada arquivo pertence.
MERGE: combina INSERT, UPDATE e DELETE em uma única transação atômica. Resolve o problema do upsert sem carregar os dados na memória.
Rust é uma linguagem de programação de sistemas com desempenho comparável ao C e segurança de memória garantida em tempo de compilação. É usada hoje em componentes críticos do Firefox, Linux Kernel e Android.
No ecossistema de dados, várias ferramentas foram reescritas em Rust para ganhos massivos de desempenho:
| Ferramenta | Antes | Com Rust |
|---|---|---|
delta-rs |
Java/Scala (Spark) | Rust puro |
polars |
Python | Rust |
arrow (partes) |
C++ | Rust (partes) |
O delta-rs é a implementação do Delta Lake em Rust. O deltaR é a interface R sobre ele.
O deltaR não está disponível como binário pré-compilado no CRAN. Durante a instalação, o R precisa compilar o código Rust localmente.
O que acontece durante remotes::install_github("ixpantia/deltaR"):
deltaRrustc) compila o código para o seu sistema.so, .dll ou .dylib)library(deltaR)Por isso a instalação demora mais do que um pacote CRAN comum.
macOS e Linux:
Windows: baixar e executar o instalador em rustup.rs
Após instalar, verificar no terminal:
A versão mínima exigida pelo deltaR é Rust >= 1.88. Para atualizar:
Tip
A primeira instalação demora porque compila o delta-rs. Instalações subsequentes reutilizam o cache de compilação do cargo e são mais rápidas.
O deltaR não lê os dados diretamente para o R. Ele gerencia a tabela Delta e delega a leitura para o arrow, duckdb ou polars:
R (você escreve código aqui)
|
| deltaR::write_deltalake()
v
delta-rs (Rust)
|
| Cria e gerencia _delta_log/ + arquivos .parquet
v
Sistema de arquivos (disco local, S3, GCS, Azure)
|
| deltaR::get_files() → retorna caminhos .parquet
v
arrow::open_dataset() → dplyr → collect()
|
v
R (tibble na memória)
library(deltaR)
library(tidyverse)
library(nycflights13)
voos_jan <- flights |>
as_tibble() |>
filter(month == 1) |>
select(year, month, day, origin, dest,
carrier, dep_delay, arr_delay,
air_time, distance)
# Na primeira escrita, a tabela é criada do zero
write_deltalake(
data = voos_jan,
path = "dados_delta/voos")Após write_deltalake(), o diretório contém:
dados_delta/voos/
├── _delta_log/
│ └── 00000000000000000000.json ← log da versão 0
└── part-00000-abc123.parquet ← dados da versão 0
Arquivos .parquet: os dados reais, idênticos ao Parquet puro.
Pasta _delta_log/: o log de transações. Cada arquivo JSON registra uma operação com metadados: quais arquivos foram adicionados ou removidos, o schema da tabela e estatísticas.
voos_fev <- flights |>
as_tibble() |>
filter(month == 2) |>
select(year, month, day, origin, dest,
carrier, dep_delay, arr_delay,
air_time, distance)
# Append: adiciona dados sem apagar os existentes
write_deltalake(
data = voos_fev,
path = "dados_delta/voos",
mode = "append")
# Overwrite: substitui todos os dados existentes
# Os arquivos anteriores NÃO são apagados fisicamente,
# apenas marcados como removidos no log
write_deltalake(
data = voos_fev,
path = "dados_delta/voos",
mode = "overwrite")Important
Atenção: o modo overwrite marca os arquivos anteriores como removidos no log, mas não os apaga fisicamente. Os dados de versões anteriores continuam acessíveis via time travel. A remoção física só acontece com vacuum().
library(arrow)
# delta_table() abre a referência para a tabela
# Não carrega os dados: apenas registra o caminho
dt <- delta_table("dados_delta/voos")
# Ver a versão atual
table_version(dt)
# Ver o schema
get_schema(dt)
# Ler com arrow via get_files()
open_dataset(get_files(dt)) |>
filter(!is.na(arr_delay)) |>
group_by(origin) |>
summarise(
total_voos = n(),
atraso_medio = round(mean(arr_delay), 2),
.groups = "drop") |>
collect()Time travel é a capacidade de acessar qualquer versão anterior da tabela como se ela ainda existisse.
Cada operação de escrita cria uma nova versão no _delta_log/. O Delta Lake registra exatamente quais arquivos pertenciam a cada versão.
Por que isso importa na prática:
dt <- delta_table("dados_delta/voos")
# Ver todas as operações realizadas
get_table_history(dt)
# Versão 0: dados de janeiro (primeira escrita)
dt_v0 <- delta_table(
path = "dados_delta/voos",
version = 0L)
open_dataset(get_files(dt_v0)) |>
summarise(total_linhas = n(), mes_unico = max(month)) |>
collect()
# Por timestamp: acessa a versão vigente naquele momento
dt_historico <- delta_table(
path = "dados_delta/voos",
timestamp = "2026-05-11 10:44:50")
table_version(dt_historico)Evolução de schema é a capacidade de modificar a estrutura da tabela sem recriar todos os dados existentes.
O problema sem evolução de schema:
Se você tentar fazer um append com um data.frame que tem uma coluna a mais do que o schema registrado na tabela, o Delta Lake rejeita a operação:
Error: Schema mismatch detected:
Column 'velocidade_kmh' not found in existing table schema.
Use schema_mode = 'merge' to allow schema evolution.
Com schema_mode = "merge": o Delta Lake aceita a nova coluna, atualiza o schema no log e trata os registros anteriores como tendo NA nessa coluna.
voos_com_coluna <- voos_jan |>
mutate(
velocidade_kmh = (distance * 1.60934) /
(air_time / 60))
# Isso geraria erro sem schema_mode = "merge"
write_deltalake(
data = voos_com_coluna,
path = "dados_delta/voos",
mode = "append",
schema_mode = "merge")
# Verificar o schema atualizado
get_schema(delta_table("dados_delta/voos"))Os registros anteriores, que não tinham velocidade_kmh, recebem NA automaticamente. Os novos registros têm o valor calculado.
MERGE combina três operações em uma única transação atômica:
Sem MERGE, implementar um upsert exige:
Com MERGE: tudo acontece dentro do banco, em uma única operação atômica, sem carregar os dados na memória do R.
# Dados de referência na tabela
voos_ref <- voos_jan |>
mutate(
flight_key = paste(month, day, flight,
origin, dest, sep = "_"),
status = "original") |>
select(flight_key, month, day, origin,
dest, carrier, arr_delay, status)
write_deltalake(data = voos_ref,
path = "dados_delta/voos_merge")
# Dados novos: alguns existentes atualizados + novos
voos_novos <- voos_jan |>
slice_sample(n = 500) |>
mutate(
flight_key = paste(month, day, flight,
origin, dest, sep = "_"),
arr_delay = arr_delay + runif(500, -5, 5),
status = "atualizado") |>
select(flight_key, month, day, origin,
dest, carrier, arr_delay, status)
# Executar o MERGE
delta_merge(
target = "dados_delta/voos_merge",
source = voos_novos,
condition = "target.flight_key = source.flight_key"
) |>
when_matched_update(
updates = c(arr_delay = "source.arr_delay",
status = "source.status")
) |>
when_not_matched_insert(
values = c(
flight_key = "source.flight_key",
month = "source.month",
arr_delay = "source.arr_delay",
status = "source.status")
) |>
merge_execute()Tip
when_matched_delete() remove os registros da tabela, mas o Delta Log mantém o registro da operação. Os dados deletados podem ser recuperados via time travel enquanto não forem removidos com vacuum().
A leitura via get_files() retorna os caminhos dos arquivos Parquet da versão atual. O open_dataset() do arrow lê apenas as colunas e partições necessárias para a consulta.
O DuckDB lê tabelas Delta diretamente com a extensão delta, sem precisar do deltaR:
library(duckdb)
con <- dbConnect(duckdb())
dbExecute(con, "INSTALL delta; LOAD delta")
dbGetQuery(con, "
SELECT origin, COUNT(*) AS total_voos,
AVG(arr_delay) AS atraso_medio
FROM delta_scan('dados_delta/voos')
WHERE arr_delay IS NOT NULL
GROUP BY origin ORDER BY atraso_medio DESC") |>
as_tibble()
dbDisconnect(con)Para nuvem, use o argumento storage_options com credenciais via Sys.getenv():
| Característica | CSV | Parquet | Delta Lake |
|---|---|---|---|
| Formato | Texto | Binário colunar | Binário colunar + log |
| Histórico de versões | Não | Não | Sim |
| Time travel | Não | Não | Sim |
| Transações ACID | Não | Não | Sim |
| Evolução de schema | Não | Manual | Nativa |
| MERGE (upsert) | Não | Não | Sim |
Leitura com arrow |
Sim | Sim | Via get_files() |
Leitura com duckdb |
Sim | Sim | Via extensão delta |
Funções centrais do deltaR:
| Função | O que faz |
|---|---|
write_deltalake() |
Cria ou atualiza uma tabela Delta |
delta_table() |
Abre a referência para a tabela |
get_files() |
Lista os arquivos da versão atual |
get_table_history() |
Retorna o histórico de operações |
delta_merge() + merge_execute() |
Executa upsert atômico |
Use Delta Lake quando precisar de:
Use Parquet simples quando os dados são imutáveis após a escrita.
deltaR: ixpantia.github.io/deltalakeRdeltaR: github.com/ixpantia/deltaRdelta-rs: delta-io.github.io/delta-rsarrow R: arrow.apache.org/docs/rContinue praticando e explorando.
Esta aula é parte do projeto Café com R. É open source. Use, compartilhe e adapte.
Que cada gole desperte uma nova ideia.
Que cada script abra uma nova conversa.
Que o Café com R se torne um ponto de encontro nosso.
SITE OFICIAL > https://jenniferlopes.github.io/meu_site/

Jennifer Lopes | Café com R