Performance comparison of gRPC and REST protocols for Hedera-style financial infrastructure workloads (blockchain custody, exchanges, payment systems).
Built in Go with a PostgreSQL backend to isolate transport layer performance from application logic.
| Metric | gRPC | REST | Difference |
|---|---|---|---|
| Throughput (50+ concurrency) | ~17-18% higher | baseline | gRPC wins |
| p99 latency (50+ concurrency) | 15-22% lower | baseline | gRPC wins |
| Streaming throughput | ~equal | ~equal | parity |
Both protocols hit DB connection pool saturation around 10K req/s.
- Go 1.21+ with modules enabled
- Docker and Docker Compose
- Protocol Buffers compiler (
protoc) with Go plugins (protoc-gen-go,protoc-gen-go-grpc) - Python 3.12+ (optional, for Python benchmarks)
- Rust/Cargo (optional, for Rust benchmarks)
# Generate protobuf code
make proto
# Start PostgreSQL and seed test data (10K accounts, 100K transactions)
make seed
# Start servers (in separate terminals)
make grpc-server # starts gRPC server on :50051
make rest-server # starts REST server on :8080
# Run a benchmark
make benchmark ARGS="--scenario=balance --protocol=grpc --concurrency=10 --duration=10s"
# View results in dashboard
open http://localhost:8080/
# Cleanup
make cleanHigh-frequency unary request/response pattern simulating account balance lookups.
| Aspect | Details |
|---|---|
| Pattern | Unary RPC / GET request |
| Payload | ~100 bytes (account ID → balance) |
| Use case | Wallet balance checks, pre-transaction validation |
| Data | 10,000 accounts with random balances |
gRPC: BalanceService.GetBalance(account_id) → BalanceResponse
REST: GET /accounts/{id}/balance → JSON
Server-side streaming pattern simulating real-time transaction event feeds.
| Aspect | Details |
|---|---|
| Pattern | Server streaming RPC / SSE |
| Payload | ~200 bytes per transaction event |
| Use case | Transaction monitoring, audit trails, real-time dashboards |
| Data | 100,000 transactions over 24-hour window |
gRPC: TransactionService.StreamTransactions(since, rate_limit) → stream Transaction
REST: GET /transactions/stream?since=... (Server-Sent Events)
Three benchmark clients are available: Go, Python, and Rust. All store results in PostgreSQL and can be visualized in the dashboard.
# Balance queries
make go-benchmark ARGS="--scenario=balance --protocol=grpc --concurrency=50 --duration=30s"
make go-benchmark ARGS="--scenario=balance --protocol=rest --concurrency=50 --duration=30s"
# Transaction streaming
make go-benchmark ARGS="--scenario=stream --protocol=grpc --rate=100 --duration=30s"
make go-benchmark ARGS="--scenario=stream --protocol=rest --rate=100 --duration=30s"The Makefile automatically creates a virtual environment at clients/python/venv/.
# Balance queries (gRPC only)
make python-benchmark ARGS="--scenario=balance --concurrency=10 --duration=30s"
# Transaction streaming
make python-benchmark ARGS="--scenario=stream --rate=100 --duration=30s"# Balance queries
make rust-benchmark ARGS="--scenario=balance --protocol=grpc --duration=30s"
make rust-benchmark ARGS="--scenario=balance --protocol=rest --duration=30s"
# Transaction streaming (gRPC only)
make rust-benchmark ARGS="--scenario=stream --protocol=grpc --rate=100 --duration=30s"Replay real Hedera Consensus Service timing patterns for realistic workload simulation. Uses the hiero-hcs-replay library.
Option 1: Live fetch and benchmark in one step
# Fetch timing from HCS topic and immediately run benchmark
make benchmark-hcs-live TOPIC=0.0.120438 NETWORK=mainnet LIMIT=1000 \
SPEEDUP=10 ARGS="--protocol=grpc --concurrency=10 --duration=30s"Option 2: Fetch once, replay multiple times
# Fetch timing data from a public HCS topic (saves to timing.json)
make fetch-hcs-timing TOPIC=0.0.120438 NETWORK=mainnet LIMIT=1000
# Run benchmark with saved timing data (10x speedup)
make benchmark-replay TIMING=timing.json SPEEDUP=10 ARGS="--protocol=grpc --concurrency=10"CLI flags for HCS replay:
| Flag | Description |
|---|---|
--hcs-topic |
HCS topic ID to fetch timing from (e.g., 0.0.120438) |
--hcs-network |
Hedera network: mainnet, testnet, previewnet |
--hcs-limit |
Max messages to fetch (default: 1000) |
--hcs-save |
Path to save fetched timing data for reuse |
--replay-timing |
Path to pre-fetched timing JSON file |
--replay-mode |
sequential (exact order) or sample (random) |
--replay-speedup |
Speed multiplier (1.0 = real-time, 10.0 = 10x faster) |
make test # Run all tests
make test-db # Database integration tests only
make test-benchmark # Benchmark unit tests only├── cmd/
│ ├── grpc-server/ # gRPC server (:50051)
│ ├── rest-server/ # REST server (:8080)
│ └── benchmark/ # CLI benchmark runner
├── pkg/
│ ├── protos/ # Protocol buffer definitions + generated code
│ └── db/ # PostgreSQL client (accounts, transactions, results)
├── migrations/ # Database schema
└── scripts/ # Seed data generation
The REST server includes a web dashboard for visualizing benchmark results.
# Start the REST server
make rest-server
# Open dashboard in browser
open http://localhost:8080/Features:
- Latency distribution charts — p50/p90/p99 comparison across protocols and clients
- Throughput comparison — req/s bar charts
- Filter controls — filter by scenario, protocol, client
- Results table — detailed view of all benchmark runs
Query benchmark results programmatically:
# Get all results
curl http://localhost:8080/api/v1/results
# Filter by scenario
curl "http://localhost:8080/api/v1/results?scenario=balance_query"
# Filter by protocol and client
curl "http://localhost:8080/api/v1/results?protocol=grpc&client=go"
# Get specific run
curl "http://localhost:8080/api/v1/results?run_id=42"Response format:
{
"results": [
{
"run_id": 42,
"scenario": "balance_query",
"protocol": "grpc",
"client": "go",
"concurrency": 50,
"throughput": 3245.67,
"p50_latency_ms": 12.5,
"p90_latency_ms": 18.2,
"p99_latency_ms": 25.8,
"total_samples": 97370,
"successful": 97370
}
],
"count": 1
}- Latency: p50, p90, p99, min, max, average
- Throughput: Requests/second, events/second
- Error rates: By error type
- Resource usage: CPU, memory (optional)
Results are stored in PostgreSQL (benchmark_runs, benchmark_samples tables) with a benchmark_stats view for analysis.
Environment variables:
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_USER |
benchmark |
Database user |
DB_PASSWORD |
benchmark_pass |
Database password |
DB_NAME |
grpc_benchmark |
Database name |
GRPC_PORT |
50051 |
gRPC server port |
REST_PORT |
8080 |
REST server port |