Tutorials
Redis-backed search
This tutorial walks through building a working full-text search index end to end, using Redis as the storage engine. By the end you will have indexed a real table, run searches against it, peeked at the raw keys Redis stores, and shared a single index across several application servers.
If you just need the configuration reference — the full list of redis_* options and key patterns — see the Redis engine page. This guide is the practical companion to it.
What you'll build
Imagine a blog running behind a load balancer with three PHP application servers. Every server needs to search the same set of articles. With the default SQLite engine each server would carry its own index file, and you'd have to copy that file around after every re-index. With Redis, all three servers point at one shared, in-memory index instead.
We'll get there in five steps:
- Start a Redis server
- Configure TNTSearch to use the Redis engine
- Build an index from a database table
- Search it — and inspect what Redis actually stored
- Share that one index across multiple servers
Prerequisites
You'll need:
- PHP >= 7.1 with the
pdo,pdo_mysql, andmbstringextensions - Composer
- A running Redis server (any recent version)
- A MySQL database with some rows to index
The fastest way to get a Redis server for local development is Docker:
docker run --name tnt-redis -p 6379:6379 -d redis
That gives you Redis listening on 127.0.0.1:6379. Any other Redis install works just as well.
Step 1 — Install the packages
TNTSearch ships with predis/predis as a dependency, so a single Composer command pulls in everything the Redis engine needs:
composer require teamtnt/tntsearch
You should know!
The Redis engine talks to Redis through the pure-PHP Predis client — you do not need the phpredis C extension installed. If Predis is missing for any reason, install it explicitly with composer require predis/predis.
Step 2 — Prepare some data to index
So the tutorial is reproducible, create a small articles table and drop a few rows in. The source data can live in any supported driver — here we use MySQL:
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
INSERT INTO articles (title, body) VALUES
('Getting started with Redis', 'Redis is an in-memory data store used as a database, cache, and message broker.'),
('Full-text search in PHP', 'TNTSearch is a fully featured full-text search engine written in PHP.'),
('Scaling behind a load balancer', 'Multiple application servers can share a single search index.');
The first column in your query is always treated as the primary key, and every other column is indexed as searchable text.
Step 3 — Configure the Redis engine
The only thing that changes compared to a normal SQLite setup is the engine class plus a few redis_* connection keys. Everything else — driver, host, database — still describes where your source data lives, not where the index goes.
use TeamTNT\TNTSearch\TNTSearch;
$tnt = new TNTSearch;
$tnt->loadConfig([
// Where the source data lives (the rows you want to index)
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'blog',
'username' => 'user',
'password' => 'pass',
// Store the index in Redis instead of a SQLite file
'engine' => 'TeamTNT\TNTSearch\Engines\RedisEngine',
'redis_host' => '127.0.0.1',
'redis_port' => '6379',
'stemmer' => \TeamTNT\TNTSearch\Stemmer\PorterStemmer::class,
]);
Note
storage is optional with the Redis engine — there is no index file on disk. It's only used when the filesystem driver needs a place to store its path map. You can leave it out for database-backed indexes.
Step 4 — Build the index
Building the index is identical to every other engine. You give the index a name, hand it a SQL query, and call run():
$indexer = $tnt->createIndex('articles.index');
$indexer->query('SELECT id, title, body FROM articles;');
$indexer->run();
That's it. Instead of writing an articles.index SQLite file, TNTSearch has now populated a set of Redis keys, all namespaced under articles.index:.
You should know!
Calling createIndex() with a name that already exists flushes the old index first. The Redis engine scans for every key matching the index prefix and deletes it, so re-indexing never leaves stale terms behind.
Step 5 — Look at what Redis stored
This is the fun part. Open redis-cli and list the keys for the index:
redis-cli --scan --pattern 'articles.index:*'
You'll see keys following a predictable pattern:
articles.index:info
articles.index:wordlist:redis
articles.index:wordlist:search
articles.index:doclist:redis:1
articles.index:doclist:search:2
...
Each term becomes a wordlist hash (how many documents contain it, how many total hits), and each term-in-a-document becomes a doclist hash. To inspect a single term:
redis-cli HGETALL articles.index:wordlist:redis
The full list of key patterns and their fields is documented on the Redis engine reference page.
Step 6 — Search it
Searching works exactly the same as with any engine — the storage backend is completely transparent to the search API:
$tnt = new TNTSearch;
$tnt->loadConfig($config); // same config as above
$tnt->selectIndex('articles.index');
$results = $tnt->search('redis', 12);
search() returns document IDs ranked by relevance, not full rows:
[
'ids' => [1],
'hits' => 1,
'docScores' => [1 => 1.62],
'execution_time' => '0.19 ms',
]
Every search feature you'd use with SQLite works with Redis too:
$tnt->searchBoolean('redis -cache'); // boolean operators
$tnt->fuzziness(true);
$tnt->search('redys'); // fuzzy: still finds "redis"
$tnt->searchBoolean('sear*'); // as-you-type prefixes
See Basic search, Boolean search, and Fuzzy search for the details of each.
Turning IDs into records
TNTSearch returns IDs so it stays database-agnostic. Fetch the actual articles yourself, preserving the relevance order:
$ids = implode(',', $results['ids']);
$articles = DB::select(
"SELECT * FROM articles WHERE id IN ($ids) ORDER BY FIELD(id, $ids)"
);
Step 7 — Share the index across servers
Here's the payoff. Because the index lives in Redis rather than in a local file, any server that can reach the same Redis instance sees the same index instantly.
A typical setup splits the work:
One indexer (a cron job, a queue worker, or an admin box) builds and updates the index:
// Runs wherever it's convenient — after content changes, on a schedule, etc. $indexer = $tnt->createIndex('articles.index'); $indexer->query('SELECT id, title, body FROM articles;'); $indexer->run();Every web server just searches, pointing at the same
redis_host:$tnt->selectIndex('articles.index'); $results = $tnt->search('redis');
Add a fourth web server tomorrow and it needs zero index setup — it reads the shared keys the moment it connects. No file copying, no per-deploy sync, no stale replicas.
You can also keep the index fresh incrementally without a full rebuild. When a single article changes, update just that document:
$tnt->selectIndex('articles.index');
$index = $tnt->getIndex();
$index->update(1, [
'id' => 1,
'title' => 'Getting started with Redis (updated)',
'body' => 'Redis is an in-memory data store...',
]);
Connecting to a remote or managed Redis
For production you'll usually point at a managed Redis (Elasticache, Redis Cloud, Upstash, …) over TLS with a password. The Redis engine supports that directly:
$tnt->loadConfig([
'driver' => 'mysql',
'engine' => 'TeamTNT\TNTSearch\Engines\RedisEngine',
'redis_host' => 'redis.example.com',
'redis_port' => '6380',
'redis_password' => 'your-password',
'redis_scheme' => 'tls',
'redis_ssl_options' => [
'verify_peer' => true,
],
// ... source database config
]);
The complete list of connection options is on the Redis engine reference page.
Should you use Redis?
Reach for the Redis engine when:
- Multiple servers need to share one search index
- You want in-memory speed and are already running Redis
- You'd rather not manage index files across deployments
Stay on the default SQLite engine when you're running a single server, your index is large, or you want a zero-dependency, on-disk setup.
Memory considerations
Redis keeps the entire index in RAM. A large corpus can consume significant memory, so monitor your Redis memory usage and enable persistence (RDB or AOF) if you want the index to survive a Redis restart without a rebuild. For very large datasets that don't fit comfortably in memory, SQLite is often the better fit.
Next steps
- Redis engine — full configuration and key-pattern reference
- Basic search — the core index-and-search workflow
- Index management — updating, inserting, and deleting documents
- Boolean search and Fuzzy search — advanced querying
