Search docs.molo17.com
    Configuration guide Connectors

    Set up a Redis Streams target for CDC events

    A Redis stream is an append-only log. The Redis Streams target does not keep a copy of your tables in Redis: it appends one entry for every change it receives, so a consumer can read the history of the table in order. This guide creates a Redis user that can only append to your streams, creates the target, copies a PostgreSQL table and follows its live changes, shows you what an insert, an update, a delete and a truncate look like in the stream, and covers the settings that decide the shape and the size of the entries.

    In this guide

    You will learn how to create a Redis ACL user with the three commands the agent needs, how to create the Redis Streams target in the Control Plane, and how a source table becomes a stream key. You will run a snapshot and live changes, read the entries with XLEN and XRANGE, and check how every operation is written. You will switch on the before image, choose the flat or the JSON layout, and cap the length of a stream. You will also reproduce the errors that are most likely on a new setup and read how to fix them.

    Quick information

    Goal

    Append the snapshot and the change events of a PostgreSQL table to a Redis stream, and read them back

    Audience

    Data engineers and Redis administrators who feed a stream consumer

    Level

    Intermediate

    Time required

    About 40 minutes

    Area

    Redis → ACL; Control Plane → Pipelines → Redis Streams target → Base settings and Advanced settings; entity → Field mapping → Target Stream

    Applies to

    Gluesync 2.3

    Before you begin

    Make sure that:

    1. You have a Redis 7.4 or later server (Community or Enterprise) and you can run redis-cli as an administrator. Redis Streams is a target only: you need another database as the source. This guide was tested with Redis 7.4 and a PostgreSQL 16 source;

    2. Core Hub can open a connection to the Redis host and port;

    3. You can sign in to the Control Plane with a role that can create pipelines;

    4. You have a source table with a primary key. This guide uses the PostgreSQL table public.customers (id integer primary key, and the text columns name, surname, address, gender, phone and email) with 20 rows;

    5. The source database is ready for change data capture. For PostgreSQL that means wal_level set to logical, a user with the REPLICATION attribute that can read the table, and the wal2json plug-in. See the PostgreSQL CDC reference for the exact statements.

    The streams do not have to exist. Redis creates a stream the first time the agent appends to it, and the agent never runs a data definition command.

    Guide steps

    Create a Redis user that can only append to your streams

    CLI

    The agent needs XADD to write, PING to test the connection, and INFO to read the server version. Give it only those commands, on only the keys it will write. This guide puts every stream under the prefix gs, so the key pattern is gs:*.

    1. Create the user. Replace the password. The reset keyword starts from a user with no commands, no keys and no password:

      redis-cli ACL SETUSER gluesync reset on '>change-me-strong' '~gs:*' +xadd +ping +info
    2. Check what the user can and cannot do:

      redis-cli --user gluesync --pass change-me-strong --no-auth-warning PING
      redis-cli --user gluesync --pass change-me-strong --no-auth-warning XADD gs:probe '*' a 1
      redis-cli --user gluesync --pass change-me-strong --no-auth-warning XADD other:probe '*' a 1
    3. Remove the probe stream with an administrator account:

      redis-cli DEL gs:probe
    Expected result

    PING answers PONG. The first XADD answers with an entry id such as 1793832350982-0. The second answers NOPERM No permissions to access a key, because other:probe does not match gs:*.

    Redis writes an entry to its ACL log when the agent connects and Redis refuses the optional HELLO and CLIENT SETINFO commands. The agent works without them. Add the HELLO and CLIENT commands to the ACL SETUSER line if you do not want those entries in ACL LOG.

    The agent also accepts a Redis server without a password. In that case switch on Disable auth in step 3 and skip the user.

    Prepare the source table

    SQL

    Run this on the PostgreSQL source. It is optional, but it makes the before image of step 7 complete. With the default replica identity, PostgreSQL sends only the key of the old row, so the before image of an update would carry the key and not the other columns.

    ALTER TABLE public.customers REPLICA IDENTITY FULL;
    Expected result

    PostgreSQL answers ALTER TABLE.

    Create the pipeline and the Redis Streams target

    UI

    1. In the Control Plane, select Create your first pipeline, or the + button of the pipeline list if you already have pipelines. The Agents setup wizard opens;

    2. In 1. Setup source, choose the PostgreSQL CDC agent tile and fill Base settings: Host name / Address, Port, Database name, Username and Password. Select Test and continue. The source appears with a green check mark. Select Next;

    3. In 2. Setup target(s), choose the Redis Streams tile under Event streaming. In Base settings fill:

      • Host name / Address: your Redis host;

      • Port: 6379, or the port of your server;

      • Database index: 0, or the logical database you want. Redis Cluster ignores it;

      • Username and Password: the user of step 1;

    4. Open Advanced settings, select Add property, choose the key Stream key prefix and enter gs as the value;

    5. Select Test and continue. The target appears with a green check mark. Select Next, enter a Pipeline name and select Finish.

    Setup target wizard step with the Redis Streams agent
    Figure 1. The Redis Streams target connection in the setup wizard.
    Expected result

    The pipeline appears in the HEALTHY group of the pipeline list, with the PostgreSQL and the Redis icons. Test and continue connects to Redis, sends PING and requires PONG, so a green check mark proves the host, the port and the credentials, but not the key pattern of the user. Step 6 shows what a key pattern mistake looks like.

    The Advanced settings tab lists every setting of the agent. The Redis Streams target reference describes each one, and the REST call for the same setup.

    Add the entity and name the stream

    UI

    1. In the pipeline, select Entities. The Add entities page opens with the Object browser. Expand the source schema public and select the table customers. The Field mapping dialog opens;

    2. Look at the Target side. Target Stream is the name of the stream and it starts as the table name, customers. Target Key namespace (optional) is a scope that goes into the stream key. Leave both as they are;

    3. Select Confirm, then Confirm and continue.

    Field mapping dialog with the PostgreSQL customers table as source and the Redis Streams target with Target Key namespace empty
    Figure 2. The Field mapping dialog of the Redis Streams target. There is no table to create: the stream is named by Target Stream.

    The agent builds the Redis key for each entity in this order:

    1. The entity Stream key override, if you set one, exactly as written;

    2. The Stream key template, if you set one, with {prefix}, {scope}, {collection} and {entity} replaced;

    3. Otherwise <prefix>:<scope>:<collection>, skipping every part that is empty.

    With the prefix gs, no key namespace and the stream customers, the key is gs:customers. The ACL pattern of step 1 must match every key you build this way.

    Expected result

    The entity appears in the pipeline on Hold as public.customers. No object was created in Redis yet.

    Run the snapshot and read the stream

    UI + CLI

    1. In the pipeline, select the play button of the entity. The Sync changes (CDC) dialog opens. Start from offers Where it stopped, Now and Full copy. Keep Full copy: it copies all rows first and then follows the changes. The Speed section shows Standard, and its Writing part says that this target has no bulk load, so the agent writes with the pipelined XADD batches of step 8;

    2. Select Start entity. The entity becomes Active and Last rows shows 20;

    3. Read the stream:

      redis-cli TYPE gs:customers
      redis-cli XLEN gs:customers
      redis-cli XRANGE gs:customers - + COUNT 1
    Expected result

    TYPE answers stream, and XLEN answers 20: one entry for each row. The first entry looks like this. Redis assigns the entry id, and _ts_ms and _transaction differ on your system:

    _op update
    _snapshot true
    _entity customers
    _key customers::1
    _keys {"id":1}
    _ts_ms 1793831650427
    _source_ts_ms 1793831650420
    _transaction {"type":"HexTransactionId","value":"0/4834C38","timestamp":1793831650420}
    id 1
    name Name-1
    surname Surname-1
    address 1 Integration Test Street
    gender Male
    phone +39-555-0001
    email customer-1@example.com

    A snapshot row is written as _op update with _snapshot true. Every value is text. _key is the document id of the row (customers::1), and it is not the stream key.

    A stream keeps every entry. If you start the snapshot again with Full copy, the agent appends the 20 rows again and XLEN grows to 40. Delivery is at-least-once and XADD never rejects a duplicate. A consumer must treat the same _key seen twice as normal.

    Follow live changes and read every operation

    SQL + CLI

    The entity is now Active and follows the source. Change the source, then read the end of the stream.

    1. Insert, update and delete one row, with a few seconds between the statements:

      INSERT INTO customers (id, name, surname, address, gender, phone, email)
      VALUES (1001, 'CDC', 'Insert', '1 Stream Lane', 'Female', NULL, 'cdc@example.com');
      UPDATE customers SET name = 'UpdatedName' WHERE id = 1001;
      DELETE FROM customers WHERE id = 1001;
    2. Read the three newest entries, newest first:

      redis-cli XREVRANGE gs:customers + - COUNT 3
    3. Remove every row, then read the last entry:

      TRUNCATE customers;
      redis-cli XREVRANGE gs:customers + - COUNT 1
    Expected result

    XLEN grows by one entry for each statement. The insert entry has _op insert and _snapshot false. The phone column is NULL, so it is not written as a field and its name is listed in _nulls ["phone"]. The update entry has _op update and the new name, UpdatedName. The delete entry has _op delete, _key customers::1001 and only the key column, id 1001: the earlier entries of the row stay in the stream. The truncate writes one marker with only _op truncate, _snapshot false, _entity customers and _ts_ms. It has no key and no row, and the earlier entries stay.

    Nothing removes a stream entry except the trim setting of step 7. A delete is an event, not an erase.

    Add the before image, the JSON layout and a length cap

    UI

    These settings change the entries of the next change. They are saved with the target connection, and the entity does not need to restart.

    1. In the pipeline list, select the Redis icon of the pipeline. The Edit target(s) page opens. Select Advanced settings;

    2. Select Add property and add Include before image. Switch it on. An update entry then carries the old column values, and a delete entry carries the row the source sent;

    3. Select Add property and add Trim strategy. Choose MAXLEN. Add Trim MAXLEN (entries kept per stream) with the value 30. Add Approximate trimming (~) and switch it off, so Redis keeps exactly 30 entries and does not wait to trim in whole blocks;

    4. Select Test and continue;

    5. Insert 40 rows and update one of them on the source, then read the length and the last entry:

      INSERT INTO customers
      SELECT n, 'N' || n, 'S' || n, 'A' || n, 'Male', '+39-' || n, 'e' || n || '@example.com'
      FROM generate_series(1, 40) AS n;
      UPDATE customers SET phone = '+39-555-9999' WHERE id = 2;
      redis-cli XLEN gs:customers
      redis-cli XREVRANGE gs:customers + - COUNT 1
    Advanced settings of the Redis Streams target with Stream key prefix gs
    Figure 3. The Advanced settings of the target: stream key prefix, include before image, trim strategy, trim length and exact trimming.
    Expected result

    XLEN answers 30, no matter how many entries were appended. The update entry has a _before field with the old row as a JSON object, for example {"id":2,"name":"N2",…​,"phone":"+39-2","email":"e2@example.com"}, and the new values as fields.

    To change the shape of the entry, add Payload layout and choose JSON. The agent then writes one field, payload, whose value is one JSON document:

    {"op":"update","snapshot":false,"entity":"customers","key":"customers::5","ts_ms":1793832140234,"source_ts_ms":1793832140231,"transaction":"{\"type\":\"HexTransactionId\",\"value\":\"0/4848070\",\"timestamp\":1793832140231}","keys":{"id":5},"after":{"id":5,"name":"N5","surname":"S5","address":"A5","gender":"Male","phone":"+39-555-7777","email":"e5@example.com"},"before":{"id":5,"name":"N5","surname":"S5","address":"A5","gender":"Male","phone":"+39-5","email":"e5@example.com"}}

    Choose FLAT when a consumer reads fields one by one. Choose JSON when it parses a document, and when it needs real numbers and booleans and null values in the row.

    Fix the errors you are most likely to meet

    CLI + UI

    Each error below was reproduced on the pipeline of this guide.

    1. Enter a wrong password and select Test and continue. The page shows Failed to save with Unable to connect to redis/<unresolved>:6379 (with your host name and port). The text does not say why. The Core Hub log has the cause: RedisCommandExecutionException: WRONGPASS invalid username-password pair or user is disabled. Fix: enter the password of the Redis user;

    2. Enter a host that does not exist. The page shows Unable to connect to <name>/<unresolved>:6379, and the log shows UnknownHostException: Failed to resolve '<name>'. Fix: enter a name that Core Hub resolves. Core Hub has to resolve it, not the machine of your browser;

    3. Enter a port with no Redis behind it. The same message appears, and the log shows Connection refused. Fix: enter the port of your server;

    4. Set the stream key prefix to gs while the user is limited to a different pattern, for example ~stream:*, and start the entity. The connection test passes, then the entity goes to Error and the notification Redis command execution error shows NOPERM No permissions to access a key. Fix: let the user write the key, for example redis-cli ACL SETUSER gluesync resetkeys '~gs:*';

    5. Set Trim strategy to MAXLEN and Trim MAXLEN (entries kept per stream) to 0, and change a row on the source. The entity goes to Error with Trim MAXLEN must be greater than zero when the trim strategy is MAXLEN, got 0. Fix: enter a value greater than zero.

    Pipeline page with the Customers to Redis Streams pipeline
    Figure 4. The entity in Error after the key pattern of the Redis user did not cover the stream key.
    Expected result

    After the fix of a permission error or a setting error, you do not need to restart the entity. Core Hub restarts an entity in Error by itself, and it writes again within about half a minute. In the permission case the restart ran a new snapshot and appended the 20 rows again, as the warning of step 5 explains.

    Data types

    The agent accepts all Gluesync data types. The target type shown in the Field mapping dialog is only a label: nothing is created in Redis, and every value is written as text. These are the entries for a PostgreSQL table with many types, from the flat layout:

    Source type Target type in the dialog Value written in the flat layout

    integer, smallint, bigint

    int, short, long

    Decimal text, such as 127 and 9007199254740991

    numeric(10,2)

    bigdecimal (10, 2)

    Plain decimal text without trailing zeros of the scale: 23.5

    numeric(30,0)

    bigdecimal

    Every digit, with no exponent: 123456789012345678901234567890

    real, double precision

    float, double

    1.5, 2.25

    boolean

    boolean

    true

    bytea

    binary

    Lowercase hex, 127c02fbdf. Add Binary encoding with the value BASE64 for Base64

    date

    date

    2024-06-10

    time

    time (6)

    12:44:45

    timestamp

    datetime (6)

    ISO-8601 without an offset: 2023-12-23T13:38:35

    timestamptz

    offset_date_time

    ISO-8601 with the offset: 2024-06-10T12:44:33Z

    time with time zone

    offset_time

    12:44:45Z

    integer[]

    array

    JSON text: [1,-2,3]

    jsonb, json

    string

    The JSON text as PostgreSQL prints it: {"color": "red", "count": "two"}

    uuid, inet, bit(4)

    string, string, string (4)

    Text: a2fbcd2f-1ef0-416d-bd8b-4a829c4f09ad, 192.168.1.1, 1010

    A NULL column is not written. Its name is listed in _nulls (_nulls ["nil"] in the test above). If a consumer must see every column, add Null values (flat layout) with the value SENTINEL: the agent then writes the text __NULL__, which you can change with Null sentinel (flat layout, SENTINEL mode). The JSON layout always writes null.

    The agent reference lists the rules for each Gluesync type in both layouts.

    Performance and size settings

    This target has no bulk load: the start dialog says so. The agent sends the XADD commands in batches and waits for each batch. These settings matter most:

    Setting Default When to change it

    Pipeline batch size (XADD per round trip)

    1000

    The number of XADD commands sent before the agent waits. Raise it on a fast network. Lower it when a batch times out. You can override it for one entity in the entity configuration

    Trim strategy

    NONE

    A stream grows without limit with NONE. Use MAXLEN to keep a number of entries, or MINID to keep the entries of the last number of seconds

    Approximate trimming (~)

    On

    Redis trims in whole blocks and keeps a little more than the limit, at a lower cost. Switch it off for an exact limit, as in step 7

    Command timeout (seconds)

    60

    The time PING and each XADD batch wait. Raise it on a slow link. A timeout or a lost connection is retried by the agent

    Read Tune replication throughput for the settings that apply to every target. Every setting, TLS and Redis Cluster mode (Additional hosts) are in the agent reference.

    Verify the result

    You have set up the Redis Streams target if:

    • The Redis user can run XADD, PING and INFO, and XADD only on the keys of your streams;

    • redis-cli TYPE <stream key> answers stream, and XLEN matches the number of source rows after the first snapshot;

    • An insert, an update and a delete on the source each append one entry, with _op insert, _op update and _op delete;

    • A truncate appends one _op truncate marker and removes nothing;

    • With the before image on, an update entry has _before;

    • With MAXLEN and exact trimming, XLEN stays at the length you set.

    Troubleshooting

    Problem Possible cause What to do

    Failed to save with Unable to connect to <host>/<unresolved>:<port>

    Wrong password or user, an unknown host, or nothing listening on the port. The message is the same for all three

    Read the Core Hub log: WRONGPASS is a bad user or password, UnknownHostException is a name that Core Hub cannot resolve, and Connection refused is a wrong port or a closed firewall

    The entity goes to Error with NOPERM No permissions to access a key

    The key pattern of the Redis user does not match the stream key, for example after you changed the prefix

    Add the key pattern: ACL SETUSER <user> resetkeys '~<prefix>:*'. The entity restarts by itself

    NOPERM User <user> has no permissions to run the '<command>' command

    The user lacks XADD, PING or INFO

    Grant them: ACL SETUSER <user> +xadd +ping +info

    The entity goes to Error with Trim MAXLEN must be greater than zero when the trim strategy is MAXLEN, got 0

    Trim strategy is MAXLEN and the length is 0. The agent checks a setting when it writes, not when you save it

    Enter a length greater than zero, or set Trim strategy to NONE

    XLEN is twice the number of rows

    The snapshot ran twice: you chose Full copy again, or the entity restarted after an error

    Expected. A stream never rejects a duplicate. Remove the stream with DEL <key> before a clean start, and start with Full copy once

    A delete or update entry has no old column values

    Include before image is off, or the table has the default replica identity

    Switch on Include before image and run ALTER TABLE …​ REPLICA IDENTITY FULL on the source

    A column does not appear in the entry

    The value is NULL. The flat layout omits it and lists its name in _nulls

    Read _nulls, or use the SENTINEL mode or the JSON layout

    The write fails because a column has the same name as a metadata field

    A source column is named _op, _key or another metadata field, or _nulls

    Change Metadata field prefix (flat layout), for example to meta_

    Contact MOLO17 Support if you need assistance with a Redis Streams target.