What is Roomzin?
Roomzin is a fast, lightweight inventory engine built for booking platforms. It's a single binary (<5 MB) with no external dependencies that can be deployed standalone or as a self-discovering cluster.
The Problem Roomzin Solves
Traditional caching layers struggle with complex travel queries. Booking platforms need to search across multiple filters — price, amenities, location, availability. This typically requires complex cache key design, manual invalidation, dozens of round-trips to join scattered data, and falls back to slow database queries on cache misses.
How Roomzin Helps
Roomzin answers complex inventory queries in a single round-trip, returning every search hit for the hot window. It's designed to handle the ~95% of live traffic that falls within the booking hot-window, while complementing Redis for static key/value operations.
Single Node Quick Start
Get Roomzin Running in Standalone Mode
This guide walks you through setting up a single-node Roomzin instance and performing a simple search using SDKs. We'll use standalone mode for simplicity—no clustering involved.
Configure
Set up configuration files
Start Server
Launch Roomzin in standalone mode
Connect SDK
Query with your preferred language
Configuration Setup
Create three YAML configuration files in your working directory:
roomzin.yml
tcp:
listen_addr: "127.0.0.1"
port: 7777
max_connections: 1000
auth_file: "auth.yml"
api_port: 8080
data_dir: "./data"
maintenance_hour: 3
auth.yml
tokens:
- token: abc123
role: admin
- token: client-token
role: client
codecs.yml
Important: Defines allowed rate features like cancellation policies and room amenities. Order is critical for internal mapping. Changes require admin operation and full restart.
rate_features:
- free_featurelation
- non_refundable
- pay_at_property
- includes_breakfast
- free_wifi
- no_prepayment
- partial_refund
- instant_confirmation
Start Roomzin Server
Launch Roomzin in standalone mode:
Connect & Query with SDK
package main
import (
"fmt"
"time"
"github.com/roomzin/roomzin-go/single"
"github.com/roomzin/roomzin-go/types"
)
func main() {
cfg := single.Config{
Host: "127.0.0.1",
TCPPort: 7777,
AuthToken: "client-token",
}
client, err := single.New(&cfg)
if err != nil {
panic(err)
}
defer client.Close()
err = client.SetProp(types.SetPropPayload{
Segment: "segment_1",
Area: "New York",
PropertyID: "550e8400-e29b-41d4-a716-446655440000",
PropertyType: "hotel",
Category: "luxury",
Stars: 4,
Latitude: 40.7138,
Longitude: -74.0050,
Amenities: []string{"wifi", "pool", "gym"},
})
if err != nil {
panic(err)
}
dates := make([]string, 0, 10)
tomorrow := time.Now().AddDate(0, 0, 1)
for i := 0; i < 10; i++ {
dates = append(dates, tomorrow.AddDate(0, 0, i).Format("2006-01-02"))
}
for i := 0; i < 10; i++ {
avail := uint8(10)
price := uint32(150)
err = client.SetRoomPkg(types.SetRoomPkgPayload{
PropertyID: "prop_1",
RoomType: "single",
Date: dates[i],
Availability: &avail,
FinalPrice: &price,
RateFeature: []string{"free_featurelation", "pay_at_property"},
})
if err != nil {
panic(err)
}
}
var limit uint64 = 10
results, err := client.SearchAvail(types.SearchAvailPayload{
Segment: "segment_1",
RoomType: "single",
Date: dates[:2],
Limit: &limit,
})
if err != nil {
panic(err)
}
for _, prop := range results {
fmt.Printf("Property: %s\n", prop.PropertyID)
for _, day := range prop.Days {
fmt.Printf(" Date: %s, Avail: %d, Price: %d\n", day.Date, day.Availability, day.FinalPrice)
}
}
}
import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import com.roomzin.roomzinjava.api.CacheClientApi;
import com.roomzin.roomzinjava.client.single.SingleClient;
import com.roomzin.roomzinjava.client.single.SingleConfig;
import com.roomzin.roomzinjava.types.SetPropPayload;
import com.roomzin.roomzinjava.types.SetRoomPkgPayload;
import com.roomzin.roomzinjava.types.SearchAvailPayload;
import com.roomzin.roomzinjava.types.PropertyAvail;
public class QuickStart {
public static void main(String[] args) {
SingleConfig config = SingleConfig.builder()
.withHost("127.0.0.1")
.withTcpPort(7777)
.withAuthToken("client-token")
.withTimeout(Duration.ofSeconds(5))
.build();
try (CacheClientApi client = new SingleClient(config)) {
// Set property
client.setProp(SetPropPayload.builder()
.segment("segment_1")
.area("New York")
.propertyId("550e8400-e29b-41d4-a716-446655440000")
.propertyType("hotel")
.category("luxury")
.stars((short) 4)
.latitude(40.7138)
.longitude(-74.0050)
.amenities(List.of("wifi", "pool", "gym"))
.build());
// Set room packages for 10 days
List dates = new ArrayList<>();
LocalDate tomorrow = LocalDate.now().plusDays(1);
for (int i = 0; i < 10; i++) {
dates.add(tomorrow.plusDays(i).toString());
}
for (String date : dates) {
client.setRoomPkg(SetRoomPkgPayload.builder()
.propertyId("prop_1")
.roomType("single")
.date(date)
.availability((short) 10)
.finalPrice(150)
.RateFeature(List.of("free_featurelation", "pay_at_property"))
.build());
}
// Search availability
List results = client.searchAvail(SearchAvailPayload.builder()
.segment("segment_1")
.roomType("single")
.date(dates.subList(0, 2))
.limit(10L)
.build());
for (PropertyAvail prop : results) {
System.out.println("Property: " + prop.getPropertyId());
for (DayAvail day : prop.getDays()) {
System.out.printf(" Date: %s, Avail: %d, Price: %d%n",
day.getDate(), day.getAvailability(), day.getFinalPrice());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
from roomzin_py.single import new_config_builder, new_client
from roomzin_py.api import CacheClientAPI
from roomzin_py.types.request import SetPropPayload, SetRoomPkgPayload, SearchAvailPayload
from roomzin_py.types.response import PropertyAvail
from datetime import datetime, timedelta
config = new_config_builder() \
.with_host("127.0.0.1") \
.with_tcp_port(7777) \
.with_token("client-token") \
.with_timeout(5.0) \
.build()
client: CacheClientAPI = new_client(config)
try:
# Set property
client.set_prop(SetPropPayload(
segment="segment_1",
area="New York",
property_id="550e8400-e29b-41d4-a716-446655440000",
property_type="hotel",
category="luxury",
stars=4,
latitude=40.7138,
longitude=-74.0050,
amenities=["wifi", "pool", "gym"]
))
# Set room packages for 10 days
dates = []
tomorrow = datetime.now() + timedelta(days=1)
for i in range(10):
dates.append((tomorrow + timedelta(days=i)).strftime("%Y-%m-%d"))
for date in dates:
client.set_room_pkg(SetRoomPkgPayload(
property_id="prop_1",
room_type="single",
date=date,
availability=10,
final_price=150,
rate_feature=["free_featurelation", "pay_at_property"]
))
# Search availability
results = client.search_avail(SearchAvailPayload(
segment="segment_1",
room_type="single",
date=dates[:2],
limit=10
))
for prop in results:
print(f"Property: {prop.property_id}")
for day in prop.days:
print(f" Date: {day.date}, Avail: {day.availability}, Price: {day.final_price}")
finally:
client.close()
using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Roomzin.Sdk.Internal.Api;
using Roomzin.Sdk.Internal.Single;
using Roomzin.Sdk.Types.Requests;
using Roomzin.Sdk.Types.Responses;
class QuickStart
{
static async Task Main()
{
var config = new ConfigBuilder()
.WithHost("127.0.0.1")
.WithTcpPort(7777)
.WithToken("client-token")
.WithTimeout(TimeSpan.FromSeconds(5))
.Build();
await using var client = Client.New(config);
try
{
// Set property
await client.SetPropAsync(new SetPropPayload
{
Segment = "segment_1",
Area = "New York",
PropertyId = "550e8400-e29b-41d4-a716-446655440000",
PropertyType = "hotel",
Category = "luxury",
Stars = 4,
Latitude = 40.7138,
Longitude = -74.0050,
Amenities = new[] { "wifi", "pool", "gym" }
});
// Set room packages for 10 days
var dates = new List();
var tomorrow = DateTime.Now.AddDays(1);
for (int i = 0; i < 10; i++)
{
dates.Add(tomorrow.AddDays(i).ToString("yyyy-MM-dd"));
}
foreach (var date in dates)
{
await client.SetRoomPkgAsync(new SetRoomPkgPayload
{
PropertyId = "prop_1",
RoomType = "single",
Date = date,
Availability = 10,
FinalPrice = 150,
RateFeature = new[] { "free_featurelation", "pay_at_property" }
});
}
// Search availability
var results = await client.SearchAvailAsync(new SearchAvailPayload
{
Segment = "segment_1",
RoomType = "single",
Date = dates.Take(2).ToArray(),
Limit = 10
});
foreach (var prop in results)
{
Console.WriteLine($"Property: {prop.PropertyId}");
foreach (var day in prop.Days)
{
Console.WriteLine($" Date: {day.Date}, Avail: {day.Availability}, Price: {day.FinalPrice}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
import { SingleClient } from 'roomzin-js';
import { CacheClientAPI } from 'roomzin-js';
async function main() {
const client: CacheClientAPI = await SingleClient.create({
host: '127.0.0.1',
tcpPort: 7777,
authToken: 'client-token',
timeout: 5000,
keepAlive: 30000,
});
try {
// Set property
await client.setProp({
segment: 'segment_1',
area: 'New York',
propertyID: '550e8400-e29b-41d4-a716-446655440000',
propertyType: 'hotel',
category: 'luxury',
stars: 4,
latitude: 40.7138,
longitude: -74.0050,
amenities: ['wifi', 'pool', 'gym'],
});
// Set room packages for 10 days
const dates: string[] = [];
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
for (let i = 0; i < 10; i++) {
const d = new Date(tomorrow);
d.setDate(d.getDate() + i);
dates.push(d.toISOString().split('T')[0]);
}
for (const date of dates) {
await client.setRoomPkg({
propertyID: 'prop_1',
roomType: 'single',
date,
availability: 10,
finalPrice: 150,
RateFeature: ['free_featurelation', 'pay_at_property'],
});
}
// Search availability
const results = await client.searchAvail({
segment: 'segment_1',
roomType: 'single',
date: dates.slice(0, 2),
limit: 10,
});
for (const prop of results) {
console.log(`Property: ${prop.propertyID}`);
for (const day of prop.days) {
console.log(` Date: ${day.date}, Avail: ${day.availability}, Price: ${day.finalPrice}`);
}
}
} catch (err) {
console.error(err);
} finally {
await client.close?.();
}
}
main();
Cluster Quick Start
Local Docker Testing Environment
Testing Environment Only
This Docker setup is for local testing only. Production deployment uses bare metal/VMs for optimal performance.
Production Reality: Single binary + three files on bare metal/VMs. This Docker playground lets you test cluster features like auto-joining and snapshot loading in minutes.
The Roomzin Quickstart repository provides a complete Docker-based testing environment for a 3-node Roomzin cluster with optional monitoring (Prometheus + Grafana).
What You Get
- 3-node Roomzin cluster – pre-configured with static discovery and TLS certificates
- Optional monitoring – Prometheus + Grafana with auto-imported Roomzin dashboard
- Standalone HTML dashboard – quick visual overview without Grafana
- Makefile automation – one-command start/stop/test
Quick Start Commands
git clone https://github.com/m-javani/roomzin-quickstart cd roomzin-quickstart docker pull mehdyjavany/roomzin:latest docker pull mehdyjavany/rzgate:latest make stop && make start
Complete documentation, examples, and setup instructions available on GitHub
Note: The cluster can start fresh or load a user-provided snapshot created as described in the Bulk Load section. See the repository README for detailed instructions.
Data Model at a Glance
Everything hinges on three primitives:
Property
Hotel or accommodation unit
Room Type
Sellable room category
Daily Package
Bookable daily inventory
Together they form a self-contained, in-memory state machine that powers every update and search without external caches or joins.
Roomzin loads the "hot window" (15, 30, 60 days or more) and keeps it mutable—add, remove, or patch availability and prices in real time while queries remain instant.
Property
A hotel or accommodation unit, sharded into segments that mirror real-world search patterns. Paris's ~1.8K hotels, for instance, split into 5 segments for parallel ingest and query.
Room Type
The sellable rooms—Standard Double, Deluxe Suite, etc.—that belong to each property.
Daily Package
A daily, bookable slice of a room type within the hot window, carrying:
Zero-Ops, In-Memory Inventory Engine
Drop Roomzin in and you get a zero-ops, in-memory inventory engine—no caches, expiry headaches, or joins required. Plug in and run at speed.
Constraints by Design
Deliberate performance trade-offs for optimal speed and reliability
Critical Considerations
-
Any change to
codecs.ymlwill invalidate existing queries - Requires a full restart of the cluster or standalone instance
- All existing snapshots will be dropped as they become invalid
- Modifications should be treated as an admin-level operation
Codecs Configuration
Rate features must be defined in codecs.yml
file. This file follows a specific schema, with up to
24 rate features.
Performance Trade-off: The limits of 24 rate features may seem restrictive, but they are a deliberate trade-off for optimal performance.
Schema Definition
The schema for codecs.yml:
rate_features:
- free_featurelation
- non_refundable
# ... up to 24 entries
Order Sensitivity: The order of entries is critical, as Roomzin uses this file for internal mapping.
Rate / Cancellation
Max: 24 entriesExample values (comma-separated):
Deployment
Infrastructure-agnostic, performance-first
Roomzin is a standalone binary optimized for microsecond-level latency. It runs natively on any infrastructure — from dedicated servers to virtual machines — without external dependencies. For maximum throughput and deterministic performance, deploy Roomzin directly on the host system and avoid additional runtime layers such as containers or orchestration stacks.
Stand-alone Deployment
Single-node or development environments
Just drop the binary next to its config, auth and codecs.yml
file and run.
Clustered Deployment
High-availability production cluster
Place the required files (config, auth, codecs, *.pem) on each machine and start the same binary as shown below. Nodes auto-discover each other—only a single seed is enough to pull the full peer list, provided the cluster graph stays connected.
Cluster Formation Modes
Founding Voter
Include --initial_voters
with identical node lists on all founding nodes
Learner
Omit --initial_voters
to join as non-voting replica
Three-node Founding Cluster Example
Example for shard1,
zone1:
Adding a Learner Node to Existing Cluster
Snapshot restore & consistency note
-
When starting a brand-new cluster from a snapshot, a strict majority of the nodes listed
in
--initial_votersmust be restored with the exact same snapshot. - The remaining founding voters and all learners can start completely empty — they will safely download the snapshot from the leader once the cluster forms.
- Empty nodes are never conflicting. Only divergent log histories cause issues.
-
New nodes not listed in
--initial_votersstart as learners and wait for the founding voters to elect a leader before catching up.
Service Discovery: Roomzin, SDKs and RZGate need to resolve node identities to network addresses. Roomzin supports Static (stable IPs) or HTTP (dynamic environments) discovery. You provide a simple HTTP endpoint — no embedded Consul, Kubernetes, or Istio dependencies. This keeps Roomzin lightweight and infrastructure-agnostic. Click on the Discovery link on sidebar to see more details
Bulk Data Loading & Migration
Need to bootstrap or migrate Gigabytes of existing data? See the Bulk Load section — Roomzin can ingest a pre-built binary snapshot at over 1 million records per second per node.
Build the snapshot offline from your current database, distribute it to the cluster, and start with all historical data instantly available.
Roomzin adapts to your infrastructure — not the other way around.
HTTP/JSON Access with RzGate
For platforms that need HTTP/JSON access instead of native SDK integration, Roomzin offers RzGate — a standalone HTTP proxy that sits between legacy clients and your Roomzin cluster.
RzGate converts REST/JSON calls to Roomzin's binary protocol with minimal overhead (~10–15%), making it ideal for:
- Existing HTTP-based booking engines
- Third-party systems and partners
- Quick testing and staging environments
Continue with native SDKs for production systems; use RzGate for legacy integration or temporary access.
Bulk Load into an Offline Snapshot
Roomzin enables efficient data initialization by creating snapshots directly from CSV files.
This bulk loading method is significantly faster than traditional insert queries, capable of
ingesting over 2.5 million records per second.
Use the build-snapshot command
to generate snapshots, which are automatically loaded from your configured
data_dir at startup.
This is a one-time initialization procedure for new shards. To minimize synchronization time with ongoing database updates, perform this load during low-traffic periods. Once complete and the live cluster is synchronized, this process does not need to be repeated.
Preparation
To create a snapshot, prepare these two CSV files and place them in a directory:
properties.csv
Defines properties with their details and attributes
packages.csv
Defines room types, availability, pricing, and rate cancellation details
Command
Parameters
properties.csv and packages.csvdata_dir in configuration)
CSV File Formats
properties.csv
prop_1,segment_1,New York,hotel,test,4,40.713800,-74.005000,wifi|pool|gym|parking
UUID string (36 characters) or ASCII text (max 14 characters). Examples: "550e8400-e29b-41d4-a716-446655440000" or "prop_1"
Primary grouping key for data partitioning and sharding. Choose segments that evenly distribute your properties (e.g., geographic regions, business units, or logical groups). This is a required field in search queries and critically impacts performance.
Secondary grouping for finer data organization within a segment. Use areas to further categorize properties (e.g., neighborhoods, districts) for more targeted searches.
packages.csv
prop_1,room_1,2025-10-03,7,120,free_featurelation|non_refundable|pay_at_property|includes_breakfast
Matches a property in properties.csv
Room type identifier (e.g., "room_1"; up to 256 types, recommended <10 for optimal performance)
Next Steps
After running the command, place the generated snapshot files in the
data_dir configured in
roomzin.yml.
Snapshots load quickly at startup, as demonstrated in our benchmarks.
After Roomzin loads the seed snapshot, run one final delta sync to capture any transactions that arrived while the snapshot was being built. Use the SDK to stream these missing events.
Authentication
Token-based authentication with role-based access control (RBAC) for secure access to the admin API and system operations.
Authentication Overview
Roomzin uses token-based authentication with role-based access control (RBAC) for the
admin API and certain actions. Tokens are defined in a separate YAML file specified in
the server configuration (auth_file).
Authentication File Format
tokens:
- token: abc123
role: admin
- token: c789
role: client
- token: mon456
role: monitoring
Roles and Permissions
Detailed breakdown of access levels for each role
role: admin
Full system access to all endpoints and administrative commands including:
role: client
Full access to SDK commands (excluding administrative operations):
Includes all Monitoring role permissions
role: monitoring
Read-only access to system status endpoints:
/healthz
/metrics
/node-info
/leader
/peers
(GET requests only)
Request Authorization
All admin API requests require a Bearer token in the Authorization header:
Invalid or unauthorized tokens return 401 or
403
errors.
Dynamic Token Management
In clustered mode, tokens can be reloaded without restart via the /reload-tokens
endpoint (admin role required).
Codecs
Schema definitions for rate features with performance-optimized limits.
Critical Considerations
-
Any change to
codecs.ymlwill invalidate existing queries - Requires a full restart of the cluster or standalone instance
- All existing snapshots will be dropped as they become invalid
- Modifications should be treated as an admin-level operation
Codecs Overview
Performance Trade-off: The limits of 24 rate features may seem restrictive, but this is a deliberate trade-off for optimal performance - hardly a real cap in real life.
Schema Definition
The schema for codecs.yml:
rate_features:
- free_featurelation
- non_refundable
# ... up to 24 entries
Order Sensitivity: The order of entries is critical, as Roomzin uses this file for internal mapping.
File Placement
The codecs.yml
file must be placed in the same directory as the main configuration file (roomzin.yml).
Configuration
Roomzin uses a YAML configuration file to define server settings and behavior.
Configuration File Location
The roomzin.yml
file is automatically located in one of these paths (in order of priority):
--config
CLI argument
./roomzin.yml)
/etc/roomzin/roomzin.yml)
The configuration includes sections for core allocation, TCP settings, cluster settings, authentication file path, API port, data directory, and maintenance scheduling. Below is a sample configuration with default values explained:
core_config:
sys_cores_count: 0
# Cores dedicated to system (TCP/QUIC/routing/API). Default: 0 (auto ≈50% of total cores; rest go to processors)
tcp:
listen_addr: "127.0.0.1" # TCP listen address (default: "127.0.0.1")
port: 7777 # TCP port for main service queries (default: 7777)
max_connections: 10000 # Maximum concurrent connections (default: 10000)
tcp_user_timeout_ms: 200 # TCP user timeout in milliseconds (default: 200)
tcp_keepalive_seconds: 30 # TCP keepalive interval in seconds (default: 30)
tcp_recv_buffer_size: 262144 # TCP receive buffer size in bytes (default: 256KB)
tcp_send_buffer_size: 131072 # TCP send buffer size in bytes (default: 128KB)
cluster:
# Node identity is set via CLI (--node-id, --shard-id, --zone-id)
port: 17777 # Cluster port for node-to-node communication (default: 17777)
cert_path: "" # Path to TLS certificate file (required for clustered mode)
key_path: "" # Path to TLS private key file (required for clustered mode)
ca_cert_path: "" # Path to CA certificate file (required for clustered mode)
discovery_kind: "static" # Discovery mechanism: static or http (default: static)
discovery_yml_path: "./discovery.yml" # Path to discovery YAML (required for static discovery)
discovery_http_host: "" # HTTP host for discovery (required for http discovery)
auth_file: "" # Path to authentication YAML file (required)
api_port: 0 # HTTP port for admin API (required; e.g., 8080)
data_dir: "" # Path to data directory (required; must be writable)
maintenance_hour: 0 # Hour (0-23) for scheduled maintenance (default: 0)
max_dates_in_query: 14 # Maximum number of dates in search queries (default: 14)
query_limit: 200 # Maximum number of records in search results (default: 200)
snapshot_interval_sec: 60 # Raft snapshot interval in seconds (default: 60)
snapshot_trigger_count: 10000 # Raft WAL entries threshold for snapshot (default: 10000)
wal_flush_threshold: 2000 # WAL flush batch size (default: 2000)
Cluster Identity via CLI
The following cluster fields are not defined in the config file—they are provided via CLI:
--node-id– Unique node identifier--shard-id– Shard identifier--zone-id– Zone identifier--initial-voters– Comma-separated list of founding voter nodes
This allows the same config file to be used across all nodes in a cluster.
Codecs Configuration
The codecs.yml
file defines rate features
like cancellation policies and room amenities. You can specify its location using the
--codecs flag:
If not specified, Roomzin looks for codecs.yml
in the same directory as your config file.
Discovery Configuration
The discovery_kind
setting determines
how nodes find each other:
- static – Requires
discovery_yml_pathto point to a YAML file with peer list - http – Requires
discovery_http_hostto point to an HTTP discovery service
The configuration is validated at startup to ensure the required fields are present for your chosen discovery method.
Configuration Notes
Core Allocation Configuration
sys_cores_count
System Cores
Number of CPU cores dedicated to system tasks (TCP/QUIC handling, routing, API, serialization, locks). These cores are critical for low tail latency and stable throughput under high request rates.
Default: 0 (auto ≈50% of total cores, rounded down). The remaining cores are automatically assigned to state processors (search, filtering, availability checks).
Performance impact:
• Too few system cores → routing, serialization and wire writes become
bottlenecks → increased tail latency.
• Too many system cores → fewer cores for processors → reduced search throughput
and higher query latency.
• Sweet spot (typically 40–60%) depends on workload size and hardware — auto
default works well for most deployments.
Core allocation is tuned internally for predictable performance. Defaults
provide good balance for most use cases.
Advanced users can override sys_cores_count after benchmarking on
their specific hardware and dataset.
TCP Buffer Configuration
tcp_recv_buffer_size
Receive Buffer Size
Size of the TCP receive buffer (in bytes) for incoming client requests. Larger values can improve throughput for high-volume workloads but increase memory usage. Default: 256KB.
tcp_send_buffer_size
Send Buffer Size
Size of the TCP send buffer (in bytes) for server responses. A low value can bottleneck performance, especially for slow-reading clients that don't read responses from the network quickly. Default: 128KB.
Note: Set this based on your client's reading speed and dataset size. Larger datasets or slower clients may need a higher value. Test incrementally to avoid wasting memory while ensuring smooth data flow.
Important Considerations
The data directory must be validated as writable during startup and stores both snapshots and WAL data.
For clustered mode, provide valid TLS certificate paths for secure node-to-node communication.
snapshot_interval_sec
and snapshot_trigger_count
control Raft snapshot frequency to manage WAL growth and recovery time.
wal_flush_threshold
controls the batch size for WAL flushes. Default: 2000 entries.
REST API
API Overview
Roomzin provides a comprehensive HTTP admin API for monitoring and cluster management. All endpoints require Bearer token authentication and return JSON responses unless otherwise specified.
Authentication
All API requests require Bearer token authentication in the Authorization header:
Tokens are configured via the authentication system and grant different access levels based on roles.
Common Endpoints
Available in both standalone and clustered modes:
| Endpoint | Method | Description | Required Role | Response Format |
|---|---|---|---|---|
| /healthz | GET |
Node health status
Standalone: "active" or "unavailable" |
monitoring+ | Status + Text |
| /metrics | GET |
System metrics in Prometheus format
Standalone: Filters cluster-specific metrics |
monitoring+ | Prometheus Text |
| /save-snapshot | POST |
Trigger manual snapshot creation Initiates immediate snapshot process. Returns conflict if already in progress. |
admin | JSON |
| /codecs | GET |
Retrieve system codecs Returns configured rate features from codecs.yml |
monitoring+ | JSON |
| /shutdown | POST |
Graceful system shutdown
Request Body: |
admin | JSON |
Clustered-Only Endpoints
Available only when running in clustered mode:
| Endpoint | Method | Description | Required Role | Response Format |
|---|---|---|---|---|
| /reload-tokens | POST |
Reload authentication tokens Refresh authentication tokens from disk without restart |
admin | JSON |
| /node-info | GET |
Get detailed node information Returns: leader_id, leader_url, shard_id, zone_id, node_id |
monitoring+ | JSON Object |
| /leader | GET |
Get current cluster leader Returns URL of the current leader node |
monitoring+ | JSON |
| /add-member/{node_id} | POST |
Promote learner to voting member
Parameters: node_id - Target node identifier |
admin | JSON |
| /remove-member/{node_id} | POST |
Demote voting member to learner
Parameters: node_id - Target node identifier |
admin | JSON |
| /transfer-leader/{node_id} | POST |
Initiate leadership transfer
Parameters: node_id - New leader candidate
identifier |
admin | JSON |
| /tls-rotate | POST |
Rotate TLS certificates Signal TLS certificate rotation for node-to-node QUIC communication |
admin | JSON |
| /peers | GET |
Get cluster peer list Returns array of peer node addresses |
monitoring+ | JSON Array |
Usage Examples
Basic Health Check
Get Node Information
Response Codes
- 200 OK - Request completed successfully
- 202 Accepted - Request accepted for processing (async operations)
- 400 Bad Request - Invalid request parameters or missing confirmation
- 401 Unauthorized - Missing or invalid authentication token
- 403 Forbidden - Insufficient permissions for the requested endpoint
- 409 Conflict - Operation cannot be performed due to current state
- 500 Internal Server Error - Server encountered an error processing request
- 503 Service Unavailable - Node is not ready to handle requests
Metrics Overview
Prometheus metrics for monitoring TCP server and cluster performance
TCP Server Metrics
Real-time monitoring of client connections and server performance
tcp_connections
tcp_commands_total
tcp_bytes_received_total
tcp_bytes_sent_total
tcp_errors_total
tcp_client_errors_total
tcp_client_disconnects_total
tcp_client_login_fail_total
tcp_timeouts_total
tcp_rejected_connections_total
tcp_rejected_request_total
Cluster Metrics
Comprehensive cluster health and performance monitoring
Connection Metrics
cluster_connection_opened_total
cluster_connection_accepted_total
cluster_connection_rejected_total
cluster_connection_error_total
cluster_connection_dropped_total
Request/Response Metrics
cluster_message_sent_total
cluster_message_received_total
cluster_bytes_sent_total
cluster_bytes_received_total
cluster_send_error_total
cluster_receive_error_total
Condition Metrics
cluster_leader_changed_total
Snapshot Metrics
cluster_snapshot_produced_total
cluster_snapshot_size_bytes
cluster_snapshot_save_duration_ms
cluster_snapshot_write_pause_duration_ms
cluster_snapshot_load_duration_ms
cluster_snapshot_failed_count_total
cluster_snapshot_transfer_count_total
cluster_snapshot_transfer_duration_ms
Write-Ahead Logging (WAL) Metrics
cluster_wal_flush_count_total
cluster_last_applied_wal_index
Commands
Roomzin provides SDKs in Go, Java, Python, C#, and Node.js for querying and managing the in-memory inventory engine over TCP. SDKs handle automatic routing, failover, load balancing, and connection pooling. Select an SDK from the left menu to view its documentation.
Property Management
Set Property
Adds a new property or updates an existing one in the system
Room Package Management
Set Room Package
Sets availability, price, and rate features like cancellation policies for a room type on a date
Availability Management
Set Room Availability
Sets the exact availability for a room type on a specific date
Increment Room Availability
Increases the availability for a room type on a specific date
Decrement Room Availability
Decreases the availability for a room type on a specific date
Search & Query
Search for Availability
Queries the system based on search params like room type, dates, price, etc.
Search Properties
Searches properties by segment, area, type, or other criteria
Property Exists
Checks if a property exists in the system
Room Type Exists
Checks if a specific room type exists for a property
List Room Types
Lists all room types for a given property
List Dates of a Room Type
Lists dates set for one of a given property's room type
Get Room Day Details
Retrieves details for a room type on a specific date
List Segments
Lists all segments and their property counts
Delete Operations
Delete Day for a Room Type
Removes a room's package for a specific date
Delete Day for a Property
Removes all packages of a property on a specific date
Delete a Room Type
Removes a specific room type from a property with all its packages
Delete Property
Removes a property and cascade purges its packages
Delete Segment
Removes a segment with all properties within the segment
Command Categories
SDKs & Integration
Why use an SDK?
Roomzin SDKs provide automatic routing, failover, load balancing, and connection pooling.
They handle cluster topology changes, retry logic, and serialization — so you can focus on
your business logic. All SDKs implement the same CacheClientAPI interface and
support both standalone and clustered modes.
Go SDK
GitHubgo get github.com/m-javani/roomzin-go
Java SDK
GitHub
Maven: io.github.m-javani:roomzin-java:1.0.0
Python SDK
GitHubpip install roomzin-py
Node.js SDK
GitHubnpm install roomzin-js
C# / .NET SDK
GitHubdotnet add package Roomzin.Sdk
No SDK for your language? Use the HTTP Proxy
If your platform cannot use one of the native SDKs — or you're integrating with a legacy system — Roomzin provides RzGate, a lightweight HTTP/JSON proxy.
- ✓ REST/JSON — works with any HTTP client
- ✓ Separate scaling — run it alongside your cluster
Benchmarking Roomzin & RzGate
Measure Roomzin's performance with precision.
Comprehensive benchmarking suite for both Roomzin (TCP/binary protocol) and RzGate (HTTP/JSON
proxy).
Supports regular and spike load patterns with customizable queries.
Roomzin Benchmark
- ✓ Direct TCP benchmarking – measures raw protocol performance
- ✓ Search & Update – benchmark both read and write operations
- ✓ Custom queries – use your own
query.yml - ✓ CSV data generator – create realistic test data
- ✓ Regular & Spike modes – sustained and burst load patterns
RzGate Benchmark
- ✓ HTTP/JSON performance – complete end-to-end latency
- ✓ TLS & HTTP/2 – includes secure communication overhead
- ✓ Response validation – sample responses in
http.json - ✓ Existing cluster – benchmarks running RzGate instances
- ✓ ~10–15% overhead – minimal performance impact vs native
Quick Setup
One command downloads everything you need: binaries, config files, certificates, and data generation scripts. Just run and start benchmarking in minutes.
📄 For All Users
The downloaded zip includes a benchmark_guide.txt file with complete
instructions.
No Rust knowledge required.
🦀 For Rust Developers
Clone and build from source:
git clone https://github.com/m-javani/roomzin-bench
Complete documentation, examples, and source code available on GitHub
Service Discovery
The Challenge
Roomzin cluster nodes need to discover each other's network addresses to form a cluster. External components — SDKs and RZGate — also need to know how to reach Roomzin nodes.
However, different infrastructures may use different discovery mechanisms and certificate identity formats.
Rather than baking in support for every possible discovery system, Roomzin abstracts address resolution and leaves the implementation to you — the client team. This keeps Roomzin lightweight and your infrastructure choices unconstrained.
How Discovery Works
Static Discovery
Simple & StableProvide a static mapping of node identities to addresses in your configuration. The client resolves addresses once and never updates them.
Use when:
- Cluster nodes have stable, predictable IPs or hostnames
- Nodes are on bare metal or VMs with static addresses
- You want zero external dependencies
HTTP Discovery
Dynamic & FlexibleConfigure an HTTP endpoint that returns the current node-to-address mapping. Clients periodically fetch updates.
Use when:
- Nodes are in Kubernetes with changing IPs
- Auto-scaling groups add/remove nodes frequently
- You want a single source of truth for addresses
HTTP Discovery API Specification
OpenAPI v3.0.0If you choose HTTP discovery, you provide an API endpoint. Roomzin, RZGate, and SDKs will query it to resolve node identities to addresses. Each component requires a slightly different response format because internal node addresses may not be suitable for external clients.
openapi: 3.0.0
info:
title: Roomzin Discovery API
version: 1.0.0
description: |
Implement these endpoints to let Roomzin components resolve node identities
to network addresses. The three endpoints return the same cluster information
but formatted for different consumers.
servers:
- url: https://your-discovery-service.com
description: Your discovery service endpoint
paths:
/discovery/roomzin:
get:
summary: For Roomzin cluster nodes
description: |
Roomzin nodes use this endpoint when joining a cluster. Returns peer
information including TLS identity for secure node-to-node authentication.
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
version:
type: integer
description: Optional schema version for future compatibility
peers:
type: array
items:
type: object
required:
- node_id
- host
- identity
- port
properties:
node_id:
type: string
description: Roomzin node identifier
host:
type: string
description: IP address, hostname, or URL
port:
type: integer
description: Port for Roomzin communication
identity:
type: object
description: TLS identity for node authentication
properties:
kind:
type: string
enum: [dns, ip, spiffe]
description: Type of SAN used in the certificate
value:
type: string
description: The identity value (DNS name, IP address, or SPIFFE ID)
example:
version: 1
peers:
- node_id: "node1"
host: "10.0.1.10"
port: 8443
identity:
kind: "ip"
value: "10.0.1.10"
- node_id: "node2"
host: "node2.roomzin.internal"
port: 8443
identity:
kind: "dns"
value: "node2.roomzin.internal"
/discovery/rzgate:
get:
summary: For RZGate instances
description: RZGate uses this endpoint to discover which Roomzin nodes to route requests to.
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
nodes:
type: array
items:
type: object
required:
- node_id
- addr
properties:
node_id:
type: string
description: Roomzin node identifier
addr:
type: string
description: Publicly accessible IP or hostname
port:
type: integer
description: Optional port (defaults to RZGate's configured port)
example:
nodes:
- node_id: "node1"
addr: "10.0.1.10"
port: 8080
- node_id: "node2"
addr: "10.0.1.11"
port: 8080
/discovery/sdk:
get:
summary: For SDK clients
description: |
SDKs fetch this to connect directly to Roomzin nodes.
Separates TCP (data streaming) and API (control) ports.
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
nodes:
type: array
items:
type: object
required:
- node_id
- addr
properties:
node_id:
type: string
description: Roomzin node identifier
addr:
type: string
description: Publicly accessible IP or hostname
tcp_port:
type: integer
description: Port for TCP data streaming
api_port:
type: integer
description: Port for HTTP API requests
example:
nodes:
- node_id: "node1"
addr: "10.0.1.10"
tcp_port: 9090
api_port: 8080
- node_id: "node2"
addr: "10.0.1.11"
tcp_port: 9090
api_port: 8080
Port flexibility: Ports are included in the specifications to support different environments — such as tests or container port mappings — where the same node might be reachable on different ports. In production, all nodes typically use the same port for a given service.
Understanding TLS Identity
Roomzin uses QUIC for secure node-to-node communication. The QUIC server's TLS library automatically verifies certificates, but verification can fail if clients use different Subject Alternative Name (SAN) types.
The identity field
tells Roomzin nodes how to verify each other's identities:
- kind: "dns" — Certificate SAN contains DNS names, value is the DNS name
- kind: "ip" — Certificate SAN contains IP addresses, value is the IP address
- kind: "spiffe" — Certificate SAN contains SPIFFE IDs, value is the SPIFFE ID
Important: All nodes in the cluster must use the same
kind
to ensure successful TLS handshakes.
Response Format Comparison
{
"peers": [{
"node_id": "node1",
"host": "10.0.1.10",
"port": 8443,
"identity": {
"kind": "ip",
"value": "10.0.1.10"
}
}]
}
Includes TLS identity for secure node authentication
{
"nodes": [{
"node_id": "node1",
"addr": "10.0.1.10",
"port": 8080
}]
}
External address for request routing
{
"nodes": [{
"node_id": "node1",
"addr": "10.0.1.10",
"tcp_port": 9090,
"api_port": 8080
}]
}
External addresses with separate data and API ports
Frequently Asked Questions
Everything you need to know about Roomzin's architecture and performance
What is Roomzin?
A fast and light inventory engine built for booking platforms.
Run it as a single instance behind your backend nodes, or spin up a cluster when you need HA and speed through horizontal scaling.
What do you mean by light?
- One self-contained binary (<5 MB) handles single mode, cluster mode, and seed-data ingest. No extra tools or dependencies.
- Feed it 5 GB of data and it uses ~2.5 GB RAM.
- Its portable snapshot on disk is even tinier.
What do you mean by fast?
Under heavy traffic it answers complicated search queries with big payloads in a few milliseconds,
beating every other tool.
Is that all?
Of course not. It’s also simple: simple to deploy, simple to use.
Explain the context
Planet-wide sharding
Global booking platforms serve the entire planet by dividing it into regional shards. Traffic is routed
to the relevant shard, where multiple nodes provide high availability and load balancing. Each node
stores only the inventory for its specific zone..
Cache is mandatory—yet painful
A cache layer sits in front of the database to hide disk latency. Sounds easy—until you discover that a
cache is just a key-value bucket. Teams must design keys, set TTLs, invalidate on every update, and
still make dozens of round-trips to collect scattered bits and join them in memory.
The ugly query
And that still doesn’t cover the hard query: “Find me two rooms, under $120 a night, breakfast included,
pay-at-property, cancellable, for five nights, in a 4-star business hotel or motel, pet-friendly, with
pool, gym, restaurant and a view, somewhere near this lat/lng.” The filter list is 100 k properties
long; availability changes by the second; price changes by the minute.
DB fallback = latency killer
Fall back to the database? Latency murders high-TPS targets. Replicate for HA? More boxes, more shards,
more midnight pages, more budget.
Roomzin steps in here
Keep reading to see where Roomzin slips into this picture and makes the noise quiet.
How Roomzin fits in?
Seat at the table
Roomzin parks right next to your cache, on top of the DB. One round-trip query—faster than any in-memory
DB—returns every search hit for the hot window.
- Full-blown DB? Nope. It accelerates only the booking hot-window (next weeks/months) that absorb ~95 % of live traffic.
- Window cap? None hard-coded. Admins keep only the interval worth RAM—usually 15-90 days.
- Tail-latency guard-rails Admins can soft-cap “max nights” and “max results per query” so a 10 k/s TPS never drags MS into seconds; edge cases spill to the main DB.
- Any constraints on data? Just one: Up to 24 rate features—like cancellation policies, included breakfast, or WiFi—must be defined in your codecs.yml file to use as package filters. This limit isn’t fixed by the product, but is set by companies to cover 95% of real‑world cases—a deliberate trade‑off for speed and simplicity.
- Redis killer? No. Redis stays for static key/value chores; Roomzin adds travel-native schema and single-query speed for hot searches.
Traffic flow in 3 steps
- Backend fires one complex search to Roomzin.
- Roomzin replies with matching property IDs + nightly room details for the whole stay.
- Client scrolls → platform fetches static hotel info from Redis only for items in view.
Result: 95 % of queries finish in milliseconds, Redis keeps doing what it loves, and your DB breathes again.
How easy is it to deploy Roomzin?
Standalone
One binary, three YAML files (config + auth + codecs list).
Run it. Done.
Cluster
Same binary on every node; they self-discover, elect a leader, and wire themselves into a secure
cluster.
You only supply:
- a seed list (one node name is enough)
- certificates (.pem files) for TLS
Scale? Add a node → cluster grows. Remove a node → cluster shrinks. Zero config change, zero downtime.
How to connect to Roomzin? Who routes the requests?
No custom routing layer needed.
Roomzin lives inside one shard; all traffic stays internal to that shard.
SDKs do the heavy lifting
- Go, Java, Python, C#, Node.js
- Topology-aware: give any single node name, the SDK downloads the cluster map
- Writes and reads are auto-routed to the fastest node(s) with built-in load-balancing
- Leader crash? New election? Node disappears? SDK reroutes instantly—no code change, no restart
- Need HTTP? Use RzGate — the standalone HTTP/JSON proxy for legacy systems
Only infra requirement: a DNS resolver so cluster and SDKs can resolve node IPs. That’s it.
What does the schema look like?
Three primitives:
- Property: segment · area · property id (uuid or short string) · type · category · stars · geo(lat+lon) · amenities
- Room Type: standard . double . deluxe . suite . etc...
- Daily Package: date · availability · final price · rate & features
Hot window (15-90 days or more) lives entirely in RAM; update or patch any field in real time while searches stay instant.
All commands/queries in SDKs are around these entities for millions of records
Start fresh or seed from existing DB?
Bulk seed in seconds
Point Roomzin at a CSV snapshot: it ingests > 2.5 M records/sec, zero INSERT queries.
One-time task per fresh shard
Run it once for a fresh shard; when the snapshot lands the cluster starts live-sync and never needs a
repeat.
Final whisk
Stream the last delta (whatever arrived while this process) with SDK.
Is data partitioned across Roomzin nodes?
Nope. Every node keeps the entire state in RAM. The leader replicates each write to all followers; consistent state arrives in single-digit milliseconds—no sharding keys, no cross-node joins, no surprises.
Are writes instant under heavy load?
Yes. Read and write paths are decoupled; the write channel always gets priority, so even when search traffic floods the queue, updates still commit in milliseconds.
Does Roomzin partition data inside a node?
Segments = inner parallelism
Each node splits its copy of the shard into user-defined segments (think “micro-regions” or “search
buckets”).
- Every segment owns its own processing queue — thousands of Paris properties? Split into 5-10 segments and let all cores run in parallel.
- No geography baked into IDs — devs choose segment keys that mirror real-world search patterns (city name, district, GeoHash, custom hash).
- Segment is mandatory in every search—just like picking a city
Use it when you want to squeeze every last core; skip it when your data is already cozy.
How visible is Roomzin?
Prometheus-native
Every node exposes metrics in plain Prometheus format: TCP activity, cluster connectivity, request flow,
system state, snapshots, WAL ops—Grafana-ready out of the box.
REST self-description
Each node also serves health check, node info, known peers, current role, known leader, last snapshot
info, and
quorum size—no guessing, just query.
Can the REST API do more than read?
Absolutely.
Admins also write: shutting down a node, update peers, trigger a snapshot, or rotate TLS certs—all without SSH or restart.
How does a node join the cluster?
- Discovery & Quorum
Starts in “Joining” mode, replays its local WAL, then knocks on every seed address. Shard-ID and certificates must match; if the cluster already exists it learns the current leader, otherwise it jumps into the election game. - State Sync
Compares its snapshot with the leader’s. Missing bytes? Streams the newest snapshot from a same-zone buddy first, falls back to any node, then rebuilds the in-memory state. - Log Catch-up
Downloads the WAL segment that starts right after the snapshot, replays every entry, and keeps ingesting live writes until it’s byte-perfect with the leader. - Go Active
Status flips to “Active”; the node now votes, replicates, and serves reads like every other member—zero manual steps, zero downtime.
Auto-maintenance?
Pick a low-traffic hour to trigger the tasks (single node or whole cluster). Roomzin drops yesterday’s expired data, then snaps a fresh checkpoint—no manual cleanup, no extra scripts.
Roomzin Distributed Consensus with TiKV Raft
Roomzin implements distributed consensus using the TiKV Raft crate, a production-proven implementation derived from etcd's Raft algorithm and inspired by HashiCorp's Raft research:
- Optimized failure detection with automatic node recovery and cluster reconfiguration
- Leader-based replication with write propagation to majority quorum before client acknowledgment
- Single-digit millisecond consensus latency with strong consistency guarantees
- Battle-tested implementation evolved from etcd's core Raft implementation
Production-Proven Foundation
Our consensus layer builds upon the TiKV Raft implementation, a CNCF graduated project derived from etcd's battle-tested Raft algorithm with enhancements inspired by HashiCorp's Raft research. This foundation provides the same reliability used in large-scale distributed systems across multiple industries, ensuring robust distributed coordination for Roomzin's real-time collaboration features.
What protocol do clients speak?
A custom, binary protocol—zero text parsing, zero wasted bits, built to serve high QPS pipelines full without breaking a sweat.
How do nodes talk to each other?
QUIC + TLS with auto certificate rotation—encrypted by default, zero-handshake latency, fast stream replication, and safe across data-centers. (Just keep valid certs on every node.)
What if my platform only speaks HTTP?
Use RzGate — roomzin's standalone HTTP/JSON proxy. It sits between your legacy HTTP clients and Roomzin, converting REST/JSON calls to Roomzin's native protocol with only ~10–15% overhead. Perfect for existing booking engines, third-party systems, or quick testing without SDK integration.
Is TLS enabled on the client TCP port?
Nope. Roomzin sits inside your private mesh; leave TLS termination to the load-balancer or service mesh you already run.
Who can do what?
Three built-in roles, no extra plugins:
- monitoring – read metrics & safe GET endpoints only
- client – CRUD on TCP, but just GET on REST
- admin – full control: writes, config, shutdown, snapshots, TLS rotate
Lock them down with sdk credentials or token headers — done.
What language is Roomzin built in?
100 % Rust—zero-GC, fearless concurrency, single 5 MB binary that ships everything it needs.
What kind of performance can I expect?
- Resilient & predictable – under any connection mix, data volume, or traffic
- Median latency – single-digit ms at million-record scale
- Snapshots – < 1 s for millions of records
- Reload – millions of records in (few) second(s)
- Memory footprint – ≤ 50 % of raw data size
Recap on the key capabilities?
Built for booking-scale workloads
- Any topology: single node → clustered
- HA: leader/follower, WAL & snaps
- Zero-ops: auto-discovery, join, catch-up
- Secure wire: QUIC + TLS auto-rotate
- RBAC & realtime metrics
- Partitioned data for parallel speed
- Hot snapshots under load in cluster mode
- Smart SDKs: Java, Go, Python, C#, Node.js
Easy integration
- SDKs: auto-route, failover, retry, one-liner install
- Deployment: <5 MB binary, zero external deps
- Load from DB: CSV seed at +2.5 M records/sec
- HTTP access: RzGate proxy for legacy/REST clients