This guide will help you get up and running with the TiKV PHP Client in minutes.
Before you begin, ensure you have:
- PHP >= 8.2 installed
- gRPC PHP extension (
ext-grpc) enabled, 1.57 or newer - Composer for dependency management
- Docker (optional, for running TiKV locally)
php --version
# Should show PHP 8.2 or higherThe client talks gRPC through the ext-grpc PHP extension, and the library
requires it ("ext-grpc": ">=1.57" in composer.json). Composer checks the
requirement at install time, so the install fails with a clear message on a
machine without the extension:
crazy-goat/tikv-client <version> requires ext-grpc >=1.57 -> it is missing from your system. Install or enable PHP's grpc extension.
<version> is the version of the package you install.
Contributors working on this repository see the root-package wording of the
same error instead:
Root composer.json requires PHP extension ext-grpc >=1.57 but it is missing from your system. Install or enable PHP's grpc extension.
Check whether the extension is present, and which version it is:
php -m | grep grpc
# Should output: grpc
php -r 'echo phpversion("grpc"), PHP_EOL;'
# Should print 1.57 or newerIf it is missing (or too old), install it:
Debian / Ubuntu
PECL compiles the extension, so the build tools come first:
sudo apt-get install php-dev build-essential autoconf
sudo pecl install grpc
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION, ".", PHP_MINOR_VERSION;')
echo "extension=grpc.so" | sudo tee "/etc/php/$PHP_VERSION/mods-available/grpc.ini"
sudo phpenmod grpcThe php-grpc distribution package comes from the
sury.org PHP repository, which is not part
of stock Debian or Ubuntu. When that repository is already configured, the
package works too:
sudo apt-get install php-grpcAlpine
apk add php83-pecl-grpcmacOS (Homebrew)
pecl install grpc
echo "extension=grpc.so" >> "$(brew --prefix)/etc/php.ini"Then enable it in your php.ini if the installer did not do it for you:
extension=grpc.soand restart PHP-FPM (or your web server).
Install the TiKV PHP Client via Composer:
composer require crazy-goat/tikv-clientThis will install the client and its dependencies:
grpc/grpc- gRPC client librarygoogle/protobuf- Protocol Bufferspsr/log- PSR-3 logging interface
The repository includes a Docker Compose setup for easy local development:
# Clone the repository
git clone https://github.com/crazy-goat/tikv-php.git
cd tikv-php
# Start TiKV cluster
make up
# Verify cluster is running
make logsThis starts a 3-node TiKV cluster with PD (Placement Driver).
If you already have a TiKV cluster, note down the PD endpoints (usually port 2379):
$pdEndpoints = ['192.168.1.100:2379', '192.168.1.101:2379'];See the TiKV documentation for manual installation instructions.
Create a file named first-app.php:
<?php
require 'vendor/autoload.php';
use CrazyGoat\TiKV\Client\RawKv\RawKvClient;
// Connect to TiKV
$pdEndpoints = ['127.0.0.1:2379']; // Adjust if using different address
$client = RawKvClient::create($pdEndpoints);
try {
echo "Connected to TiKV!\n";
// Store some data
$client->put('hello', 'world');
echo "Stored: hello => world\n";
// Retrieve the data
$value = $client->get('hello');
echo "Retrieved: hello => $value\n";
// Store multiple values
$client->batchPut([
'user:1' => 'Alice',
'user:2' => 'Bob',
'user:3' => 'Charlie',
]);
echo "Stored 3 users\n";
// Retrieve multiple values
$users = $client->batchGet(['user:1', 'user:2', 'user:3']);
echo "Retrieved users:\n";
foreach ($users as $key => $value) {
echo " $key => $value\n";
}
// Scan all users with the lazy iterator (constant memory)
$userCount = 0;
foreach ($client->scanPrefixIterator('user:') as $_) {
$userCount++;
}
echo "Found $userCount users by scanning\n";
// Cleanup
$client->delete('hello');
$client->batchDelete(['user:1', 'user:2', 'user:3']);
echo "Cleanup complete\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
} finally {
// Always close the client
$client->close();
echo "Disconnected\n";
}Run your application:
php first-app.phpExpected output:
Connected to TiKV!
Stored: hello => world
Retrieved: hello => world
Stored 3 users
Retrieved users:
user:1 => Alice
user:2 => Bob
user:3 => Charlie
Found 3 users by scanning
Cleanup complete
Disconnected
$client = RawKvClient::create(['127.0.0.1:2379']);- The client connects to PD (Placement Driver), not directly to TiKV nodes
- PD provides cluster topology and region routing information
- The client automatically discovers TiKV nodes from PD
// Single key operations
$client->put('key', 'value'); // Store
$value = $client->get('key'); // Retrieve (returns null if not found)
$client->delete('key'); // Delete
// Batch operations (more efficient for multiple keys)
$client->batchPut(['k1' => 'v1', 'k2' => 'v2']);
$values = $client->batchGet(['k1', 'k2']); // Returns array: ['k1' => 'v1', 'k2' => 'v2']
$client->batchDelete(['k1', 'k2']);// Range scan [startKey, endKey). limit: 0 (the default) returns the whole
// range: the client pages internally and buffers every row, guarded by
// options['maxScanRows'] (default 100000).
$results = $client->scan('user:a', 'user:z');
// Prefix scan (convenience method)
$results = $client->scanPrefix('user:');
// Results format:
// [
// ['key' => 'user:1', 'value' => 'Alice'],
// ['key' => 'user:2', 'value' => 'Bob'],
// ...
// ]
// For a large range, prefer the lazy iterator — it holds one page at a time
// and never buffers the whole range:
foreach ($client->scanPrefixIterator('user:', batchSize: 500) as $key => $value) {
echo "$key: $value\n";
}limit: 0 no longer silently caps the result at one TiKV page (10240 rows).
Because the complete result is still held in memory, an unbounded scan that
matches more than options['maxScanRows'] rows throws
ScanLimitExceededException instead of truncating; use
scanIterator() / scanPrefixIterator() for datasets larger than that.
Always close the client when done:
$client->close();This closes gRPC connections and releases resources.
The client also supports ACID transactions via the TxnKV API. This is useful when you need strong consistency guarantees.
For the full picture — isolation and timestamps, optimistic vs pessimistic, lock heartbeats, the per-exception retry verdicts and a runnable retry-on-conflict example — see Transactions (TxnKV) in the README.
Prerequisite cluster mode. TxnKV requires a TiKV cluster in default (V1) mode —
enable-ttlmust not be set (seetikv-v1.toml). A cluster withenable-ttl = trueruns in V1TTL mode, which serves RawKV with TTL but not transactional requests. See TTL (Time-To-Live) for the full exclusivity note.
use CrazyGoat\TiKV\Client\TxnKv\TxnKvClient;
$txnClient = TxnKvClient::create(['127.0.0.1:2379']);
try {
// Pessimistic transaction (the default of begin()). The value stays
// buffered until commit(), but the physical lock is taken before
// set()/delete() returns, carrying the timestamp of the preceding read
// (or a fresh one if there was no read). An intervening commit is
// therefore reported as a conflict at the write instead of being
// silently overwritten.
$txn = $txnClient->beginPessimistic();
// Optimistic transaction — locks only on commit
// $txn = $txnClient->beginOptimistic();
// The `pessimistic` option still selects the mode:
// $txn = $txnClient->begin(['pessimistic' => false]); // same as beginOptimistic()
// ... use the transaction ...
} finally {
$txnClient->close();
}
begin()on its own starts a pessimistic transaction — the opposite of the TiKV documentation and of client-go and client-rust, where optimistic is the default (issue #439). The default stays as it is for now, so state the mode withbeginPessimistic()/beginOptimistic()in new code. Both are thin wrappers overbegin()and accept the same options (priority,enable1Pc,enableAsyncCommit,eagerPessimisticLocks,lockWaitTimeMs).
// Start a transaction (pessimistic by default — use beginOptimistic() for
// the optimistic mode)
$txn = $txnClient->beginPessimistic();
// Read (snapshot at startTs)
$value = $txn->get('account:1');
// Write (value buffered until commit; the default pessimistic mode also takes
// the physical lock here, so a conflict surfaces on this line)
$txn->set('account:1', '1000');
$txn->delete('account:2');
// Batch read
$values = $txn->batchGet(['account:1', 'account:2']);
// Scan within transaction
$results = $txn->scan('account:', 'account;');
// Commit (2-phase: prewrite + commit)
$txn->commit();
// Or rollback if something went wrong
// $txn->rollback();$txn = $txnClient->beginPessimistic();
try {
$fromBalance = (int) $txn->get('account:alice');
$toBalance = (int) $txn->get('account:bob');
$amount = 100;
if ($fromBalance < $amount) {
throw new RuntimeException('Insufficient funds');
}
$txn->set('account:alice', (string) ($fromBalance - $amount));
$txn->set('account:bob', (string) ($toBalance + $amount));
$txn->commit();
echo "Transfer committed!\n";
} catch (Exception $e) {
if ($txn->getStatus() === \CrazyGoat\TiKV\Client\TxnKv\TransactionStatus::Active) {
$txn->rollback();
}
echo "Transfer rolled back: " . $e->getMessage() . "\n";
}| Feature | Pessimistic | Optimistic |
|---|---|---|
| Lock timing | Physical lock during each set() / delete() (default) |
Constraint check at prewrite |
| Conflicts | Reported by the write call, or by commit() for deferred compatibility mode |
Reported by commit() |
| Best for | High contention | Low contention |
| Default lock TTL | 30 seconds | 3 seconds |
| Use case | Financial transfers | Caching, config |
Note:
begin()starts a pessimistic transaction (the default),beginOptimistic()an optimistic one — see the note above.
Note: values remain buffered until
commit(), but the default eager pessimistic mode acquires each key's physical lock beforeset()ordelete()returns. A conflict, deadlock, or wait timeout therefore surfaces at the write call. A failed eager lock statement triggers best-effort cleanup before the exception is rethrown; if cleanup itself fails, the transaction remains active but unwritable so the caller can retryrollback(). A preceding pessimistic read's per-keyfor_update_tsis reused by the corresponding write, and prewrite keepsDO_CONSTRAINT_CHECKas a safety net for an intervening commit. For legacy applications that require the old behavior, passbegin(['eagerPessimisticLocks' => false]); that compatibility mode defers the lock pass tocommit(). In either mode, callers should treat a conflict as a failed transaction and retry the full read-modify-write operation.
Now that you have the basics working, explore more features:
- Operations Guide - Complete guide to all RawKV operations
- TTL (Time-To-Live) for automatic expiration
- Atomic operations (Compare-And-Swap)
- Range deletions
- Reverse scanning
- Configuration Guide - Configure for production
- TLS/SSL encryption
- PSR-3 logging
- Timeout settings
- Retry policies
- Advanced Features - Production-ready patterns
- Connection pooling
- Region caching
- Batch optimization
- Error handling
- Architecture - How the client works
- PD discovery and region routing
- gRPC communication
- Retry mechanisms
- Performance considerations
If you get "Connection refused" errors:
- Check if TiKV is running:
docker-compose ps - Verify PD endpoint is correct
- Check firewall rules
If you see "Class 'Grpc\Channel' not found":
- Install the
ext-grpcextension (1.57 or newer) — see Installing the gRPC Extension above. - Enable it in
php.ini:extension=grpc.so - Restart web server/PHP-FPM
If TTL operations fail:
- Check TiKV configuration:
enable-ttlmust betruein tikv.toml - Restart TiKV after configuration change
See Troubleshooting for more solutions.
Caution: enabling
enable-ttlswitches the cluster to V1TTL mode, which makes TxnKV transactions unusable on that cluster. TTL and transactions are mutually exclusive — see Cluster mode is exclusive.
The repository includes complete examples in the examples/ directory:
# Basic operations
php examples/basic.php
# Batch operations
php examples/batch.php
# Scanning
php examples/scan.php
# TTL
php examples/ttl.php
# Atomic operations
php examples/atomic.php
# TLS
php examples/tls.php
# Logging
php examples/logging.php- Documentation: Browse the docs/ directory
- Issues: GitHub Issues
- Examples: Check the
examples/directory
You've learned:
- ✅ How to install the TiKV PHP Client
- ✅ How to connect to a TiKV cluster
- ✅ Basic CRUD operations (RawKV)
- ✅ Batch operations for efficiency
- ✅ Scanning for data retrieval
- ✅ ACID transactions (TxnKV)
- ✅ Pessimistic vs optimistic transaction modes
- ✅ Proper resource cleanup
Ready to dive deeper? Continue with the Operations Guide!