Redis data sources

EXTERNAL_REDIS() connects FeatureQL to Redis instances for low-latency feature retrieval. Redis is typically the fastest option for real-time serving — ideal for cached profiles, feature flags, and precomputed aggregates.

Inspect a hash fixture directly

The Redis SLT executor can read selected hash fields with HMGET and assert them as typed query columns:

Redis
HMGET serving_slt:customer_hash:1 customer_id last_order_id
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINT
1101

Creating a Redis source

Before querying Redis, create a persistent connection with SOURCE_REDIS():

FeatureQL
CREATE OR REPLACE FEATURE FM.TUTORIALS.EXTERNAL.REDIS_SRC AS
    SOURCE_REDIS(
        'redis://host.docker.internal:6380'
        WITH (
            timeout='500ms',
            key_patterns=ARRAY['serving_slt:customer_hash:*']
        )
    );
Result
feature_name VARCHARstatus VARCHARmessage VARCHAR
FM.TUTORIALS.EXTERNAL.REDIS_SRCCREATEDFeature created as not exists

The connection string follows the standard redis://host:port format. The timeout parameter controls both connection and operation timeouts. key_patterns is an optimization hint that tells FeatureMesh which keys this source will access.

In a local development serving process, explicitly refresh the persisted source before querying it:

FeatureQL
REFRESH FEATURES FM.TUTORIALS.EXTERNAL.REDIS_SRC;
Result
FEATURE VARCHARKIND VARCHARSTATUS VARCHARMESSAGE VARCHAR
FM.TUTORIALS.EXTERNAL.REDIS_SRCSOURCE_REDISREFRESHED(empty)

REFRESH FEATURES waits for connector reconciliation to finish. It is deliberately gated to single-node development environments.

JSON key retrieval

The simplest pattern fetches a Redis key containing a JSON object, then parses it into a typed row:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    REDIS_KEY := 'serving_slt:customer:' || CUSTOMER_ID::VARCHAR,
    CUSTOMER_DETAILS_JSON := EXTERNAL_REDIS(KEY REDIS_KEY FROM FM.TUTORIALS.EXTERNAL.REDIS_SRC),
    CUSTOMER_DETAILS := JSON_PARSE_AS(
        CUSTOMER_DETAILS_JSON,
        TYPE 'ROW(customer_id BIGINT, last_order_id BIGINT)'
    ),
SELECT
    CUSTOMER_ID := BIND_VALUE(1),
    LAST_ORDER_ID := CUSTOMER_DETAILS[last_order_id]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINT
1101

The key expression 'customer:' || CUSTOMER_ID::VARCHAR builds a dynamic key per input. EXTERNAL_REDIS(KEY ...) returns the raw JSON string, and JSON_PARSE_AS() converts it to a structured row with type safety. From there, bracket notation extracts individual fields like CUSTOMER_DETAILS[last_order_id].

This pattern works well for complex objects — customer profiles, product catalogs, configuration records.

Hash field access

When your data lives in Redis hashes, you can skip JSON parsing entirely and fetch individual fields directly:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    REDIS_KEY := 'serving_slt:customer_hash:' || CUSTOMER_ID::VARCHAR,
SELECT
    CUSTOMER_ID := BIND_VALUE(1),
    LAST_ORDER_ID := EXTERNAL_REDIS(
        KEY REDIS_KEY
        FIELD 'last_order_id'
        FROM FM.TUTORIALS.EXTERNAL.REDIS_SRC
    )
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID VARCHAR
1101

The FIELD keyword tells FeatureQL to issue an HGET instead of a GET. This is more efficient than fetching the entire object when you only need one or two fields — less data over the wire, no parsing overhead.

Serving reads the current Redis value rather than a snapshot captured at refresh time. After changing the hash field, the same query immediately returns the new value:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    REDIS_KEY := 'serving_slt:customer_hash:' || CUSTOMER_ID::VARCHAR,
SELECT
    CUSTOMER_ID := BIND_VALUE(1),
    LAST_ORDER_ID := EXTERNAL_REDIS(
        KEY REDIS_KEY
        FIELD 'last_order_id'
        FROM FM.TUTORIALS.EXTERNAL.REDIS_SRC
    )
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID VARCHAR
1202

Table-style access with EXTERNAL_COLUMNS()

For more advanced patterns, EXTERNAL_COLUMNS(... FROM VIEW(...)) treats Redis keys as rows in a virtual table. This lets you join on the key itself:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    REDIS_VIEW := EXTERNAL_VIEW(
        `SELECT key, customer_id, last_order_id
         FROM %FM.TUTORIALS.EXTERNAL.REDIS_SRC[serving_slt:customer_hash:*]`
        ON `SELF.key='serving_slt:customer_hash:' || %CUSTOMER_ID`
        AS ROW(key VARCHAR, customer_id BIGINT, last_order_id BIGINT)
    ),
SELECT
    CUSTOMER_ID := BIND_VALUE(1),
    LAST_ORDER_ID := REDIS_VIEW[last_order_id]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINT
1101

Or join on field values within the hashes:

FeatureQL
WITH
    CUSTOMER_ID := INPUT(BIGINT),
    REDIS_VIEW := EXTERNAL_VIEW(
        `SELECT key, customer_id, last_order_id
         FROM %FM.TUTORIALS.EXTERNAL.REDIS_SRC[serving_slt:customer_hash:*]`
        ON `SELF.customer_id=%CUSTOMER_ID`
        AS ROW(key VARCHAR, customer_id BIGINT, last_order_id BIGINT)
    ),
SELECT
    CUSTOMER_ID := BIND_VALUE(1),
    LAST_ORDER_ID := REDIS_VIEW[last_order_id]
;
Result
CUSTOMER_ID BIGINTLAST_ORDER_ID BIGINT
1101

Performance

Table-style access scans all keys matching the pattern. Reserve this for small datasets — it is significantly slower than direct key or hash field lookups.

Error handling

Redis operations return NULL when they fail — whether from network issues, missing keys, timeouts, or JSON parsing errors. Use COALESCE() to provide fallback values:

CUSTOMER_TIER := COALESCE(
    EXTERNAL_REDIS(KEY 'customer_hash:' || CUSTOMER_ID::VARCHAR FIELD 'tier' FROM REDIS_SRC),
    'standard'
)
sql