Apache Paimon primary-key tables support the Aggregation Merge Engine, which lets you specify aggregate functions for non-primary-key columns at write time. For precise deduplication, Paimon provides two aggregate functions, rbm32 and rbm64, both built on RoaringBitmap. This topic describes how to combine custom UDFs with Paimon bitmap aggregation to perform precise deduplication in Spark SQL.
Background
The Paimon Aggregation Merge Engine allows you to specify aggregate functions (such as sum, max, and min) for non-primary-key columns at write time. During compaction and reads, Paimon automatically merges rows that share the same primary key by applying these aggregate functions.
For precise deduplication, Paimon provides two aggregate functions: rbm32 and rbm64. Both are built on RoaringBitmap. At write time, you serialize integer values into bitmap bytes and write them to a BINARY column. The Paimon engine automatically performs an OR merge on bitmaps with the same primary key during compaction. Because Paimon does not include a built-in UDF for converting integers to bitmap bytes, you need to implement one yourself.
This topic provides a set of sample UDFs that demonstrate how to perform precise deduplication in Spark SQL by using the Paimon bitmap aggregation feature.
Prerequisites
-
An EMR Serverless Spark workspace is created.
-
The Paimon Bitmap UDF JAR file (
paimon-spark-bitmap-1.0-SNAPSHOT.jar) is downloaded and uploaded to OSS. -
A SQL Compute session is created and started.
-
A task of the SparkSQL session type is created.
UDF reference
This topic provides four unified UDFs that are automatically compatible with both 32-bit and 64-bit bitmaps.
|
UDF |
Description |
32-bit and 64-bit behavior |
|
to_bitmap |
Converts integers to serialized bitmap bytes. |
Produces a 32-bit format when the input type is INT (for use with rbm32) and a 64-bit format when the input type is BIGINT (for use with rbm64). |
|
bitmap_count |
Returns the bitmap cardinality (the number of distinct elements). |
Automatically detects the format. |
|
bitmap_contains |
Checks whether a specified value exists in the bitmap. |
Automatically detects the format. |
|
bitmap_to_string |
Converts a bitmap to a comma-separated string. Use this UDF for debugging. |
Automatically detects the format. |
rbm32 example: deduplicate user page visits
The following example demonstrates how to use the rbm32 aggregate function to perform precise deduplication on user page visits. rbm32 is suitable for scenarios where IDs are within the INT (32-bit integer) range.
Step 1: Register the UDFs
Register the bitmap-related UDFs. This example uses temporary UDFs. You can register temporary or permanent UDFs based on your needs.
Replace oss://bucket/path/to/paimon-spark-bitmap-1.0-SNAPSHOT.jar with the actual OSS path of the JAR file.
CREATE TEMPORARY FUNCTION to_bitmap AS 'org.apache.paimon.spark.bitmap.ToBitmapUDF' USING JAR 'oss://bucket/path/to/paimon-spark-bitmap-1.0-SNAPSHOT.jar';
CREATE TEMPORARY FUNCTION bitmap_count AS 'org.apache.paimon.spark.bitmap.BitmapCountUDF' USING JAR 'oss://bucket/path/to/paimon-spark-bitmap-1.0-SNAPSHOT.jar';
CREATE TEMPORARY FUNCTION bitmap_contains AS 'org.apache.paimon.spark.bitmap.BitmapContainsUDF' USING JAR 'oss://bucket/path/to/paimon-spark-bitmap-1.0-SNAPSHOT.jar';
CREATE TEMPORARY FUNCTION bitmap_to_string AS 'org.apache.paimon.spark.bitmap.BitmapToStringUDF' USING JAR 'oss://bucket/path/to/paimon-spark-bitmap-1.0-SNAPSHOT.jar';
Step 2: Create a Paimon aggregation table
Create a Paimon primary-key table that uses the rbm32 aggregate function.
CREATE TABLE user_page_visits (
user_id INT,
page_visits BINARY
) USING paimon
TBLPROPERTIES (
'primary-key' = 'user_id',
'merge-engine' = 'aggregation',
'fields.page_visits.aggregate-function' = 'rbm32'
);
Key parameters:
-
'primary-key' = 'user_id': specifies user_id as the primary key. -
'merge-engine' = 'aggregation': enables the Aggregation Merge Engine. -
'fields.page_visits.aggregate-function' = 'rbm32': applies the rbm32 aggregate function to the page_visits column for INT values.
Step 3: Write data
Use to_bitmap to convert page IDs to bitmap bytes before writing them to the table.
-- Initial write
INSERT INTO user_page_visits VALUES
(1, to_bitmap(100, 101, 102)), -- User 1 visited pages 100, 101, 102
(2, to_bitmap(101, 103)), -- User 2 visited pages 101, 103
(3, to_bitmap(102, 104)); -- User 3 visited pages 102, 104
-- Incremental write (Paimon automatically OR-merges bitmaps for the same user_id)
INSERT INTO user_page_visits VALUES
(1, to_bitmap(103, 105)), -- User 1 also visited pages 103, 105
(2, to_bitmap(104, 106)); -- User 2 also visited pages 104, 106
Step 4: Query the deduplication results
Query how many distinct pages each user has visited.
SELECT user_id, bitmap_count(page_visits) AS unique_pages
FROM user_page_visits;
Expected results:
|
user_id |
unique_pages |
Description |
|
1 |
5 |
Pages 100, 101, 102, 103, and 105 are merged automatically. |
|
2 |
4 |
Pages 101, 103, 104, and 106 are merged automatically. |
|
3 |
2 |
Pages 102 and 104. |
You can also run the following queries for more operations.
-- Check whether a user has visited a specific page
SELECT user_id, bitmap_contains(page_visits, 101) AS visited_page_101
FROM user_page_visits;
-- View the values in the bitmap for debugging
SELECT user_id, bitmap_to_string(page_visits) AS pages
FROM user_page_visits;
rbm64 example: large ID scenario such as device fingerprints
When IDs exceed the INT range, such as device fingerprint IDs, use the rbm64 aggregate function, which is built on 64-bit RoaringBitmap. rbm64 shares the same set of UDFs as rbm32, so no additional registration is required.
If the four UDFs described above are already registered in the current session, you do not need to register them again.
Step 1: Create a table
Create a Paimon primary-key table that uses the rbm64 aggregate function. Because device IDs exceed the INT range, rbm64 is used.
CREATE TABLE app_device_visits (
app_id INT,
device_bitmap BINARY
) USING paimon
TBLPROPERTIES (
'primary-key' = 'app_id',
'merge-engine' = 'aggregation',
'fields.device_bitmap.aggregate-function' = 'rbm64'
);
Step 2: Write data
When you pass BIGINT values to to_bitmap, it automatically produces the 64-bit format.
-- Initial write
INSERT INTO app_device_visits VALUES
(1001, to_bitmap(CAST(8000000001 AS BIGINT), CAST(8000000002 AS BIGINT), CAST(8000000003 AS BIGINT))),
(1002, to_bitmap(CAST(8000000002 AS BIGINT), CAST(8000000004 AS BIGINT)));
-- Incremental write (automatically merged)
INSERT INTO app_device_visits VALUES
(1001, to_bitmap(CAST(8000000004 AS BIGINT), CAST(8000000006 AS BIGINT)));
Step 3: Query the deduplication results
SELECT app_id,
bitmap_count(device_bitmap) AS unique_devices,
bitmap_contains(device_bitmap, CAST(8000000002 AS BIGINT)) AS has_device
FROM app_device_visits;
Expected results:
|
app_id |
unique_devices |
has_device |
Description |
|
1001 |
5 |
true |
After merging: 8000000001, 8000000002, 8000000003, 8000000004, and 8000000006. |
|
1002 |
2 |
true |
Contains 8000000002 and 8000000004. |
You can also view the values in the bitmap.
SELECT app_id, bitmap_to_string(device_bitmap) AS devices
FROM app_device_visits;
References
-
For more information about the Paimon Aggregation Merge Engine, see Aggregation.
-
For more information about RoaringBitmap, see RoaringBitmap.