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

FT.SEARCH Query Syntax, Boolean Logic, Phrase/Prefix/Fuzzy Search, Sorting, and Pagination

Compose FT.SEARCH safely with explicit dialect, boolean/phrase/prefix/fuzzy syntax, projections, sorting, parameters, and deterministic paging.

Intermediate160–190 minutesQuery language and paging labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart now has correctly typed index fields. The next risk is query composition: a syntactically small Search string can encode boolean logic, phrase constraints, prefix/fuzzy expansion, sorting, projections, and pagination, and each of those choices changes correctness or cost. The safest approach is to make the dialect and result-order contract explicit.

01

Compose scoped TEXT/TAG/NUMERIC/GEO predicates with explicit boolean logic and parentheses.

02

Use exact phrases, prefixes, fuzzy terms, negation, and optional clauses without confusing their retrieval semantics.

03

Use PARAMS and explicit DIALECT 2 where values or modern syntax require a stable parser contract.

04

Sort/project results deliberately and explain why LIMIT without deterministic sorting can duplicate or omit rows across pages.

05

Diagnose query parsing and cost with FT.EXPLAINCLI and bounded evidence instead of trial-and-error punctuation.

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:*. Redis 8 documentation still lists Dialect 1 as the server default while marking Dialects 1, 3, and 4 deprecated. The course therefore requests DIALECT 2 for modern query syntax and verifies any newer optimization before production adoption.

1. Re-create the bounded Search fixture

redis-cli · source documents
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"}'
redis-cli · 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

Wait until FT.INFO reports the expected documents before comparing query result sets. Query syntax testing against a half-built index confuses parser behavior with indexing readiness.

2. Space means AND; pipe means OR; parentheses protect intent

In the Search query language, multiple terms/clauses generally intersect, while | expresses union. Negation uses a leading minus. Parentheses should be used whenever human readers could disagree about precedence—especially as TAG, TEXT, and numeric clauses mix.

redis-cli · boolean composition
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@category:{outdoor} @stock>0' RETURN 3 sku name stock 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 '(@category:{outdoor}|@category:{urban}) @priceCents<13000' RETURN 3 sku name priceCents 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 '@category:{travel} -@stock:[0 0]' RETURN 3 sku name stock DIALECT 2

Do not build authorization by string-concatenating a tenant clause into user-provided search text. Application authorization must be enforced independently and query values should be parameterized/escaped according to the field grammar.

3. Phrase search is not the same as ANDing terms

runner backpack means both analyzed terms are required; "runner backpack" asks for the phrase relationship. SLOP and INORDER can relax/control distance and order when the use case genuinely needs them. Phrase support depends on index offsets; schema options that remove offsets can disable that capability.

redis-cli · compare AND and phrase intent
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@description:(runner backpack)' RETURN 2 sku description 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 '@description:"runner backpack"' RETURN 2 sku description DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.EXPLAINCLI atlasmart-ch09-products-json-idx '@description:"runner backpack"' DIALECT 2

4. Prefix and fuzzy terms expand the candidate vocabulary

A prefix such as Trail* matches indexed terms beginning with the prefix. Fuzzy terms use percent markers to permit edit distance. Both improve recall when spelling or morphology is uncertain, but both can expand work. Evaluate them with representative vocabulary size and tail latency.

redis-cli · prefix and fuzzy retrieval
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@name:(Trail*)' RETURN 2 sku name 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 '@name:(%Backpak%)' RETURN 2 sku name DIALECT 2docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.PROFILE atlasmart-ch09-products-json-idx SEARCH LIMITED QUERY '@name:(%Backpak%)' NOCONTENT DIALECT 2

The profile shape is version-sensitive. Use it to identify iterators and time spent; do not parse human-readable profiler text as a stable application API.

5. Scope terms to fields instead of searching “everything” by accident

Unscoped text can match any indexed TEXT attribute. Field selectors such as @name: and @description: constrain intent. This can improve both relevance and cost by reducing unnecessary candidate matches. Exact TAG/NUMERIC/GEO predicates are already field-scoped by their grammar.

redis-cli · scoped versus unscoped text
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx 'running' RETURN 2 sku name 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 '@name:(running)' RETURN 2 sku name 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 '@description:(running)' RETURN 2 sku description DIALECT 2

6. Query dialect is part of the client/server contract

Redis Search dialects exist so syntax can evolve without silently breaking old applications. Redis 8 documentation currently deprecates Dialects 1, 3, and 4 while still listing Dialect 1 as the server default. Maintained clients may explicitly select another dialect—for example redis-py 6.x chooses Dialect 2 for Search methods. Therefore record both server and client behavior and request a dialect explicitly when query semantics depend on it.

Course rule

All modern Chapter 09 examples request DIALECT 2 unless a lesson explicitly demonstrates a dialect-dependent return shape. Do not change the server-wide default merely to make one lab work.

7. PARAMS separates values from query grammar

PARAMS substitutes values where concrete values are allowed and requires Dialect 2 or newer. It is useful for numeric/geo/vector values and reduces ad-hoc query-string construction. It does not let parameters replace field names or arbitrary grammar, so application code still needs a safe query builder for TAG/TEXT syntax.

redis-cli · parameterized GEO and numeric values
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@location:[$lon $lat $radius km] @priceCents:[$min $max]' PARAMS 10 lon 49.8671 lat 40.4093 radius 5 min 3000 max 15000 RETURN 3 sku name priceCents DIALECT 2

The PARAMS 10 count is the number of name/value tokens that follow: five names and five values. Count mistakes are protocol errors, which is why client-library APIs are preferable in application code.

8. RETURN is projection; NOCONTENT is ids only

Search can return full source content, selected fields, or just document IDs. Returning less data reduces network/serialization work, but it does not change the set of matching documents. Design the projection from the API contract rather than returning entire JSON documents by default.

redis-cli · projection choices
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '@category:{outdoor}' NOCONTENT 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 '@category:{outdoor}' RETURN 3 sku name priceCents 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 '@category:{outdoor}' RETURN 6 '$.sku' AS sourceSku '$.name' AS sourceName DIALECT 2

9. SORTBY needs a sortable contract and costs memory

Attributes used for low-latency sorting should be declared SORTABLE in the schema. This keeps sortable values in index structures and therefore consumes additional memory. Relevance order and explicit field order are different contracts; APIs should state which one callers receive.

redis-cli · deterministic price and SKU ordering
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.SEARCH atlasmart-ch09-products-json-idx '*' RETURN 3 sku name priceCents SORTBY priceCents ASC LIMIT 0 3 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 '*' RETURN 2 sku name SORTBY sku ASC LIMIT 0 3 DIALECT 2

sku is unique in the fixture and therefore useful for stable demonstration ordering. In real systems, choose a sorting/paging contract whose uniqueness and mutation semantics are explicit.

10. Wrong approach: paginate with LIMIT and no deterministic ordering

Redis documents that LIMIT without sorting is non-deterministic: repeated pages can duplicate or miss values. Deep offsets also require increasingly more work in common plans. The repair is not “retry until pages look right.” Add a deterministic sort where suitable, or use an aggregation cursor for large streaming result sets.

redis-cli · bad and repaired tiny-page examples
docker 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 2 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 '*' NOCONTENT LIMIT 2 2 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 '*' RETURN 1 sku SORTBY sku ASC LIMIT 0 2 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 '*' RETURN 1 sku SORTBY sku ASC LIMIT 2 2 DIALECT 2

Because the fixture is tiny, you may not observe duplicates in the first two commands. The failure is the missing guarantee itself; production behavior under concurrent updates and larger result sets is what makes it unsafe.

11. Escaping belongs to the field grammar, not one universal backslash rule

TEXT, TAG, wildcard, phrase, numeric, and GEO clauses have different special characters and dialect rules. Dialect 2 allows unescaped spaces in TAG queries, but punctuation such as braces, pipes, and other metacharacters can still need escaping. Use the current query-syntax documentation or a maintained client query builder; never run raw end-user strings as a Search grammar fragment.

Security boundary

Search query escaping prevents parser mistakes/injection into the Search grammar. It does not implement authentication, tenant authorization, or permission to view a matched product.

12. Explain before tuning

FT.EXPLAIN/FT.EXPLAINCLI reveal how the parser interpreted a query. They are especially useful when boolean precedence, stemming, field scoping, or escaping produces surprising matches. First make the logical plan match the business question; only then use FT.PROFILE to investigate runtime cost.

redis-cli · explain a mixed structured/full-text query
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.EXPLAINCLI atlasmart-ch09-products-json-idx '(@category:{outdoor} @priceCents<15000) @description:(running)' DIALECT 2

13. Reproducible lesson lab and verification

Run the source/index setup, then capture result IDs for boolean, phrase, fuzzy, numeric/GEO, and sorted-page queries. Verify both parser intent and output order.

Check Pass condition
Boolean query Parenthesized AND/OR result matches the three known fixtures.
Phrase vs AND You can explain why their result sets may differ.
Prefix/fuzzy Expanded retrieval is deliberate and profileable.
Pagination Sorted SKU pages are deterministic for this static fixture.
Dialect Every modern query explicitly records DIALECT 2.
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-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

14. Production judgment

Keep query grammar small, explicit, and tested. Boolean/fuzzy/prefix complexity can increase candidate work and p99 latency; full content can inflate network replies; sortable fields trade memory for sort efficiency; unsorted pagination is non-deterministic; deep offsets are a poor default for large result sets; dialect/client defaults can change semantics; and source updates during a query can affect loaded values. Use timeout/retry policies appropriate to read queries, but do not hide malformed queries behind retries. Treat authorization, index readiness, and source-of-truth reconciliation separately.

15. Summary and next step

FT.SEARCH is a query language, not a string contains API. You now have explicit boolean, phrase, prefix/fuzzy, parameter, sorting, projection, paging, and dialect contracts. Lesson 4 moves from selecting documents to transforming result sets with FT.AGGREGATE pipelines, grouping, reducers, filters, APPLY, and result shaping.

Check your understanding

  1. What does a space between clauses normally mean?
  2. Why request DIALECT 2 explicitly?
  3. Is LIMIT without SORTBY deterministic paging?
  4. What is RETURN for?
  5. Does query escaping provide tenant authorization?
Review the answers

Intersection/AND; use parentheses when combining it with OR or negation.

So copied query semantics do not depend on a mutable server-wide or client default.

No. Redis documents that it can produce duplicate or missing values across pages.

Projection: limit which source/index attributes are returned without changing which documents match.

No. It only protects query grammar construction; authorization is a separate application/security control.

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.