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:
HMGET serving_slt:customer_hash:1 customer_id last_order_id| CUSTOMER_ID BIGINT | LAST_ORDER_ID BIGINT |
|---|---|
| 1 | 101 |
Creating a Redis source
Before querying Redis, create a persistent connection with SOURCE_REDIS():
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:*']
)
);| feature_name VARCHAR | status VARCHAR | message VARCHAR |
|---|---|---|
| FM.TUTORIALS.EXTERNAL.REDIS_SRC | CREATED | Feature 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:
REFRESH FEATURES FM.TUTORIALS.EXTERNAL.REDIS_SRC;| FEATURE VARCHAR | KIND VARCHAR | STATUS VARCHAR | MESSAGE VARCHAR |
|---|---|---|---|
| FM.TUTORIALS.EXTERNAL.REDIS_SRC | SOURCE_REDIS | REFRESHED | (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:
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]
;| CUSTOMER_ID BIGINT | LAST_ORDER_ID BIGINT |
|---|---|
| 1 | 101 |
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:
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
)
;| CUSTOMER_ID BIGINT | LAST_ORDER_ID VARCHAR |
|---|---|
| 1 | 101 |
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:
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
)
;| CUSTOMER_ID BIGINT | LAST_ORDER_ID VARCHAR |
|---|---|
| 1 | 202 |
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:
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]
;| CUSTOMER_ID BIGINT | LAST_ORDER_ID BIGINT |
|---|---|
| 1 | 101 |
Or join on field values within the hashes:
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]
;| CUSTOMER_ID BIGINT | LAST_ORDER_ID BIGINT |
|---|---|
| 1 | 101 |
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'
)