Chapter 09 · Redis Search: Indexes, Text/Numeric/Tag/Geo Fields, Querying, and Aggregation

Create Search Indexes over Hash/JSON Data and Understand Schema/Prefix Scope

Create bounded Redis Search indexes over JSON and Hash data, prove prefix/filter scope, and separate source keys from secondary index state.

Intermediate150–180 minutesIndex scope labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart can already fetch a product when its Redis key is known, but catalog pages need queries such as “outdoor products under a price ceiling near this store.” Redis Search solves that access-path problem by maintaining a secondary index over selected HASH or JSON fields. The index is derived state: source keys remain the application records, while the Search schema defines which attributes become searchable, filterable, sortable, or geospatial.

01

Distinguish direct Redis key access from a Search secondary index and explain why both can coexist.

02

Create bounded Search indexes over JSON and Hash sources with explicit PREFIX and FILTER scope.

03

Map source fields or JSONPath expressions to TEXT, TAG, NUMERIC, and GEO index attributes.

04

Use FT.INFO and deliberate inside/outside fixtures to prove what is and is not indexed.

05

Reason about index memory, write amplification, ACL boundaries, initial scans, and schema evolution before production use.

Exact lab baseline

All Chapter 09 mandatory labs reuse the disposable Chapter 01 environment: Redis Open Source 8.10.1 from Docker Official Image redis:8.10.1, container atlasmart-redis-ch01, standalone topology, host publication 127.0.0.1:6379, TLS disabled only because traffic stays on loopback, default ACL user disabled, named users atlasmart-app and academy-admin, logical database 0, AOF with appendfsync everysec plus RDB snapshots, persistent /data, and no explicit maxmemory limit or eviction policy. Redis 8 integrates Search and JSON into Redis Open Source; the mandatory path needs no separate historical Redis Stack image or paid service. Source writes use the restricted application user where possible; Search/index administration and evidence commands use the disposable academy-admin user because the Chapter 01 application ACL was intentionally not broadened for Search administration. Fixtures stay under atlasmart:ch09:*. Search commands are version-sensitive and belong to the @search command category in current Redis. No lesson changes host firewall rules, managed-service configuration, or unrelated data.

1. Primary key access and secondary Search access answer different questions

Primary key access means the application already knows a Redis key such as atlasmart:ch09:product:1001 and retrieves that record directly. A secondary index derives searchable structures from source fields so the application can locate keys from predicates it did not know in advance. Search does not replace the source record; it provides another access path.

Question Direct key access Redis Search
“Give me product 1001.” Use the known Redis key; no secondary predicate is needed. Usually unnecessary overhead.
“Find outdoor products under 130 AZN.” Application would need some other index or a scan. TAG + NUMERIC predicates express this directly.
“Find products whose description contains runner.” A key lookup cannot discover unknown keys. TEXT analysis and the inverted index provide discovery.
Source of business truth The HASH/JSON key plus application contract. Derived index state; verify readiness and indexing errors.

2. Prove the server feature surface before freezing commands

Redis 8 integrates Search and JSON into Redis Open Source, but a lesson should still prove the target server actually exposes the commands. This catches wrong endpoints, old images, compatible-but-different services, ACL denials, and accidental connections to a Redis instance that does not match the course baseline.

redis-cli · version and Search capability evidence
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO serverdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO FT.CREATEdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO FT.SEARCHdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO JSON.GETdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin ACL WHOAMI

Expected evidence: redis_version:8.10.1 appears in the server section, each command lookup returns metadata rather than nil, and ACL WHOAMI identifies academy-admin. This proves command availability on this endpoint; it does not prove that a particular index exists or is populated.

3. Build deterministic JSON source fixtures and a deliberate out-of-scope key

The prefix is part of the data contract. The first three keys share atlasmart:ch09:product:; the fourth deliberately does not. All values are bounded synthetic JSON documents. Monetary values stay as integer cents so this Search lesson does not quietly reintroduce floating-point money.

redis-cli · bounded JSON fixtures
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch09:product:1001 atlasmart:ch09:product:1002 atlasmart:ch09:product:1003 atlasmart:ch09:outside:product:9001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch09:product:1001 '$' '{"schemaVersion":2,"sku":"SKU-1001","name":"Trail Running Backpack","description":"Lightweight running backpack for trail and city travel","category":["outdoor","travel"],"brand":"AtlasPeak","priceCents":12990,"stock":12,"location":"49.8671,40.4093"}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch09:product:1002 '$' '{"schemaVersion":2,"sku":"SKU-1002","name":"City Runner Pack","description":"Compact runner backpack for commuting and urban travel","category":["urban","travel"],"brand":"AtlasPeak","priceCents":9990,"stock":0,"location":"49.8920,40.3777"}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch09:product:1003 '$' '{"schemaVersion":2,"sku":"SKU-1003","name":"Trail Bottle","description":"Insulated bottle for hiking and trail running","category":["outdoor","hydration"],"brand":"NorthSpring","priceCents":3490,"stock":40,"location":"49.8500,40.4000"}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch09:outside:product:9001 '$' '{"schemaVersion":2,"sku":"SKU-9001","name":"Outside Prefix Backpack","description":"This key proves prefix scope","category":["outdoor"],"brand":"ScopeTest","priceCents":1,"stock":1,"location":"49.8671,40.4093"}'

Verify source truth directly before creating any index. JSON.GET against all four keys should succeed. At this point no Search result has been proven.

4. FT.CREATE chooses one source type and an explicit prefix

FT.CREATE selects a source type with ON HASH or ON JSON. One index does not silently mix the two storage types. For JSON, the schema identifiers are JSONPath expressions; AS gives them concise query attributes. PREFIX limits which Redis keys are candidates for indexing. Omitting it defaults to all keys, which is rarely a good production default in a shared database.

redis-cli · create the JSON product index
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.CREATE atlasmart-ch09-products-json-idx ON JSON PREFIX 1 atlasmart:ch09:product: SCHEMA '$.sku' AS sku TAG SORTABLE '$.name' AS name TEXT WEIGHT 2.0 '$.description' AS description TEXT '$.category[*]' AS category TAG SORTABLE '$.brand' AS brand TAG SORTABLE '$.priceCents' AS priceCents NUMERIC SORTABLE '$.stock' AS stock NUMERIC SORTABLE '$.location' AS location GEO '$.schemaVersion' AS schemaVersion NUMERIC

The schema makes name and description full-text fields, category/brand/sku exact-match tags, prices and stock numeric fields, and location a point-valued GEO field. SORTABLE is added only to attributes we plan to sort or aggregate efficiently because sortable values consume additional index memory.

5. FT.INFO proves index scope and readiness better than assumptions

After index creation, inspect the index rather than immediately assuming all source documents are searchable. FT.INFO exposes schema/configuration plus metrics such as num_docs, indexing, percent_indexed, hash_indexing_failures, term/record counts, and index-memory components. Field names can evolve, so inspect the actual 8.10.1 reply rather than parsing an undocumented array position.

redis-cli · inspect index state and prove prefix exclusion
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.INFO atlasmart-ch09-products-json-idxdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '*' NOCONTENT LIMIT 0 0 DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@sku:{SKU-9001}' NOCONTENT DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch09:outside:product:9001 '$.sku'

With the deterministic fixture, the Search count should be three once indexing is ready, the outside-prefix SKU should return zero Search results, and direct JSON access still proves the fourth source key exists. That difference is the evidence for PREFIX scope—not a missing source record.

6. Wrong approach: create the right schema over the wrong prefix

A syntactically valid index can be operationally useless if its prefix does not match the application namespace. That failure is dangerous because FT.CREATE still returns OK.

redis-cli · controlled wrong-prefix failure and diagnosis
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.CREATE atlasmart-ch09-wrong-prefix-idx ON JSON PREFIX 1 atlasmart:ch09:wrong: SCHEMA '$.name' AS name TEXTdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-wrong-prefix-idx '*' NOCONTENT LIMIT 0 0 DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.INFO atlasmart-ch09-wrong-prefix-idxdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.DROPINDEX atlasmart-ch09-wrong-prefix-idx

The zero-document result is expected. Diagnose by comparing index prefix configuration with real key names, then drop the disposable wrong index. Never “repair” this by broadening to the default all-key scope without first designing namespaces and cardinality.

7. FILTER narrows candidates after source-type/prefix selection

FILTER is an indexing-time predicate using the Search aggregation expression language. It is distinct from a query-time filter: documents that do not satisfy the index FILTER are not represented in that index. The following HASH fixture makes the distinction concrete and uses a documented field predicate.

redis-cli · HASH index with active-only FILTER
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch09:offer:1 atlasmart:ch09:offer:2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch09:offer:1 name "Trail Pack Offer" status active priceCents 11990docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch09:offer:2 name "Draft Pack Offer" status draft priceCents 10990docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.CREATE atlasmart-ch09-active-offers-idx ON HASH PREFIX 1 atlasmart:ch09:offer: FILTER '@status=="active"' SCHEMA name TEXT status TAG priceCents NUMERICdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-active-offers-idx '*' NOCONTENT LIMIT 0 0 DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGETALL atlasmart:ch09:offer:2

The Search index should contain only the active offer while the draft hash remains directly readable. FILTER logic, source schema, and later mutations therefore belong in migration tests; a field rename can remove documents from index scope without deleting them from Redis.

8. Existing keys, future writes, and initial-scan choices are different phases

Creating an index normally scans matching existing keys and then maintains the index as matching source documents change. SKIPINITIALSCAN deliberately skips the first phase; it is not a performance flag to copy casually because existing records will not be retroactively included merely because the index exists. During initial population or bulk changes, observe indexing, percent_indexed, num_docs, and failures before shifting user traffic.

Readiness rule

A successful FT.CREATE reply proves that the index definition was accepted. It does not by itself prove that all expected existing documents have been indexed, that zero documents failed schema conversion, or that the query result set matches the application contract.

9. Field types are query contracts, not display formatting

Declaring a field TEXT, TAG, NUMERIC, or GEO determines indexing and query semantics. A product category is not “basically text” when the requirement is exact membership; a price string is not a numeric range merely because humans can read digits; and a longitude/latitude string is not useful until it is indexed as GEO with the expected coordinate order.

Type Index intent Example question Common mistake
TEXT tokenized full-text retrieval description contains stemmed running terms expect exact categorical equality
TAG exact categorical filtering category contains outdoor expect stemming or relevance ranking
NUMERIC range/filter/sort priceCents between 5000 and 15000 store inconsistent string/non-number JSON values
GEO radius around lon/lat within 5 km of store reverse latitude/longitude or mix units

10. Source memory and index memory are both part of capacity

The JSON key still consumes Redis memory after it is indexed. Search then adds document tables, inverted indexes, term dictionaries, offset vectors, sortable values, key tables, and other structures depending on schema/features. Indexing a field also adds write work when that source field changes. Measure the before/after state rather than claiming a universal memory multiplier.

redis-cli · source and index memory evidence
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch09:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO memorydocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.INFO atlasmart-ch09-products-json-idx

For this three-document fixture the absolute values are too small for capacity extrapolation. The production exercise is to repeat the measurement with representative document sizes, term distributions, sortable fields, write rates, persistence settings, and concurrent queries.

11. Search ACLs do not replace source-key or application authorization

Search commands have their own ACL category and index names are not an application authorization boundary. A query that can discover a document may expose key identifiers or selected content depending on command permissions and result options. Production design therefore separates network/TLS, Redis ACL command/key permissions, index administration, and application-level tenant/business authorization. The disposable course uses academy-admin for Search so Chapter 09 does not silently broaden the application user.

12. Reproducible lesson lab

Blast radius: only the four bounded JSON keys, two bounded Hash keys, and named Chapter 09 indexes above. No FLUSH*, host changes, or production endpoints. Run the JSON fixture and index creation blocks, then verify the following checklist.

Verification Expected evidence
Source keys exist Direct JSON.GET/HGETALL succeeds for all intended fixtures.
JSON index scope Three product keys indexed; outside-prefix key remains searchable only by direct key access.
Wrong prefix failure Wrong index accepts definition but has zero docs; FT.INFO shows its prefix.
HASH FILTER scope Active offer is indexed; draft offer remains source-only.
No hidden cleanup Dropping a Search index without DD leaves source keys intact unless DD is explicitly requested.
redis-cli · bounded cleanup
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.DROPINDEX atlasmart-ch09-products-json-idxdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.DROPINDEX atlasmart-ch09-active-offers-idxdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch09:product:1001 atlasmart:ch09:product:1002 atlasmart:ch09:product:1003 atlasmart:ch09:outside:product:9001 atlasmart:ch09:offer:1 atlasmart:ch09:offer:2

13. Production judgment

Use Redis Search when applications need secondary predicates, full-text retrieval, structured filters, geospatial queries, aggregation, or later vector retrieval over data already modeled appropriately in Redis. Prefer direct key access when the key is already known and no secondary discovery is needed. Every indexed field carries memory and write-amplification cost; every broad prefix increases candidate cardinality; sortable attributes cost additional memory; index builds and migrations need readiness checks; ACLs and tenant authorization remain separate; persistence/replication must account for both source data and index rebuild/recovery behavior; and Cluster/managed-service semantics must be verified against the exact target. Do not treat an index as backup or schema enforcement.

14. Summary and next step

You now have the correct foundation: Search is a derived secondary access path over explicitly scoped HASH or JSON source documents. Prefixes and filters decide which documents participate; schema types decide how attributes can be queried; and FT.INFO proves index state. Lesson 2 moves inside those field types to show why TEXT analysis, TAG equality, numeric ranges, and GEO queries produce intentionally different results.

Check your understanding

  1. Does FT.CREATE replace the underlying JSON or Hash keys?
  2. What does PREFIX prove?
  3. Why can FT.CREATE returning OK still be insufficient readiness evidence?
  4. Can one Search index silently mix ON HASH and ON JSON?
  5. Why not index every field?
Review the answers

No. The Search index is derived from source keys; direct key access remains a separate path.

It limits candidate Redis keys by key-name prefix. An out-of-prefix source key can exist while returning no Search result.

Initial indexing, field conversion failures, and document counts still need to be observed with FT.INFO and sample queries.

No. The source type is chosen when the index is created.

Each field increases schema surface and can add memory and update work even if applications never query it.

Authoritative references

These references were re-checked for the Redis 8.10.1 course snapshot. Command output and planner details can change between Redis releases, clients, RESP modes, and topologies; prefer the target-version reference when reproducing the lab.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.