Skip to content
Merged
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
128 changes: 128 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,58 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --no-fail-fast

schema-integration:
name: Schema E2E Integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Protoc
run: sudo apt-get install -y protobuf-compiler
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Python dependencies
run: pip install httpx
- name: Run Schema E2E Test
run: ./scripts/test_schema_app_e2e.sh

client-features-integration:
name: Client Features Integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Protoc
run: sudo apt-get install -y protobuf-compiler
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Python dependencies
run: pip install httpx
- name: Run Client Features Test
run: ./scripts/test_client_features.sh

client-features-integration-ts:
name: Client Features Integration (TypeScript)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Protoc
run: sudo apt-get install -y protobuf-compiler
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run Client Features Test (TS)
run: ./scripts/test_client_features_ts.sh

# ═══════════════════════════════════════════════════════════════════════════
# CHAOS ENGINEERING TESTS (reusable workflow)
# ═══════════════════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -430,3 +482,79 @@ jobs:
- name: "📝 Post to Step Summary"
run: |
cat benchmark_results/BENCHMARK_RESULTS.md >> $GITHUB_STEP_SUMMARY

# ═══════════════════════════════════════════════════════════════════════════
# CROSS-LANGUAGE BENCHMARKS
# ═══════════════════════════════════════════════════════════════════════════
cross-lang-benchmark:
name: "🌍 Cross-Language Benchmarks"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Protoc
run: sudo apt-get install -y protobuf-compiler

- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Build PrkDB CLI
run: cargo build --release -p prkdb-cli

- name: Install Python dependencies
run: pip install httpx

- name: Start PrkDB Server
run: |
./target/release/prkdb-cli serve --port 8080 --grpc-port 50051 &
echo "Waiting for server..."
sleep 5

- name: Define Benchmark Schema
run: |
# Create a simple schema for benchmarking
cat > bench.proto <<EOF
syntax = "proto3";
package models;
message Benchmark {
string id = 1;
string payload = 2;
int64 timestamp = 3;
}
EOF
protoc --descriptor_set_out=bench.desc --include_imports bench.proto

# Register it
./target/release/prkdb-cli schema --server http://127.0.0.1:50051 register --collection benchmark --proto bench.desc

- name: Generate Clients
run: |
# Python
./target/release/prkdb-cli codegen --server http://127.0.0.1:50051 --lang python --out benches/client_py --collection benchmark
# TypeScript
./target/release/prkdb-cli codegen --server http://127.0.0.1:50051 --lang typescript --out benches/client_ts --collection benchmark

- name: "🐍 Run Python Benchmark"
run: |
python3 benches/bench_python.py --server http://127.0.0.1:8080 --records 10000

- name: "📘 Run TypeScript Benchmark"
run: |
# Compile TS if needed or run with tsx (simpler ESM support)
npm install -g tsx typescript
# Fix import path in generated file if necessary?
# The benchmark script imports from ./client_ts/benchmark.ts

tsx benches/bench_ts.ts
env:
PRKDB_SERVER: http://127.0.0.1:50051
NUM_RECORDS: 10000
72 changes: 72 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Deploy VitePress Docs

on:
push:
branches: [main]
paths:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch: # Allow manual triggers

# Sets permissions for GitHub Pages deployment
permissions:
contents: read
pages: write
id-token: write

# Prevent concurrent deployments
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # For lastUpdated feature

- name: Retrieve Node.js version
id: node-version
run: |
if [ -f .tool-versions ]; then
echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT
else
echo "node-version=20" >> $GITHUB_OUTPUT
fi

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.node-version.outputs.node-version }}
cache: 'npm'
cache-dependency-path: docs/package-lock.json

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Install dependencies
working-directory: docs
run: npm ci

- name: Build with VitePress
working-directory: docs
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/prkdb-orm",
"crates/prkdb-orm-macros",
"crates/prkdb-proto",
"crates/prkdb-schema",
"crates/prkdb-storage-sled",
"crates/prkdb-storage-sql",
"crates/prkdb-storage-segmented",
Expand Down Expand Up @@ -39,7 +40,7 @@ criterion = { version = "0.5", features = ["async_tokio"] }
dashmap = "5.5"
uuid = { version = "1.11", features = ["v4"] }
prometheus = "0.13"
axum = "0.8.8"
axum = "0.7.9"
lazy_static = "1.5"
crossbeam = "0.8"
mimalloc = { version = "0.1", default-features = false }
Expand Down Expand Up @@ -67,6 +68,7 @@ prkdb-orm = { path = "crates/prkdb-orm" }
prkdb-storage-sled = { path = "crates/prkdb-storage-sled" }
prkdb-storage-sql = { path = "crates/prkdb-storage-sql" }
prkdb-storage-segmented = { path = "crates/prkdb-storage-segmented" }
prkdb-schema = { path = "crates/prkdb-schema" }

# Optimize for CI to avoid Linker OOM (Bus Error signal 7)
# Reduces debug info for dependencies in dev/test builds
Expand Down
100 changes: 100 additions & 0 deletions benches/bench_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

import sys
import os
import json
import random
import string
import argparse
import asyncio
import time

# Add generated client to path
sys.path.append(os.path.join(os.getcwd(), "client_py"))

try:
from prkdb_client import PrkDbClient
except ImportError as e:
print(f"❌ Error: PrkDB Client not found or dependency missing: {e}")
sys.exit(1)

def random_string(length=10):
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))


def main():
parser = argparse.ArgumentParser(description="PrkDB Python Benchmark")
parser.add_argument("--server", default="http://127.0.0.1:8081", help="PrkDB Server URL(s), comma-separated")
parser.add_argument("--records", type=int, default=10000, help="Number of records")
args = parser.parse_args()

servers = args.server.split(",")
try:
asyncio.run(run_benchmark(servers, args.records))
except KeyboardInterrupt:
print("\n⚠️ Interrupted")

async def run_benchmark(servers, num_records):
print(f"🚀 Connecting to {servers}...")

# Create a pool of clients
clients = [PrkDbClient(host=s) for s in servers]

print(f" 📤 Starting Producer: {num_records} records...")

produce_start = time.time()
success_count = 0

# Batch concurrent requests to simulate load
BATCH_SIZE = 100

for i in range(0, num_records, BATCH_SIZE):
batch_end = min(i + BATCH_SIZE, num_records)
current_batch = []

for j in range(i, batch_end):
data = {
"id": f"bench_{j}",
"payload": random_string(100),
"timestamp": int(time.time() * 1000)
}
# Round-robin initial client
client = clients[j % len(clients)]
current_batch.append(put_with_retry(clients, "benchmark", data))

# Wait for batch
results = await asyncio.gather(*current_batch, return_exceptions=True)
for res in results:
if not isinstance(res, Exception):
success_count += 1
else:
print(f"Error: {res}")

# Close all clients
for c in clients:
await c.close()

duration = time.time() - produce_start
mbps = (num_records * 100) / duration / 1024 / 1024

print(f"✅ Producer Finished: {success_count}/{num_records} records")
print(f"⏱️ Duration: {duration:.2f}s")
print(f"📈 Throughput: {mbps:.2f} MB/s")

async def put_with_retry(clients, collection, data):
# Try clients in order (starting from random or just round-robin)
# Simple failover: try all clients
last_err = None
for client in clients:
try:
await client.put(collection, data)
return
except Exception as e:
last_err = e
# Logic to detect "Not Leader"?
# Serve.rs returns 500 for forwarding error.
# We just try next node.
continue
raise last_err

if __name__ == "__main__":
main()
Loading