Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 6 additions & 223 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,231 +35,14 @@ Springtail replicas are well-suited for traditional read replica workloads:

Any read-only transaction that might cause contention on your primary database is an ideal candidate for Springtail.

## Prerequisites
## Documentation

- Docker and Docker Compose
- Python 3
- AWS CLI (for local cluster only)
Full documentation is available at **[docs.springtail.io](https://docs.springtail.io/)**, including:

## Quick Start: Local Development Environment

The local dev environment runs a PostgreSQL 16 instance and a development container with all build tooling pre-installed.

### 1. Build the base Docker image

From the repository root:

```shell
cd docker
./docker_base.sh
```

This builds the `springtail:base` image from `Dockerfile.base`, which includes a patched PostgreSQL 16 (built from source with RLS support for foreign tables), Redis, the C++ toolchain, and all Ansible-provisioned dependencies.

### 2. Start the dev environment

```shell
export SPRINGTAIL_SRC=/path/to/springtail # absolute path to your repo checkout
docker compose up -d
```

This starts two services:

| Service | Container | Description | Host Port |
|------------|-------------------|----------------------------------|-----------|
| `postgres` | `pg16` | PostgreSQL 16 with logical replication enabled | 5432 |
| `dev` | `dev-springtail` | Development container with build tools | 2222 (SSH) |

The dev container mounts your source tree at `/home/dev/springtail` and starts PostgreSQL, Redis, and SSH automatically via its entrypoint.

### 3. Build Springtail inside the container

Shell into the dev container and run the debug build:

```shell
docker exec -it dev-springtail bash

# Inside the container:
cd ~/springtail
./vcpkg.sh # one-time: install C++ dependencies
./debug.sh # build debug binaries into ./debug/
```

### 4. Run the unit tests (C++ / CTest)

```shell
cd ~/springtail/debug
make build_tests
ctest
```

Or build and run in one step:

```shell
cmake --build debug --target check
```

The `check` target kills any running Springtail processes, installs SQL triggers, builds the tests, and runs them via CTest.

### 5. Run the integration tests

The integration test runner is a Python script that exercises Springtail end-to-end against a real PostgreSQL instance. It must be run from its own directory:

```shell
cd ~/springtail/python/testing
python3 test_runner.py
```

This runs the **default** test configuration, which includes the test sets `basic`, `framework`, `preload`, `enum_bits`, `complex`, `numeric`, `query_benchmark`, and `recovery` (with various overlay configurations).

#### Common test_runner.py options

```shell
# Run the default configuration (same as no arguments)
python3 test_runner.py

# Run a specific named configuration (e.g., nightly, github_ci_p1)
python3 test_runner.py -c nightly

# Run a single test set
python3 test_runner.py basic

# Run specific test cases within a test set
python3 test_runner.py basic test_create.sql test_insert.sql

# Run with a specific overlay
python3 test_runner.py -o small_log_rotate recovery

# Skip downloading test data from S3 (useful offline or in CI)
python3 test_runner.py --skip-downloads

# Output JUnit XML report
python3 test_runner.py -j results.xml
```

Available test sets: `basic`, `complex`, `enum_bits`, `framework`, `include_schema`, `large_data`, `live_startup`, `numeric`, `policy_roles`, `preload`, `query_benchmark`, `recovery`, `text_tables`.

Available overlays: `small_log_rotate`, `small_log_rotate_with_streaming`, `small_cache_size`, `streaming_postgres_config`, `integration_test_config`, `include_schema_config`.

### 6. Tear down

```shell
cd /path/to/springtail/docker
docker compose down -v
```

---

## Quick Start: Local Cluster (Multi-Node)

The local cluster simulates a full multi-node Springtail deployment using Docker Compose. It runs a primary database, Redis, a mock AWS environment, and the full set of Springtail services (proxy, ingestion, FDW nodes, and controller).

All cluster commands are run from the `local-cluster/` directory:

```shell
cd local-cluster
```

### 1. Build the required Docker images

From the repository root, build the base service image if it doesn't already exist:

```shell
docker build -t local-cluster-img:latest -f docker/Dockerfile.local-cluster .
```

The `cluster up` command will also build the controller image (`local-cluster-controller:latest`) and custom PostgreSQL image (`postgres-custom:16`) automatically if needed.

### 2. Build a Springtail package

```shell
./cluster build-package /tmp/springtail-packages
```

This runs the full build and packaging process inside the base image and outputs a tarball named `springtail-<date>-<gitsha>.tar.gz` into the specified directory.

### 3. Start the cluster

```shell
./cluster up /tmp/springtail-packages/springtail-<date>-<gitsha>.tar.gz
```

Optionally disable SSL for inter-service connections:

```shell
./cluster up /path/to/package.tar.gz --disable-ssl
```

Startup takes 1-2 minutes. The cluster will:
1. Start the mock AWS service (Moto), Redis, and primary PostgreSQL
2. Upload the package to mock S3
3. Run the bootstrap container to configure secrets, Redis auth, and shared environment
4. Launch the proxy, ingestion, and FDW services

### 4. Check cluster status

```shell
./cluster status
```

### 5. Interact with services

```shell
# Shell into a service
./cluster sh proxy
./cluster sh ingestion
./cluster sh fdw1
./cluster sh controller

# View logs
./cluster logs proxy
./cluster logs ingestion

# Restart a service
./cluster restart proxy

# List all services
./cluster ls
```

### 6. Host ports

Services are exposed on the host at these ports:

| Service | Host Port |
|----------------|-----------|
| Primary DB | 15432 |
| Redis | 16379 |
| Proxy | 55432 |
| FDW 1 | 45432 |
| FDW 2 | 45433 |
| AWS Mock (Moto)| 29999 |
| Controller API | 19824 |

Connect to the proxy from the host with any PostgreSQL client:

```shell
psql -h localhost -p 55432 -U postgres
```

### 7. Tear down the cluster

```shell
# Stop Springtail services but keep the primary DB and dependencies
./cluster down

# Stop a specific service
./cluster down proxy

# Stop everything and remove all data volumes and networks
./cluster down all
```

## Quick Start: Production Deployment

In a production deployment, each Springtail host runs a `coordinator` process that supervises the daemons for one service tier (`ingestion`, `fdw`, or `proxy`), installs binaries from S3, monitors component liveness via Redis, and reacts to lifecycle state changes (startup, reload, drain, shutdown) driven by the controller.

For details on starting Springtail, adding replica (FDW) nodes, removing replica nodes, and stopping Springtail, see [`python/coordinator/README.md`](python/coordinator/README.md).
- **[Quickstart](https://docs.springtail.io/quickstart)** — the fastest way to get a local cluster running for testing.
- **[Local dev environment](https://docs.springtail.io/local-dev-environment)** — build Springtail and run the unit and integration tests in a development container.
- **[Local cluster deployment](https://docs.springtail.io/local-cluster-deployment)** — run a full multi-node deployment locally with Docker Compose.
- **[Production deployment](https://docs.springtail.io/deployment)** — deploy and manage Springtail in production with the [coordinator](python/coordinator/README.md).

## License

Expand Down
42 changes: 18 additions & 24 deletions wiki/client-session.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,24 @@ The Client Session builds upon a base Session class which provides:

### Primary States

```
┌─────────────┐
│ STARTUP │ Initial state, handling authentication
└──────┬──────┘
┌─────────────┐
│ AUTH_SERVER │ Waiting for server authentication
└──────┬──────┘
┌─────────────┐
│ READY │ Idle, waiting for next query
└──────┬──────┘
┌─────────────┐
│ QUERY │ Processing query (delegated to server session)
└──────┬──────┘
┌─────────────┐
│ ERROR │ Fatal error, connection closing
└─────────────┘
```mermaid
stateDiagram-v2
[*] --> STARTUP
STARTUP --> AUTH_SERVER: client auth completes
AUTH_SERVER --> READY: server auth completes
READY --> QUERY: client sends query message
QUERY --> READY: server sends ReadyForQuery
STARTUP --> ERROR: fatal error / disconnect
AUTH_SERVER --> ERROR: fatal error / disconnect
READY --> ERROR: fatal error / disconnect
QUERY --> ERROR: fatal error / disconnect
ERROR --> [*]

note right of STARTUP: Initial state, handling authentication
note right of AUTH_SERVER: Waiting for server authentication
note right of READY: Idle, waiting for next query
note right of QUERY: Processing query (delegated to server session)
note right of ERROR: Fatal error, connection closing
```

### State Transitions
Expand Down
47 changes: 15 additions & 32 deletions wiki/extension-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,38 +66,21 @@ Initialization flow during database setup that loads extensions configured in `s

## Extension Loading Flow

```
Application Startup
PgCopyTable::init_pg_extn_registry(db_id, xid)
→ Reads extension config from system.json
→ For each extension:
├─→ PgExtnRegistry::init_libraries()
│ ├─ dlopen() loads extension .so file
│ └─ Stores library handle in _library_map
├─→ _load_extn_types()
│ ├─ Queries pg_type for extension types
│ ├─ For each type:
│ │ ├─ dlsym() loads type I/O functions (typinput, typoutput, typreceive, typsend)
│ │ └─ PgExtnRegistry::add_type()
│ └─ Creates extension types in Server::create_usertype()
├─→ _load_extn_operators()
│ ├─ Queries pg_operator for extension operators
│ ├─ For each operator:
│ │ ├─ dlsym() loads operator implementation function
│ │ └─ PgExtnRegistry::add_operator()
│ └─ Stores operator_name → function_ptr mappings
└─→ _load_extn_opclasses()
├─ Queries pg_opclass for GIN/GIST opclasses
├─ For each opclass method (compress, penalty, union, etc.):
│ ├─ dlsym() loads support function
│ └─ PgExtnRegistry::add_opclass()
└─ Stores (opclass_name, support_number) → method mappings
```mermaid
flowchart TD
Start["Application Startup"]
Init["PgCopyTable::init_pg_extn_registry(db_id, xid)<br/>Reads extension config from system.json<br/>For each extension:"]

Libs["PgExtnRegistry::init_libraries()<br/>- dlopen() loads extension .so file<br/>- Stores library handle in _library_map"]
Types["_load_extn_types()<br/>- Queries pg_type for extension types<br/>- dlsym() loads type I/O functions (typinput, typoutput, typreceive, typsend)<br/>- PgExtnRegistry::add_type()<br/>- Creates extension types in Server::create_usertype()"]
Ops["_load_extn_operators()<br/>- Queries pg_operator for extension operators<br/>- dlsym() loads operator implementation function<br/>- PgExtnRegistry::add_operator()<br/>- Stores operator_name → function_ptr mappings"]
OpClasses["_load_extn_opclasses()<br/>- Queries pg_opclass for GIN/GIST opclasses<br/>- dlsym() loads support function (compress, penalty, union, etc.)<br/>- PgExtnRegistry::add_opclass()<br/>- Stores (opclass_name, support_number) → method mappings"]

Start --> Init
Init --> Libs
Init --> Types
Init --> Ops
Init --> OpClasses
```

---
Expand Down
Loading