Processar dados de comportamento do usuário é desafiador devido ao alto volume e à diversidade de formatos. Os modelos tradicionais de tabela larga oferecem desempenho eficiente em consultas, mas geram alta redundância de dados, maior sobrecarga de armazenamento e ciclos lentos de manutenção. Este tutorial demonstra como usar o Realtime Compute for Apache Flink, o ApsaraDB for MongoDB e o Hologres para criar um pipeline de tabela larga em tempo real que equilibra esses fatores.
Como funciona
O Realtime Compute for Apache Flink processa o fluxo de dados. O ApsaraDB for MongoDB armazena as tabelas de fatos e dimensões como um banco de dados NoSQL orientado a documentos, com esquema flexível e alta capacidade de leitura e escrita. O Hologres atua como data warehouse analítico — os dados ficam disponíveis para consulta imediatamente após a gravação.
O pipeline utiliza dois jobs do Flink conectados por um tópico Kafka:
Job 1: lê o fluxo de captura de dados de alteração (CDC) do MongoDB. Quando a tabela de fatos (
game_sales) sofre alterações, suas chaves primárias (PKs) são enviadas diretamente para o Kafka. Se uma tabela de dimensões for alterada, um lookup join identifica os registros afetados na tabela de fatos e envia suas PKs para o Kafka.Job 2: consome as PKs do Kafka, executa lookup joins nas tabelas de fatos e dimensões do MongoDB para reconstruir o registro completo da tabela larga e faz o upsert do resultado no Hologres.
Benefícios:
Alto throughput de escrita: O ApsaraDB for MongoDB gerencia leituras e escritas de alta concorrência em clusters fragmentados, escalando desempenho e armazenamento para suportar grandes volumes de atualizações frequentes.
Propagação eficiente de alterações: Apenas as PKs dos registros afetados são encaminhadas ao Kafka, e não as linhas completas. Isso mantém o reprocessamento mínimo, independentemente do volume total de dados.
Consulta em tempo real: O Hologres suporta upserts de baixa latência e disponibiliza os dados para consulta logo após cada escrita.
Prática guiada
Ao final deste tutorial, você terá um pipeline ativo em que as alterações no MongoDB — tanto na tabela de fatos quanto nas tabelas de dimensões — se propagam automaticamente para uma tabela larga no Hologres e ficam imediatamente disponíveis para consulta.
O pipeline une três coleções do MongoDB em uma única tabela larga no Hologres:
game_sales
<table> <thead> <tr> <td><p>sale_id</p></td> <td><p>game_id</p></td> <td><p>platform_id</p></td> <td><p>sale_date</p></td> <td><p>units_sold</p></td> <td><p>sale_amt</p></td> <td><p>status</p></td> </tr> </thead> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <tbody></tbody> </table>
game_dimension
<table> <thead> <tr> <td><p>game_id</p></td> <td><p>game_name</p></td> <td><p>release_date</p></td> <td><p>developer</p></td> <td><p>publisher</p></td> </tr> </thead> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <tbody></tbody> </table>
platform_dimension
<table> <thead> <tr> <td><p>platform_id</p></td> <td><p>platform_name</p></td> <td><p>type</p></td> </tr> </thead> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <tbody></tbody> </table>
game_sales_details
<table> <thead> <tr> <td><p>sale_id</p></td> <td><p>game_id</p></td> <td><p>platform_id</p></td> <td><p>sale_date</p></td> <td><p>units_sold</p></td> <td><p>sale_amt</p></td> <td><p>status</p></td> </tr> </thead> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <tbody> <tr> <td><p>game_name</p></td> <td><p>release_date</p></td> <td><p>developer</p></td> <td><p>publisher</p></td> <td><p>platform_name</p></td> <td><p>type</p></td> <td><p></p></td> </tr> </tbody> </table>
Fluxo de propagação de alterações
Cada alteração passa por quatro etapas:
Captura: Detecta alterações em tempo real nas tabelas de dimensões do MongoDB.
Propagação: Ao detectar mudança em uma tabela de dimensões, o Job 1 realiza um lookup join (por exemplo, em
game_id) para localizar as linhas afetadas na tabela de fatos e extrai suas PKs (por exemplo,sale_id).Disparo: Envia as PKs para o Kafka para notificar o Job 2 sobre atualizações pendentes.
Upsert: O Job 2 busca os dados mais recentes, reconstrói a linha da tabela larga e executa o upsert no Hologres.
Pré-requisitos
Antes de começar, verifique se você possui:
Um workspace do Realtime Compute for Apache Flink executando VVR 8.0.5 ou posterior. Para mais informações, consulte Activate Realtime Compute for Apache Flink.
Uma instância do ApsaraDB for MongoDB na versão 4,0 ou superior. Para mais informações, consulte Create a sharded cluster instance.
Uma instância exclusiva do Hologres na versão 1,3 ou superior. Para mais informações, consulte Purchase a Hologres instance.
Uma instância do ApsaraMQ for Kafka. Para mais informações, consulte Deploy an ApsaraMQ for Kafka instance.
Todas as quatro instâncias na mesma Virtual Private Cloud (VPC). Caso estejam em VPCs diferentes, estabeleça conectividade entre VPCs ou ative o acesso à internet para o Realtime Compute for Apache Flink. Para mais informações, consulte How does Realtime Compute for Apache Flink access a service across VPCs? e How does Realtime Compute for Apache Flink access the Internet?
Permissões de usuário RAM ou função RAM para os recursos relevantes.
Etapa 1: Preparar os dados
Criar coleções do MongoDB
Adicione o bloco CIDR do seu workspace Flink à lista de permissões do MongoDB. Para detalhes, consulte Configure a whitelist for an instance e How do I configure a whitelist?
-
No editor SQL do console do Data Management (DMS), crie o banco de dados
mongo_test:use mongo_test; -
Crie as coleções
game_sales,game_dimensioneplatform_dimensione insira dados de exemplo:// Game sales table (status: 1 = active, 0 = logically deleted) db.game_sales.insert( [ {sale_id:0,game_id:101,platform_id:1,"sale_date":"2024-01-01",units_sold:500,sale_amt:2500,status:1}, ] ); // Game dimension table db.game_dimension.insert( [ {game_id:101,"game_name":"SpaceInvaders","release_date":"2023-06-15","developer":"DevCorp","publisher":"PubInc"}, {game_id:102,"game_name":"PuzzleQuest","release_date":"2023-07-20","developer":"PuzzleDev","publisher":"QuestPub"}, {game_id:103,"game_name":"RacingFever","release_date":"2023-08-10","developer":"SpeedCo","publisher":"RaceLtd"}, {game_id:104,"game_name":"AdventureLand","release_date":"2023-09-05","developer":"Adventure","publisher":"LandCo"}, ] ); // Platform dimension table db.platform_dimension.insert( [ {platform_id:1,"platform_name":"PCGaming","type":"PC"}, {platform_id:2,"platform_name":"PlayStation","type":"Console"}, {platform_id:3,"platform_name":"Mobile","type":"Mobile"} ] ); -
Verifique as inserções:
db.game_sales.find(); db.game_dimension.find(); db.platform_dimension.find();A consulta
db.game_dimension.find()retorna 4 registros:game_id: 101,game_name: SpaceInvaders,release_date: 2023-06-15game_id: 102,game_name: PuzzleQuest,release_date: 2023-07-20game_id: 103,game_name: RacingFever,release_date: 2023-08-10game_id: 104,game_name: AdventureLand,release_date: 2023-09-05
Criar a tabela do Hologres
Faça login no Hologres console, clique em Instances no painel de navegação à esquerda e selecione sua instância do Hologres. No canto superior direito, clique em Connect to Instance.
-
Na barra de navegação superior, clique em Metadata Management > Create Database. Insira
testno campo Database Name, defina Policy como SPM e clique em OK. Para mais informações, consulte Create a database.Na caixa de diálogo, selecione a instância chamada User-behavior-test, defina Log On Immediately como Yes e clique em OK.
-
Na barra de navegação superior, clique em SQL Editor. Clique no ícone SQL para criar uma nova consulta, selecione a instância e o banco de dados de destino e execute a seguinte instrução para criar a tabela larga
game_sales_details:CREATE TABLE game_sales_details( sale_id INT not null primary key, game_id INT, platform_id INT, sale_date VARCHAR(50), units_sold INT, sale_amt INT, status INT, game_name VARCHAR(50), release_date VARCHAR(50), developer VARCHAR(50), publisher VARCHAR(50), platform_name VARCHAR(50), type VARCHAR(50) );
Criar o tópico Kafka
Faça login no ApsaraMQ for Kafka console. Clique em Instances no painel de navegação à esquerda e selecione sua instância.
No painel de navegação à esquerda, clique em Whitelist Management e adicione o bloco CIDR do seu workspace Flink.
No painel de navegação à esquerda, clique em Topics > Create Topic. No painel direito, insira
game_sales_factno campo Name, adicione uma descrição e mantenha os valores padrão nos demais campos. Clique em OK.
Etapa 2: Criar jobs de fluxo
Job 1: Gravar chaves primárias no Kafka
O Job 1 monitora as três coleções do MongoDB. Quando game_sales é alterado, sale_id é enviado diretamente para o Kafka. Se houver mudança em uma tabela de dimensões, um lookup join em game_sales recupera os valores de sale_id afetados, que são então transmitidos para o Kafka.
O conector do MongoDB desempenha duas funções neste pipeline. No Job 1, atua como source CDC, lendo o fluxo de alterações do MongoDB. No Job 2, funciona como source de lookup, buscando o estado atual de cada documento pela chave primária. Ambas as funções utilizam a mesma configuração de conector.
Faça login no console do Realtime Compute for Apache Flink.
Na coluna Actions do seu workspace, clique em Console.
No menu de navegação à esquerda, clique em Development > ETL.
Clique em New Blank Stream Draft.
Na caixa de diálogo New Draft, insira
dwd_mongo_kafkaem Name, selecione uma versão do mecanismo e clique em Create.-
Copie o SQL abaixo para o editor. Cada uma das três instruções
INSERTcaptura independentemente as alterações de uma coleção do MongoDB e transmite os valores desale_idafetados para o sink do Kafka. Isso garante que o Hologres seja atualizado com precisão e em tempo real sempre que qualquer tabela for modificada. Armazene valores sensíveis, como strings de conexão e senhas, como variáveis em vez de codificá-los diretamente. Para mais informações, consulte Manage variables.Tempo
**Estado de
game_dimensionparagame_id = 101**T1
game_name = "SpaceInvaders"T2
Atualizado para
game_name = "SpaceInvaders_v2"-- Source: game_sales (CDC stream) CREATE TEMPORARY TABLE game_sales ( `_id` STRING, -- MongoDB auto-generated ID sale_id INT, -- Sales ID PRIMARY KEY (_id) NOT ENFORCED ) WITH ( 'connector' = 'mongodb', 'uri' = '${secret_values.MongoDB-URI}', 'database' = 'mongo_test', 'collection' = 'game_sales' ); -- Source: game_dimension (CDC stream) CREATE TEMPORARY TABLE game_dimension ( `_id` STRING, game_id INT, PRIMARY KEY (_id) NOT ENFORCED ) WITH ( 'connector' = 'mongodb', 'uri' = '${secret_values.MongoDB-URI}', 'database' = 'mongo_test', 'collection' = 'game_dimension' ); -- Source: platform_dimension (CDC stream) CREATE TEMPORARY TABLE platform_dimension ( `_id` STRING, platform_id INT, PRIMARY KEY (_id) NOT ENFORCED ) WITH ( 'connector' = 'mongodb', 'uri' = '${secret_values.MongoDB-URI}', 'database' = 'mongo_test', 'collection' = 'platform_dimension' ); -- Lookup source: game_sales (used for dimension-change joins) CREATE TEMPORARY TABLE game_sales_dim ( `_id` STRING, sale_id INT, game_id INT, platform_id INT, PRIMARY KEY (_id) NOT ENFORCED ) WITH ( 'connector' = 'mongodb', 'uri' = '${secret_values.MongoDB-URI}', 'database' = 'mongo_test', 'collection' = 'game_sales' ); -- Sink: Kafka topic that stores affected PKs CREATE TEMPORARY TABLE game_sales_fact ( sale_id INT, PRIMARY KEY (sale_id) NOT ENFORCED ) WITH ( 'connector' = 'upsert-kafka', 'properties.bootstrap.servers' = '${secret_values.Kafka-hosts}', 'topic' = 'game_sales_fact', 'key.format' = 'json', 'value.format' = 'json', 'properties.enable.idempotence' = 'false' -- Required when writing to ApsaraMQ for Kafka ); BEGIN STATEMENT SET; -- Stream PKs from game_sales changes INSERT INTO game_sales_fact (sale_id) SELECT sale_id FROM game_sales; -- Stream PKs of game_sales rows affected by game_dimension changes INSERT INTO game_sales_fact (sale_id) SELECT gs.sale_id FROM game_dimension AS gd JOIN game_sales_dim FOR SYSTEM_TIME AS OF PROCTIME() AS gs ON gd.game_id = gs.game_id; -- Stream PKs of game_sales rows affected by platform_dimension changes INSERT INTO game_sales_fact (sale_id) SELECT gs.sale_id FROM platform_dimension AS pd JOIN game_sales_dim FOR SYSTEM_TIME AS OF PROCTIME() AS gs ON pd.platform_id = gs.platform_id; END;Sobre lookup joins A cláusula
FOR SYSTEM_TIME AS OF PROCTIME()define um lookup join (um tipo de junção temporal). No momento em que uma linha do source é processada, a junção busca a linha correspondente da tabela de dimensões no MongoDB e congela esse snapshot para o resultado da junção. Se a tabela de dimensões for atualizada posteriormente, as linhas já processadas não serão afetadas. Por exemplo: Uma linhagame_salesprocessada em T1 faz junção com"SpaceInvaders". Uma linha processada em T2 faz junção com"SpaceInvaders_v2". A condição de junção égd.game_id = gs.game_idepd.platform_id = gs.platform_id. Para mais informações, consulte JOIN statements for dimension tables e Choose Kafka, Upsert Kafka, or Kafka JSON catalog. No canto superior direito, clique em Deploy. Na caixa de diálogo, clique em Confirm. Para mais informações, consulte Deploy a job.
Job 2: Reconstruir a tabela larga e fazer upsert no Hologres
O Job 2 consome valores de sale_id do tópico Kafka game_sales_fact, executa lookup joins nas tabelas de fatos e dimensões do MongoDB e faz o upsert das linhas resultantes da tabela larga no Hologres.
Siga as etapas em Job 1 para criar um novo rascunho chamado dws_kafka_mongo_holo e implantá-lo com o seguinte SQL:
-- Source: Kafka topic that provides affected PKs
CREATE TEMPORARY TABLE game_sales_fact
(
sale_id INT,
PRIMARY KEY (sale_id) NOT ENFORCED
)
WITH (
'connector' = 'upsert-kafka',
'properties.bootstrap.servers' = '${secret_values.Kafka-hosts}',
'topic' = 'game_sales_fact',
'key.format' = 'json',
'value.format' = 'json',
'properties.group.id' = 'game_sales_fact',
'properties.auto.offset.reset' = 'earliest'
);
-- Lookup source: game_sales fact table
CREATE TEMPORARY TABLE game_sales
(
`_id` STRING,
sale_id INT,
game_id INT,
platform_id INT,
sale_date STRING,
units_sold INT,
sale_amt INT,
status INT,
PRIMARY KEY (_id) NOT ENFORCED
)
WITH (
'connector' = 'mongodb',
'uri' = '${secret_values.MongoDB-URI}',
'database' = 'mongo_test',
'collection' = 'game_sales'
);
-- Lookup source: game_dimension
CREATE TEMPORARY TABLE game_dimension
(
`_id` STRING,
game_id INT,
game_name STRING,
release_date STRING,
developer STRING,
publisher STRING,
PRIMARY KEY (_id) NOT ENFORCED
)
WITH (
'connector' = 'mongodb',
'uri' = '${secret_values.MongoDB-URI}',
'database' = 'mongo_test',
'collection' = 'game_dimension'
);
-- Lookup source: platform_dimension
CREATE TEMPORARY TABLE platform_dimension
(
`_id` STRING,
platform_id INT,
platform_name STRING,
type STRING,
PRIMARY KEY (_id) NOT ENFORCED
)
WITH (
'connector' = 'mongodb',
'uri' = '${secret_values.MongoDB-URI}',
'database' = 'mongo_test',
'collection' = 'platform_dimension'
);
-- Sink: Hologres wide table
CREATE TEMPORARY TABLE IF NOT EXISTS game_sales_details
(
sale_id INT,
game_id INT,
platform_id INT,
sale_date STRING,
units_sold INT,
sale_amt INT,
status INT,
game_name STRING,
release_date STRING,
developer STRING,
publisher STRING,
platform_name STRING,
type STRING,
PRIMARY KEY (sale_id) NOT ENFORCED
)
WITH (
'connector' = 'hologres',
'dbname' = 'test',
'tablename' = 'public.game_sales_details',
'username' = '${secret_values.AccessKeyID}',
'password' = '${secret_values.AccessKeySecret}',
'endpoint' = '${secret_values.Hologres-endpoint}',
'sink.delete-strategy' = 'IGNORE_DELETE', -- Insert or update only; never delete rows
'sink.on-conflict-action' = 'INSERT_OR_UPDATE', -- Enable partial column updates
'sink.partial-insert.enabled' = 'true'
);
INSERT INTO game_sales_details (
sale_id, game_id, platform_id, sale_date, units_sold, sale_amt, status,
game_name, release_date, developer, publisher, platform_name, type
)
SELECT
gsf.sale_id,
gs.game_id,
gs.platform_id,
gs.sale_date,
gs.units_sold,
gs.sale_amt,
gs.status,
gd.game_name,
gd.release_date,
gd.developer,
gd.publisher,
pd.platform_name,
pd.type
FROM game_sales_fact AS gsf
JOIN game_sales FOR SYSTEM_TIME AS OF PROCTIME() AS gs
ON gsf.sale_id = gs.sale_id
JOIN game_dimension FOR SYSTEM_TIME AS OF PROCTIME() AS gd
ON gs.game_id = gd.game_id
JOIN platform_dimension FOR SYSTEM_TIME AS OF PROCTIME() AS pd
ON gs.platform_id = pd.platform_id;
Etapa 3: Iniciar os jobs
No Console de Desenvolvimento, escolha O&M > Deployments e inicie ambas as implantações de job.
-
Depois que ambos os jobs atingirem o estado Running, acesse HoloWeb e consulte a tabela
game_sales_details:SELECT * FROM game_sales_details;A linha inicial inserida na Etapa 1 aparece no resultado.
A consulta retorna um registro com os seguintes valores de campo:
sale_id: 0game_id: 0platform_id: 101sale_date: 2024-01-01units_sold: 500sale_amt: 2500status: 1game_name: SpaceInvadersrelease_date: 2023-06-15
Etapa 4: Atualizar e consultar dados
As alterações em game_sales e nas tabelas de dimensões no MongoDB se propagam automaticamente para o Hologres. Os exemplos a seguir demonstram cada tipo de atualização.
Atualizações da tabela de fatos
-
Insira mais cinco linhas em
game_sales:db.game_sales.insert( [ {sale_id:1,game_id:101,platform_id:1,"sale_date":"2024-01-01",units_sold:500,sale_amt:2500,status:1}, {sale_id:2,game_id:102,platform_id:2,"sale_date":"2024-08-02",units_sold:400,sale_amt:2000,status:1}, {sale_id:3,game_id:103,platform_id:1,"sale_date":"2024-08-03",units_sold:300,sale_amt:1500,status:1}, {sale_id:4,game_id:101,platform_id:3,"sale_date":"2024-08-04",units_sold:200,sale_amt:1000,status:1}, {sale_id:5,game_id:104,platform_id:2,"sale_date":"2024-08-05",units_sold:100,sale_amt:3000,status:1} ] );Consulte
game_sales_detailsno Hologres. Cinco novas linhas aparecerão.A tabela de resultados da consulta contém colunas
game_name(como SpaceInvaders, PuzzleQuest, RacingFever e AdventureLand) erelease_datealém dos campos sincronizados do MongoDB, exibindo os detalhes associados aos jogos. -
Atualize
sale_datede2024-01-01para2024-08-01:db.game_sales.updateMany({"sale_date": "2024-01-01"}, {$set: {"sale_date": "2024-08-01"}});Consulte
game_sales_details. A colunasale_daterefletirá o novo valor. -
Exclua logicamente a linha onde
sale_id = 5definindostatuscomo0:db.game_sales.updateMany({"sale_id": 5}, {$set: {"status": 0}});Consulte
game_sales_details. A colunastatusparasale_id = 5mudará para0.
Atualizações da tabela de dimensões
-
Adicione novos jogos e plataformas às tabelas de dimensões:
// New games db.game_dimension.insert( [ {game_id:105,"game_name":"HSHWK","release_date":"2024-08-20","developer":"GameSC","publisher":"GameSC"}, {game_id:106,"game_name":"HPBUBG","release_date":"2018-01-01","developer":"BLUE","publisher":"KK"} ] ); // New platforms db.platform_dimension.insert( [ {platform_id:4,"platform_name":"Steam","type":"PC"}, {platform_id:5,"platform_name":"Epic","type":"PC"} ] );Inserir dados apenas nas tabelas de dimensões não aciona a sincronização — o pipeline é impulsionado por alterações em
game_sales. Insira registros de vendas correspondentes para disparar a atualização da tabela larga:db.game_sales.insert( [ {sale_id:6,game_id:105,platform_id:4,"sale_date":"2024-09-01",units_sold:400,sale_amt:2000,status:1}, {sale_id:7,game_id:106,platform_id:1,"sale_date":"2024-09-01",units_sold:300,sale_amt:1500,status:1} ] );Consulte
game_sales_details. Duas novas linhas aparecerão com os dados enriquecidos das dimensões. -
Atualize os dados de dimensão no MongoDB:
// Update release date db.game_dimension.updateMany({"release_date": "2018-01-01"}, {$set: {"release_date": "2024-01-01"}}); // Update platform type db.platform_dimension.updateMany({"type": "PC"}, {$set: {"type": "Switch"}});Os campos atualizados se propagarão para as linhas correspondentes no Hologres.