O que é Apache Iceberg?

Larissa Rocha
7 min readFeb 28, 2023

--

Apache Iceberg é um formato de tabela open source que tem se tornado cada vez mais popular. A primeira vez que eu ouvi falar em Iceberg, algumas perguntas passaram pela minha cabeça, como por exemplo: Como funciona? Qual o motivo de ser tão popular? Por que precisamos dele? E por último e não menos importante: o que raios é um formato de tabela?

O objetivo desse post é compartilhar o que aprendi tentando responder essas perguntas.

Photo by Xavier Balderas Cejudo on Unsplash

O que é um formato de tabela?

Essa pode parecer uma pergunta simples, afinal, quando interagimos com uma tabela os dados apenas estão lá, magicamente, mas como?

É aí que entra o formato de tabela, que em uma definição simples, é uma forma de organizar arquivos que contém dados e apresentá-los em uma única tabela, e se você nunca tinha parado para pensar nisso antes, é porque o formato de tabela fornece essa camada de abstração entre você que está lendo ou escrevendo na tabela, e os dados.

Esse é um conceito que existe há um bom tempo e já era implementado pelos sistemas de gerenciamento de banco de dados relacional (SGBD), nesse caso, todas as interações com os arquivos de dados eram gerenciadas pela ferramenta. No entanto, no mundo big data, precisamos lidar com uma grande volumetria de dados de diversas fontes, com isso, ter um único sistema gerenciando todos os dados não é mais viável, é por isso que um formato de tabela open source é importante.

Por que Iceberg?

Ok, agora já entendemos que precisamos de uma forma de interagir com os arquivos de dados, nesse momento você deve estar se perguntando como as coisas funcionavam antes do Iceberg existir e por que esse formato tem ganhado tanta atenção da comunidade.

Quando o Big Data se popularizou, lá por meados dos anos 2000, o ecossistema Hadoop tentava endereçar algumas necessidades que surgiam, entre elas, a de facilitar o acesso dos usuários aos dados e metadados, com esse objetivo foi criado o Hive e o formato de tabela Hive foi o estado da arte durante muito tempo.

Nesse formato, uma tabela é definida por todos os arquivos dentro de um determinado diretório, ou mais de um diretório, caso a tabela contenha partições. As tabelas Hive tinham a seu favor o fato de serem independentes do tipo de arquivo (parquet, avro, etc) e possuirem um metastore, que age como um repositório central de metadados. Entretanto, suas desvantagens foram ficando mais acentuadas a medida que o volume de dados escalava, por exemplo, pequenas mudanças nos dados eram ineficientes pois era preciso mudar toda a partição e não era possível atualizar mais de uma partição ao mesmo tempo de forma consistente.

A Netflix esbarrou nesses problemas e decidiu que era hora de criar um novo formato de tabela, um que funcionasse a nível de arquivo, e não de diretório, um formato que garantisse consistência, fornecesse planning e execução mais rápida de queries, e permitisse que as tabelas evoluissem com o tempo. Assim surgiu o Iceberg, que diferente do formato Hive, define uma tabela como uma lista canônica de arquivos.

Como o formato Iceberg funciona?

Agora que já vimos o cenário que levou a Netflix a criar esse novo formato de tabela, precisamos entender sua arquitetura para entender como Iceberg consegue endereçar os problemas que as tabelas Hive não conseguiam.

Uma visão geral da arquitetura do Iceberg pode ser vista na figura 1. Vamos detalhar os componentes:

Figura 1 — Fonte: https://www.dremio.com/blog/the-life-of-a-read-query-for-apache-iceberg-tables/
  • Catálogo (Catalog): O catálogo é um repositório central onde é armazenada a referência ao arquivo de metadado de cada tabela, no exemplo da figura 1, a tabela table1 está apontando para o arquivo de metadado da direita. O requisito principal do catálogo é suportar operações atômicas para atualizar o ponteiros.
  • Arquivo de metadados (metadate file): É um arquivo json que armazena metadados de uma tabela em um determinado instante de tempo. Nele podemos encontrar detalhes sobre o schema da tabela, informação sobre partição, id do snapshot atual (current-snapshot-id), o caminho para a lista de manifesto (manifest list), etc. O arquivo de metadado parece algo como:
{
"format-version" : 1,
"table-uuid" : "dba0cf2e-e7ba-4a0f-b41c-366378994d8f",
"location" : "s3://dm-iceberg/iceberg-emr/NYC_trip_ice",
"last-updated-ms" : 1658756162215,
"last-column-id" : 19,
"schema" : {
"type" : "struct",
"schema-id" : 0,
"fields" : [ {
"id" : 1,
"name" : "VendorID",
"required" : false,
"type" : "long"
}
"current-snapshot-id" : 6599723564686825597,
"refs" : {
"main" : {
"snapshot-id" : 6599723564686825597,
"type" : "branch"
}
},
"snapshots" : [ {
"snapshot-id" : 6599723564686825597,
"timestamp-ms" : 1658756162215,
"summary" : {
"operation" : "append",
"added-data-files" : "1",
"added-records" : "3599920",
"total-records" : "3599920",
"total-files-size" : "0",
"total-data-files" : "1",
"total-delete-files" : "0",
"total-position-deletes" : "0",
"total-equality-deletes" : "0"
},
"manifest-list" :
"s3://dm-iceberg/iceberg-emr/NYC_trip_ice/metadata/snap-6599723564686825597-1-b5097d5b-1d5b-46cd-be1a-1d735fcbdfe4.avro",
"schema-id" : 0
} ],
  • Lista de manifesto (manifest list): Como o próprio nome já diz, é uma lista com todos os arquivos manifesto, contém informações sobre todos os arquivos manifesto que compões um determinado snapshot, como sua localização por exemplo. É um arquivo .avro .
  • Arquivo manifesto (manifest file): O arquivo manifesto mantém o controle sobre todos os arquivos de dados, junto com detalhes e estatísticas, por exemplo:
{
"status" : 1,
"snapshot_id" : null,
"data_file" : {
"file_path" :
"s3://dm-iceberg/iceberg-emr/NYC_trip_ice/1d2163d3-9058-a238-624b-5d31e0a55100/2_12_0.parquet",
"file_format" : "PARQUET",
"partition" : {},
"record_count" : 3599920,
"file_size_in_bytes" : 119098431,
"block_size_in_bytes" : 67108864,
"column_sizes" : [],
"value_counts" : [],
"null_value_counts" : [],
"nan_value_counts" : [],
"lower_bounds" : [],
"upper_bounds" : [],
"key_metadata" : null,
"split_offsets" : null,
"sort_order_id" : 0
}
}
  • Arquivos de dados (data files): São os arquivos que de fato contém os dados.

Esses são os componentes do Iceberg, eles vão interagir em camadas diferentes: a camada do catálogo, dos metadados e dos dados. Nesse blog post encontrei essa figura que para mim ficou muito mais claro:

Figura 2 — Fonte https://www.dremio.com/blog/a-hands-on-look-at-the-structure-of-an-apache-iceberg-table/

Agora que conhecemos os componentes, podemos pensar em como as coisas funcionam quando fazemos operações em uma tabela iceberg.

Criação de uma tabela

Quando executamos um comando de CREATE TABLE, por exemplo:

CREATE TABLE table1 (
order_id BIGINT,
customer_id BIGINT,
order_amount DECIMAL(10, 2),
order_ts TIMESTAMP
)
USING iceberg
PARTITIONED BY ( HOUR(order_ts) );

O que o Iceberg vai fazer é o seguinte:

  1. Escrever o arquivo de metadados (com snapshot s0), então, na sua ferramenta de armazenamento (por exemplo s3) será criada a seguinte estrutura de pastas:
- table1/
- metadata/
v1.metadata.json
- data/

2. Atualizar o ponteiro do catálogo para apontar para esse arquivo de metadados

Esse comando apenas cria a tabela e define seu schema, agora digamos que vamos inserir um registro nessa tabela, executando, por exemplo:

INSERT INTO table1 VALUES (
123,
456,
36.17,
'2021-01-26 08:10:23'
);

O que o Iceberg vai fazer é:

  1. Escrever o arquivo com os dados no diretório data dentro da partição que definimos (order_ts_hour)
  2. Escrever um arquivo manifesto que aponta para esse aquivo de dados
  3. Criar uma lista de manifesto apontando para o arquivo manifesto criado no passo 2
  4. Criar um novo arquivo de metadados (v2.metadata.json) com um novo snapshot s1
  5. Atualizar o ponteiro do catálogo para apontar para o novo arquivo de metadados

Após isso, a estrutura de pastas vai se parecer com algo do tipo:

- table1/
- metadata/
v1.metadata.json
v2.metadata.json
d8f9-ad19-4e.avro -> arquivo manifesto
snap-2938-1-4103.avro -> lista de manifesto
- data/
- order_ts_hour=2021-01-26-08/
00000-5-cae2d.parquet

Leitura em uma tabela

Agora digamos que vamos ler os dados dessa tabela:

SELECT * FROM table1

Quando uma query é executada em uma tabela existente, primeiro o query engine vai até o catálogo, lá está registrada a localização do arquivo de metadados da tabela. Ao abrir o arquivo de metadados, temos a localização da lista manifesto para o snapshot mais recente (também podemos especificar um determinado snapshot). Na lista de manifesto, vamos ter a localização desse arquivo manifesto, que por sua vez aponta para o arquivo (ou arquivos) de dados.

A figura 3 abaixo ilustra esse processo:

Figura 3 — Fonte: a autora

Eu sei que é meio complexo de visualizar, mas esse vídeo aqui me ajudou demais.

Conclusão

Nesse artigo, procurei cobrir as principais características do Apache Iceberg e sua arquitetura. Como vimos, da forma como são construídas, as tabelas Iceberg permitem operações atômicas e consistentes já que a leitura e a escrita não interferem uma na outra, por exemplo, você continua lendo do snapshot s0 enquanto o snapshot s1 está sendo escrito. A forma como os arquivos são escritos fornece estatísticas sobre as tabelas e os caminhos para os arquivos seguintes, otimizando o planning de queries e evitando operações custosas ao sistema de arquivos.

Além do mais, o Iceberg possui outras capacidades como schema evolution e time travel que eu não comentei aqui pois acho que mereceriam um post a parte. Enfim, espero que esse artigo tenha ajudado a sanar algumas dúvidas, abaixo deixo uma lista de referências que foram úteis para mim.

Referências

Revisão: Victor Gustavo da Silva Oliveira

--

--