A concepção de "tabela de corrente" (ou "scd - slowly changing dimension" tipo 2, em inglês) é uma abordagem de modelagem de dados em data warehousing focada em como os dados históricos são armazenados. Essencialmente, ela visa registrar todas as alterações de um registro ao longo do tempo, desde sua criação até o estado atual.
Considere a seguinte tabela de corrente que armazena informações básicas de usuários e seus ciclos de vida. Esta estrutura permite recuperar tanto os dados mais recentes quanto os históricos.
| Data de Registro | ID do Usuário | Número de Telefone | Data de Início | Data de Fim |
|---|---|---|---|---|
| 2017-01-01 | 001 | 111111 | 2017-01-01 | 9999-12-31 |
| 2017-01-01 | 002 | 222222 | 2017-01-01 | 2017-01-01 |
| 2017-01-01 | 002 | 233333 | 2017-01-02 | 9999-12-31 |
| 2017-01-01 | 003 | 333333 | 2017-01-01 | 9999-12-31 |
| 2017-01-01 | 004 | 444444 | 2017-01-01 | 2017-01-01 |
| 2017-01-01 | 004 | 432432 | 2017-01-02 | 2017-01-02 |
| 2017-01-01 | 004 | 432432 | 2017-01-03 | 9999-12-31 |
| 2017-01-02 | 005 | 555555 | 2017-01-02 | 2017-01-02 |
| 2017-01-02 | 005 | 115115 | 2017-01-03 | 9999-12-31 |
| 2017-01-03 | 006 | 666666 | 2017-01-03 | 9999-12-31 |
Data de Inícioindica o momento em que o registro se tornou válido.Data de Fimindica o momento em que o registro deixou de ser válido.Data de Fim= '9999-12-31' significa que o registro está atualmente ativo.- Para consultar todos os registros ativos no momento, utilize:
SELECT * FROM tabela_corrente WHERE Data de Fim = '9999-12-31'. - Para obter um snapshot histórico em uma data específica (ex: '2017-01-01'), utilize:
SELECT * FROM tabela_corrente WHERE Data de Início <= '2017-01-01' AND Data de Fim >= '2017-01-01'.
Cenários de Uso de Tabela de Corrente
Em projetos de data warehousing, frequentemente nos deparamos com tabelas que exigem um tartamento especial:
- Grandes Volumes de Dados: Tabelas com bilhões de registros e muitos campos podem consumir centenas de gigabytes, mesmo com compressão (como ORC), e ainda mais com redundância de dados no HDFS.
- Atualizações Frequentes: Campos como informações de contato do usuário, descrições de produtos ou status de pedidos podem ser atualizados frequentemente.
- Necessidade de Histórico: A exigência de visualizar snapshots históricos em pontos ou períodos específicos no tempo é comum (ex: o status de um pedido em uma data passada).
- Baixa Taxa de Mudança: Embora haja muitas atualizações em termos absolutos, a proporção de registros alterados em relação ao total pode ser pequena (ex: 2 milhões de 1 bilhão de usuários mudam diariamente).
Para essas tabelas, as seguintes abordagens podem ser consideradas:
- Opção 1: Manter Apenas o Registro Mais Recente: Um processo diário que substitui os dados antigos pelos mais recentes.
- Opção 2: Manter Fatias Diárias Completas: Armazenar uma cópia completa da tabela a cada dia.
- Opção 3: Utilizar Tabela de Corrente: Implementar a abordagem de tabela de corrente.
Comparativo das Abordagens
- Opção 1 (Apenas o Mais Recente):
- Vantagens: Simples de implementar, economiza espaço, consultas simples sem necessidade de filtros de data complexos.
- Desvantagens: Perda total de histórico. Recuperar dados passados exige acesso a outras fontes (ex: tabelas de log).
- Opção 2 (Fatias Diárias Completas):
- Vantagens: Preserva todo o histórico de forma completa e confiável.
- Desvantagens: Consumo de espaço de armazenamento excessivo, pois muitas informações idênticas são repetidas em cada fatia diária. Manter um histórico longo pode se tornar impraticável.
- Opção 3 (Tabela de Corrente):
- Vantagens: Equilibra o uso de espaço com a necessidade de histórico. O volume de dados adicionado diariamente é significativamente menor que em uma fatia completa. Atende à necessidade de dados atuais e históricos através de consultas com filtros de data.
- Desvantagens: Consultas históricas podem ser um pouco mais complexas do que com fatias diárias.
A tabela de corrente oferece um bom equilíbrio entre a eficiência de armazenamento e a capacidade de rastrear mudanças históricas, tornando-a uma opção valiosa.
Design de Tabela de Corrente
Vamos ilustrar com um exemplo de uma tabela usuário em um banco de dados relacional (como MySQL) e como ela se transforma em uma tabela de corrente no data warehouse.
Dados Originais:
- 2017-01-01:| Data de Registro | ID do Usuário | Número de Telefone | | :--------------- | :------------ | :----------------- | | 2017-01-01 | 001 | 111111 | | 2017-01-01 | 002 | 222222 | | 2017-01-01 | 003 | 333333 | | 2017-01-01 | 004 | 444444 |
- 2017-01-02 (Usuários 002 e 004 atualizados; 005 é novo):| Data de Registro | ID do Usuário | Número de Telefone | Observação | | :--------------- | :------------ | :----------------- | :----------------------------------- | | 2017-01-01 | 001 | 111111 | - | | 2017-01-01 | 002 | 233333 | (Mudou de 222222 para 233333) | | 2017-01-01 | 003 | 333333 | - | | 2017-01-01 | 004 | 432432 | (Mudou de 444444 para 432432) | | 2017-01-02 | 005 | 555555 | (Novo em 2017-01-02) |
- 2017-01-03 (Usuários 004 e 005 atualizados; 006 é novo):| Data de Registro | ID do Usuário | Número de Telefone | Observação | | :--------------- | :------------ | :----------------- | :----------------------------------- | | 2017-01-01 | 001 | 111111 | - | | 2017-01-01 | 002 | 233333 | - | | 2017-01-01 | 003 | 333333 | - | | 2017-01-01 | 004 | 654321 | (Mudou de 432432 para 654321) | | 2017-01-02 | 005 | 115115 | (Mudou de 555555 para 115115) | | 2017-01-03 | 006 | 115115 | (Novo em 2017-01-03) |
Representação como Tabela de Corrente (dados em 2017-01-03):
| Data de Registro | ID do Usuário | Número de Telefone | Data de Início | Data de Fim |
|---|---|---|---|---|
| 2017-01-01 | 001 | 111111 | 2017-01-01 | 9999-12-31 |
| 2017-01-01 | 002 | 222222 | 2017-01-01 | 2017-01-01 |
| 2017-01-01 | 002 | 233333 | 2017-01-02 | 9999-12-31 |
| 2017-01-01 | 003 | 333333 | 2017-01-01 | 9999-12-31 |
| 2017-01-01 | 004 | 444444 | 2017-01-01 | 2017-01-01 |
| 2017-01-01 | 004 | 432432 | 2017-01-02 | 2017-01-02 |
| 2017-01-01 | 004 | 432432 | 2017-01-03 | 9999-12-31 |
| 2017-01-02 | 005 | 555555 | 2017-01-02 | 2017-01-02 |
| 2017-01-02 | 005 | 115115 | 2017-01-03 | 9999-12-31 |
| 2017-01-03 | 006 | 666666 | 2017-01-03 | 9999-12-31 |
Implementação e Atualização de Tabela de Corrente no Hive
Para implementar uma tabela de corrente no Hive, geralmetne precisamos de:
- Uma tabela na camada ODS (Operational Data Store) com o volume total de dados do usuário, usada para a inicialização.
- Uma tabela que registra as atualizações diárias de usuários.
É importante definir a granularidade temporal da tabela de corrente. Uma abordagem comum é usar granularidade diária, onde apenas o estado final de um registro em um dia é considerado, mesmo que tenha havido múltiplas alterações.
Obtenção de Incrementos Diários
Para capturar as mudanças diárias, pode-se monitorar as alterações em bancos de dados de origem (usando ferramentas como Canal) e consolidar essas mudanças. Uma alternativa é comparar fatias diárias completas: concatenar todos os campos de um registro, calcular um hash (como MD5) e comparar os hashes entre dias para identificar alterações.
Estrutura das Tabelas (Hive DDL)
- Tabela ODS de Usuários:
CREATE EXTERNAL TABLE ods.user_profile (
user_id STRING COMMENT 'Identificador do usuário',
phone_number STRING COMMENT 'Número de telefone',
registration_date STRING COMMENT 'Data de registro'
)
COMMENT 'Tabela de perfil de usuário ODS'
PARTITIONED BY (data_particao STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user_profile';
- Tabela ODS de Atualizações de Usuários:
CREATE EXTERNAL TABLE ods.user_updates (
user_id STRING COMMENT 'Identificador do usuário',
phone_number STRING COMMENT 'Número de telefone',
registration_date STRING COMMENT 'Data de registro'
)
COMMENT 'Tabela de atualizações diárias de usuário ODS'
PARTITIONED BY (data_particao STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/ods/user_updates';
- Tabela de Corrente de Usuários (DWS - Data Warehouse Service):
CREATE EXTERNAL TABLE dws.user_history (
user_id STRING COMMENT 'Identificador do usuário',
phone_number STRING COMMENT 'Número de telefone',
registration_date STRING COMMENT 'Data de registro',
start_date TIMESTAMP COMMENT 'Data de início de validade',
end_date TIMESTAMP COMMENT 'Data de fim de validade'
)
COMMENT 'Tabela de corrente de histórico de usuários'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION '/dws/user_history';
Lógica de Atualização (Exemplo para 2017-01-02)
Assumindo que a tabela dws.user_history já foi inicializada com os dados de '2017-01-01', e temos as atualizações para '2017-01-02' na ods.user_updates particionada por '2017-01-02'.
INSERT OVERWRITE TABLE dws.user_history
SELECT
user_id,
phone_number,
registration_date,
start_date,
CASE
WHEN end_date = '9999-12-31' AND update_info.user_id IS NOT NULL
THEN '2017-01-01' -- Data em que a validade expira para registros que serão substituídos
ELSE end_date
END AS end_date
FROM
dws.user_history AS current_state
LEFT JOIN
(SELECT user_id FROM ods.user_updates WHERE data_particao = '2017-01-02') AS update_info
ON
current_state.user_id = update_info.user_id
WHERE
current_state.end_date = '9999-12-31' -- Considera apenas registros atualmente ativos
UNION ALL
SELECT
new_updates.user_id,
new_updates.phone_number,
new_updates.registration_date,
'2017-01-02' AS start_date, -- Novo período de validade começa hoje
'9999-12-31' AS end_date -- Novo registro está ativo
FROM
ods.user_updates AS new_updates
WHERE
new_updates.data_particao = '2017-01-02';
Considerações Adicionais
- Tabela de Corrente vs. Tabela de Transações (Log Table): Uma tabela de transações registra cada alteração individualmente, enquanto uma tabela de corrente mantém apenas a versão ativa de um registro em um determinado período. A granularidade da tabela de corrente (ex: diária) é uma decisão de design.
- Performance de Consulta: Tabelas de corrente podem crescer significativamente. Para otimizar consultas em dados históricos extensos:
- Indexação: Se a plataforma de consulta suportar (ex: em bancos de dados relacionais), indexar
start_dateeend_datepode melhorar o desempenho. - Arquivamento/Particionamento: Manter uma cópia completa de dados históricos para consultas menos frequentes e uma tabela separada com dados recentes (ex: últimos 3 meses) para consultas de alta frequência.