Todos los productos
Search
Centro de documentos

Realtime Compute for Apache Flink:Use BITMAP type for exact deduplication

Última actualización: Sep 03, 2026

COUNT DISTINCT es exacto pero costoso: Flink debe mantener cada clave distinta en el estado, y este crece de forma lineal con el volumen de datos. Las funciones aproximadas como APPROX_COUNT_DISTINCT reducen la sobrecarga, pero introducen errores estadísticos. El tipo de dato BITMAP elimina ambos problemas: ofrece el mismo recuento exacto que COUNT DISTINCT al comprimir identificadores enteros en objetos bitmap compactos y reemplazar el mantenimiento del estado con operaciones bit a bit eficientes, todo ello con una fracción del costo de memoria y CPU.

El uso de BITMAP para deduplicación es diferente de los índices bitmap que se emplean en motores de almacenamiento columnar. Ambas técnicas comparten el nombre, pero resuelven problemas distintos: los índices bitmap filtran columnas de baja cardinalidad, mientras que la deduplicación con BITMAP cuenta valores distintos.

Limitaciones

  • Solo Ververica Runtime (VVR) 11.5 y versiones posteriores admiten el tipo de dato BITMAP y sus funciones relacionadas.

  • El tipo BITMAP almacena 32-bit unsigned integers.

Conceptos clave

Funcionamiento de BITMAP

BITMAP se basa en el estándar RoaringBitmap. Cada bitmap representa un conjunto de enteros sin signo de 32 bits. En lugar de escanear filas y rastrear claves individuales en el estado de Flink, BITMAP agrega los identificadores en un objeto binario compacto y calcula la cardinalidad (el recuento de valores distintos) mediante operaciones de tiempo constante.

Dos rutas de construcción

Cada bitmap en estos ejemplos se crea de una de dos formas:

  • Agregación desde datos sin procesar: BITMAP_BUILD_AGG o BITMAP_BUILD_CARDINALITY_AGG agregan identificadores enteros de un flujo directamente en un bitmap.

  • Deserialización desde almacenamiento: BITMAP_FROM_BYTES convierte un blob binario, escrito previamente por BITMAP_TO_BYTES, nuevamente en un bitmap para realizar operaciones adicionales.

Comprender qué ruta aplica en cada ejemplo le ayudará a seguir los patrones SQL que se presentan a continuación.

Aditividad: una decisión arquitectónica clave

La forma en que genera los resultados del bitmap determina lo que los consumidores posteriores pueden hacer con los datos:

Tipo de salida

Función

¿Aditivo?

Cuándo utilizarlo

Recuento escalar (BIGINT)

BITMAP_BUILD_CARDINALITY_AGG

No: los recuentos de diferentes ventanas no se pueden sumar

Paneles en tiempo real, monitoreo sencillo

Bitmap serializado (BYTES)

BITMAP_BUILD_AGG + BITMAP_TO_BYTES

Sí: los procesos posteriores pueden unir bitmaps de cualquier granularidad temporal

Capas de almacén de datos, integración con procesamiento analítico en línea (OLAP), uniones de flujos y lotes

Un recuento escalar "no aditivo" significa que no puede sumar dos recuentos de UV de 1 minuto para obtener un UV deduplicado de 2 minutos. La deduplicación a través de límites temporales requiere los propios objetos bitmap.
La preagregación en múltiples dimensiones tiene una restricción de escalado: para admitir consultas sobre cualquier combinación de N dimensiones, debe definir 2^N grupos. Planifique sus combinaciones de dimensiones en consecuencia.

Uso

Los cuatro ejemplos siguientes avanzan de lo simple a lo complejo:

Ejemplo

Patrón

Salida

1. Deduplicación exacta en tiempo real

Reemplazo directo de COUNT DISTINCT

Recuento escalar de UV por ventana

2. Almacenamiento de bitmaps a nivel de minuto

Construcción y persistencia de bitmaps serializados

Objetos bitmap binarios para su reutilización

3. Filtrado de segmentos de usuarios

Consolidación y operaciones de conjuntos sobre bitmaps almacenados

Recuento de UV filtrado por hora

4. Análisis de retención en tiempo real

Unión de flujo y lote sobre objetos bitmap

Recuento de usuarios retenidos frente al día anterior

Para consultar la referencia completa de funciones BITMAP, consulte Funciones BITMAP.

Ejemplo 1: Deduplicación exacta en tiempo real

Cuente visitantes únicos (UV) a nivel de minuto directamente desde un flujo de eventos en tiempo real. Esta es la sustitución más sencilla de COUNT DISTINCT: ofrece el mismo resultado exacto con un menor consumo de recursos.

DDL

-- Source: real-time event stream
CREATE TEMPORARY TABLE user_events
(
      user_id     INT
      ,tag        STRING          -- Event tag
      ,event_time TIMESTAMP(3)
      ,WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
)
WITH ('connector' = 'kafka' ...)
;

-- Sink: minute-level UV statistics
CREATE TEMPORARY TABLE minute_uv
(
      ts_date    STRING
      ,ts_hour   STRING
      ,ts_minute STRING
      ,tag       STRING           -- Event tag
      ,minute_uv BIGINT           -- Deduplicated UV count
)
WITH ('connector' = 'jdbc' ...)
;

DML

INSERT INTO minute_uv
SELECT
    DATE_FORMAT(window_start, 'yyyy-MM-dd') AS ts_date,
    DATE_FORMAT(window_start, 'HH')         AS ts_hour,
    DATE_FORMAT(window_start, 'mm')         AS ts_minute,
    tag,
    -- Builds a bitmap from user_ids in the window, then immediately returns
    -- its cardinality (the exact deduplicated count).
    -- Equivalent to COUNT(DISTINCT user_id) but with lower memory overhead.
    BITMAP_BUILD_CARDINALITY_AGG(user_id)   AS uv
FROM TABLE(
    TUMBLE(
        TABLE user_events,
        DESCRIPTOR(event_time),
        INTERVAL '1' MINUTE
    )
)
GROUP BY
    window_start,
    window_end,
    tag;

Cuándo usar este patrón: paneles en tiempo real y métricas de monitoreo por minuto.

Importante

La salida es un recuento escalar y no es aditiva. No puede sumar dos recuentos de UV de 1 minuto para obtener un UV deduplicado de 2 minutos. Para agregaciones que abarcan múltiples ventanas, utilice el Ejemplo 2.

Ejemplo 2: Almacenamiento de bitmaps a nivel de minuto

En lugar de generar un recuento escalar, serialice el propio bitmap en el almacenamiento. Los trabajos posteriores pueden unir estos blobs binarios en cualquier granularidad temporal (de minutos a horas o días) sin reprocesar los datos originales del evento.

DDL

-- Source: real-time event stream (same schema as Example 1)
CREATE TEMPORARY TABLE user_events
(
      user_id     INT
      ,tag        STRING          -- Event tag
      ,event_time TIMESTAMP(3)
      ,WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
)
WITH ('connector' = 'kafka' ...)
;

-- Sink: minute-level serialized bitmaps
CREATE TEMPORARY TABLE minute_bitmaps
(
      ts_date    STRING
      ,ts_hour   STRING
      ,ts_minute STRING
      ,tag       STRING           -- Event tag
      ,minute_bm BYTES            -- Serialized bitmap (RoaringBitmap binary format)
)
WITH ('connector' = 'jdbc' ...)
;

DML

INSERT INTO minute_bitmaps
SELECT
    DATE_FORMAT(window_start, 'yyyy-MM-dd') AS ts_date,
    DATE_FORMAT(window_start, 'HH')         AS ts_hour,
    DATE_FORMAT(window_start, 'mm')         AS ts_minute,
    tag,
    -- Two-step process:
    -- 1. BITMAP_BUILD_AGG: aggregates user_ids within the tumbling window
    --    into an in-memory bitmap object.
    -- 2. BITMAP_TO_BYTES: encodes the bitmap into RoaringBitmap binary format
    --    for persistent storage.
    BITMAP_TO_BYTES(
        BITMAP_BUILD_AGG(user_id)
    ) AS user_id_bitmap
FROM TABLE(
    TUMBLE(
        TABLE user_events,
        DESCRIPTOR(event_time),
        INTERVAL '1' MINUTE
    )
)
GROUP BY
    window_start,
    window_end,
    tag;

Cuándo usar este patrón: para construir una capa intermedia de detalle de almacén de datos (DWD) o de servicio de almacén de datos (DWS), o para alimentar motores OLAP que admiten bitmaps de forma nativa, como StarRocks o Hologres.

Ventaja clave: la salida BYTES serializada conserva toda la información del conjunto. Cualquier trabajo posterior puede llamar a BITMAP_OR_AGG(BITMAP_FROM_BYTES(...)) para unir bitmaps de nivel de minuto en bitmaps de nivel de hora o de día, lo que permite consolidaciones flexibles por granularidad temporal sin acceder a los datos sin procesar originales.

Ejemplo 3: Filtrado de segmentos de usuarios

Con los bitmaps a nivel de minuto del Ejemplo 2, cuente los usuarios por hora que buscaron una palabra clave (etiqueta A) y agregaron un producto al carrito (etiqueta B), pero no completaron el pago (etiqueta C).

Todas las operaciones de conjuntos se ejecutan sobre un pequeño número de objetos bitmap en memoria, sin escaneos ni redistribuciones de tablas grandes.

DDL

-- Source: pre-computed minute-level bitmaps (produced by Example 2)
CREATE TEMPORARY TABLE minute_bitmaps
(
      ts_date    STRING
      ,ts_hour   STRING
      ,ts_minute STRING
      ,tag       STRING           -- Event tag
      ,minute_bm BYTES            -- Pre-computed serialized minute-level bitmap
)
WITH ('connector' = 'jdbc' ...)
;

-- Sink: hourly filtered UV counts
CREATE TEMPORARY TABLE hour_combined_uv
(
      ts_date  STRING
      ,ts_hour STRING
      ,tag     STRING             -- Metric label
      ,uv      BIGINT
)
WITH ('connector' = 'jdbc' ...)
;

DML

La consulta se ejecuta en tres pasos lógicos:

  1. Deserialización y consolidación: convierta los BYTES almacenados nuevamente en objetos bitmap y luego use BITMAP_OR_AGG para unir todos los bitmaps de nivel de minuto dentro de la hora; obtendrá un bitmap unido por etiqueta.

  2. Operaciones de conjuntos: aplique intersección y diferencia sobre los bitmaps horarios en memoria.

  3. Recuento: llame a BITMAP_CARDINALITY sobre el conjunto resultante.

INSERT INTO hour_combined_uv
SELECT
    ts_date,
    ts_hour,
    'A and B andnot C' AS metric_name,
    -- Step 3: Count the elements in the final bitmap.
    BITMAP_CARDINALITY(
        -- Step 2: Set operations -- (Tag A ∩ Tag B) − Tag C.
        BITMAP_ANDNOT(
            BITMAP_AND(hour_bm_a, hour_bm_b),
            hour_bm_c
        )
    ) AS uv
FROM (
    SELECT
        ts_date,
        ts_hour,
        -- Step 1: Deserialize stored BYTES and union all minute-level bitmaps
        -- within the hour into a single hourly bitmap per tag.
        BITMAP_OR_AGG(BITMAP_FROM_BYTES(minute_bm))
            FILTER (WHERE tag = 'A') AS hour_bm_a,

        BITMAP_OR_AGG(BITMAP_FROM_BYTES(minute_bm))
            FILTER (WHERE tag = 'B') AS hour_bm_b,

        BITMAP_OR_AGG(BITMAP_FROM_BYTES(minute_bm))
            FILTER (WHERE tag = 'C') AS hour_bm_c
    FROM minute_bitmaps
    WHERE tag IN ('A', 'B', 'C')
    GROUP BY
        ts_date,
        ts_hour
);

Cuándo usar este patrón: análisis de segmentos de usuarios, análisis de embudos o cualquier escenario que requiera un filtrado flexible entre dimensiones.

Ventaja clave: la lógica de negocio está completamente desacoplada de la generación de datos. Para agregar o cambiar una condición de filtro, modifique solo la consulta SQL; no se requieren cambios en el pipeline de origen.

Ejemplo 4: Análisis de retención en tiempo real

Calcule el recuento en tiempo real de visitantes recurrentes para una página específica: usuarios que aparecieron hoy y también aparecieron ayer.

Este proceso combina un flujo de bitmaps en tiempo real a nivel de minuto (datos de hoy, actualizados continuamente) con un bitmap precalculado a nivel de día desde el almacenamiento (datos de ayer, estáticos). La intersección proporciona el recuento de usuarios retenidos en milisegundos, sin necesidad de uniones de tablas grandes.

DDL

-- Source 1: real-time minute-level bitmaps for today (produced by Example 2)
CREATE TEMPORARY TABLE minute_bitmaps
(
      ts_date    STRING
      ,ts_hour   STRING
      ,ts_minute STRING
      ,tag       STRING           -- Page name
      ,minute_bm BYTES            -- Pre-computed serialized minute-level bitmap
)
WITH ('connector' = 'jdbc' ...)
;

-- Source 2: pre-computed day-level bitmaps from storage (historical data)
CREATE TEMPORARY TABLE daily_bitmaps
(
      ts_date   STRING
      ,tag      STRING            -- Page name
      ,daily_bm BYTES             -- Pre-computed serialized day-level bitmap
)
WITH ('connector' = 'jdbc' ...)
;

-- Sink: real-time retention metrics
CREATE TEMPORARY TABLE realtime_retention_metrics
(
      ts_date         STRING
      ,tag            STRING      -- Page name
      ,retained_users BIGINT      -- Users present both today and yesterday
      ,total_users    BIGINT      -- Total visitors yesterday (retention denominator)
      ,update_time    TIMESTAMP(3)
      ,PRIMARY KEY (ts_date, tag) NOT ENFORCED
)
WITH ('connector' = 'jdbc' ...)
;

DML

La consulta se ejecuta en tres pasos lógicos:

  1. Agregación en tiempo real (subconsulta T): une continuamente los fragmentos de bitmap a nivel de minuto del día de hoy en un bitmap acumulativo "hasta la fecha actual".

  2. Unión de flujo y lote: une el bitmap en tiempo real de hoy con el bitmap diario estático de ayer desde el almacenamiento.

  3. Intersección: BITMAP_AND sobre los dos bitmaps produce el conjunto de usuarios presentes en ambos días.

INSERT INTO realtime_retention_metrics
SELECT
    T.ts_date,
    T.tag,
    -- Metric 1: retained users -- appeared today AND yesterday.
    BITMAP_CARDINALITY(
        BITMAP_AND(
            T.today_bm,                           -- Real-time bitmap for today
            BITMAP_FROM_BYTES(Y.daily_bm)         -- Yesterday's historical bitmap
        )
    ) AS retained_users,

    -- Metric 2: yesterday's total visitors (retention denominator).
    BITMAP_CARDINALITY(
        BITMAP_FROM_BYTES(Y.daily_bm)
    ) AS total_users,

    CURRENT_TIMESTAMP AS update_time
FROM (
    -- Subquery T: aggregate today's minute-level slices in real time
    -- into a single cumulative bitmap for the current day.
    SELECT
        ts_date,
        tag,
        BITMAP_OR_AGG(BITMAP_FROM_BYTES(minute_bm)) AS today_bm
    FROM minute_bitmaps
    GROUP BY ts_date, tag
) T
-- Stream-batch join: match each real-time record with the previous day's bitmap.
INNER JOIN daily_bitmaps Y
    ON T.tag = Y.tag
    AND CAST(T.ts_date AS DATE) = CAST(Y.ts_date AS DATE) + INTERVAL '1' DAY;

Cuándo usar este patrón: monitoreo en tiempo real de la respuesta de los usuarios a promociones, o análisis de la fidelidad de usuarios activos día contra día.

Ventajas clave:

  • Rapidez: las soluciones tradicionales unen dos tablas con cientos de millones de filas para calcular COUNT DISTINCT. BITMAP realiza el mismo cálculo sobre unos pocos objetos binarios en milisegundos.

  • Eficiencia de almacenamiento: los datos históricos, como daily_bitmaps, residen en almacenamiento externo (Hadoop Distributed File System [HDFS], OSS o JDBC), lo que mantiene pequeño el estado de Flink.

Pasos siguientes

  • Funciones BITMAP: referencia completa de funciones que incluye sintaxis, tipos de parámetros y valores de retorno.