Skip to content

Latest commit

 

History

History
546 lines (400 loc) · 14.9 KB

getting-started.md

File metadata and controls

546 lines (400 loc) · 14.9 KB

Getting Started with TiKV PHP Client

This guide will help you get up and running with the TiKV PHP Client in minutes.

Table of Contents

  1. Prerequisites
  2. Installation
  3. Setting Up TiKV
  4. Your First TiKV Application
  5. Next Steps

Prerequisites

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)

Checking PHP Version

php --version
# Should show PHP 8.2 or higher

Installing the gRPC Extension

The 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 newer

If 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 grpc

The 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-grpc

Alpine

apk add php83-pecl-grpc

macOS (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.so

and restart PHP-FPM (or your web server).

Installation

Install the TiKV PHP Client via Composer:

composer require crazy-goat/tikv-client

This will install the client and its dependencies:

  • grpc/grpc - gRPC client library
  • google/protobuf - Protocol Buffers
  • psr/log - PSR-3 logging interface

Setting Up TiKV

Option 1: Using Docker (Recommended for Development)

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 logs

This starts a 3-node TiKV cluster with PD (Placement Driver).

Option 2: Using Existing TiKV Cluster

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'];

Option 3: Manual TiKV Setup

See the TiKV documentation for manual installation instructions.

Your First TiKV Application

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.php

Expected 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

Understanding the Basics

Connection

$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

Key-Value Operations

// 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']);

Scanning

// 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.

Resource Cleanup

Always close the client when done:

$client->close();

This closes gRPC connections and releases resources.

Transactional Operations (TxnKV)

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-ttl must not be set (see tikv-v1.toml). A cluster with enable-ttl = true runs in V1TTL mode, which serves RawKV with TTL but not transactional requests. See TTL (Time-To-Live) for the full exclusivity note.

Creating a Transaction

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 with beginPessimistic() / beginOptimistic() in new code. Both are thin wrappers over begin() and accept the same options (priority, enable1Pc, enableAsyncCommit, eagerPessimisticLocks, lockWaitTimeMs).

Basic Transaction Operations

// 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();

Transfer Example (ACID Guarantee)

$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";
}

Pessimistic vs Optimistic

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 before set() or delete() 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 retry rollback(). A preceding pessimistic read's per-key for_update_ts is reused by the corresponding write, and prewrite keeps DO_CONSTRAINT_CHECK as a safety net for an intervening commit. For legacy applications that require the old behavior, pass begin(['eagerPessimisticLocks' => false]); that compatibility mode defers the lock pass to commit(). In either mode, callers should treat a conflict as a failed transaction and retry the full read-modify-write operation.

Next Steps

Now that you have the basics working, explore more features:

Learn More Operations

  • Operations Guide - Complete guide to all RawKV operations
    • TTL (Time-To-Live) for automatic expiration
    • Atomic operations (Compare-And-Swap)
    • Range deletions
    • Reverse scanning

Production Configuration

  • Configuration Guide - Configure for production
    • TLS/SSL encryption
    • PSR-3 logging
    • Timeout settings
    • Retry policies

Advanced Topics

  • Advanced Features - Production-ready patterns
    • Connection pooling
    • Region caching
    • Batch optimization
    • Error handling

Understanding the Architecture

  • Architecture - How the client works
    • PD discovery and region routing
    • gRPC communication
    • Retry mechanisms
    • Performance considerations

Common Issues

Connection Refused

If you get "Connection refused" errors:

  1. Check if TiKV is running: docker-compose ps
  2. Verify PD endpoint is correct
  3. Check firewall rules

gRPC Extension Not Found

If you see "Class 'Grpc\Channel' not found":

  1. Install the ext-grpc extension (1.57 or newer) — see Installing the gRPC Extension above.
  2. Enable it in php.ini: extension=grpc.so
  3. Restart web server/PHP-FPM

TTL Not Working

If TTL operations fail:

  1. Check TiKV configuration: enable-ttl must be true in tikv.toml
  2. Restart TiKV after configuration change

See Troubleshooting for more solutions.

Caution: enabling enable-ttl switches the cluster to V1TTL mode, which makes TxnKV transactions unusable on that cluster. TTL and transactions are mutually exclusive — see Cluster mode is exclusive.

Examples

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

Getting Help

  • Documentation: Browse the docs/ directory
  • Issues: GitHub Issues
  • Examples: Check the examples/ directory

Summary

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!