GitHub

Getting Started with ArcherDB

Time to First Query

Total time: ~10 minutes

Section Time Description
Prerequisites 0 min Verify requirements
Installation 2 min Download binary
Starting cluster 1 min Format and start
SDK installation 1 min Install your language
Hello World 3 min Insert and query
Next steps 1 min What to explore

This guide gets you to your first spatial query quickly, then shows you the full capabilities.

Prerequisites

  • Operating System: Linux (kernel >= 5.6), macOS, or Windows
  • For SDKs: Python 3.9+, Node.js 18+, Go 1.21+, or Java 11+

Security Boundary Note

ArcherDB expects security controls at the infrastructure boundary:

  • Authentication/authorization at your API or gateway layer
  • TLS/mTLS in gateway/service mesh or private network transport
  • Encryption at rest and key management in storage/cloud platform
  • Backup: ArcherDB has built-in upload to S3, GCS, and Azure Blob (see Backup Operations); platform snapshots remain a defense-in-depth option

Installation (~2 min)

Download the pre-built binary for your platform:

Linux (x86_64)
curl -L https://github.com/ArcherDB-io/archerdb/releases/latest/download/archerdb-linux-x86_64.tar.gz | tar xz
sudo mv archerdb /usr/local/bin/
archerdb --version
macOS (Apple Silicon)
curl -L https://github.com/ArcherDB-io/archerdb/releases/latest/download/archerdb-macos-aarch64.tar.gz | tar xz
sudo mv archerdb /usr/local/bin/
archerdb --version
macOS (Intel)
curl -L https://github.com/ArcherDB-io/archerdb/releases/latest/download/archerdb-macos-x86_64.tar.gz | tar xz
sudo mv archerdb /usr/local/bin/
archerdb --version
Build from Source
git clone https://github.com/ArcherDB-io/archerdb.git
cd archerdb
./zig/download.sh
./zig/zig build
# Binary at ./zig-out/bin/archerdb

Choose a Tier

ArcherDB tier presets are open-source runtime and capacity profiles:

  • lite (recommended for demos/evaluation): fastest first-run experience, low footprint, intentionally storage-limited.
  • standard: baseline production profile.
  • pro: higher-performance mainstream profile.
  • enterprise: high-end production profile.
  • ultra: top-end profile.

If building from source, choose a tier explicitly:

./zig/zig build -Dconfig=lite
# or: standard, pro, enterprise, ultra

Starting a Cluster (~1 min)

Single-Node Development

# Format data file (cluster=0 for dev)
archerdb format --cluster=0 --replica=0 --replica-count=1 data.archerdb

# Start server on port 3000
archerdb start --addresses=3000 data.archerdb

You should see: info: server ready on 127.0.0.1:3000

Production Three-Node Cluster

For fault tolerance (survives 1 node failure):

# Node 1 (192.168.1.1)
archerdb format --cluster=12345 --replica=0 --replica-count=3 /data/archerdb.db
archerdb start --addresses=192.168.1.1:3000,192.168.1.2:3000,192.168.1.3:3000 /data/archerdb.db

# Node 2 (192.168.1.2)
archerdb format --cluster=12345 --replica=1 --replica-count=3 /data/archerdb.db
archerdb start --addresses=192.168.1.1:3000,192.168.1.2:3000,192.168.1.3:3000 /data/archerdb.db

# Node 3 (192.168.1.3)
archerdb format --cluster=12345 --replica=2 --replica-count=3 /data/archerdb.db
archerdb start --addresses=192.168.1.1:3000,192.168.1.2:3000,192.168.1.3:3000 /data/archerdb.db

SDK Installation (~1 min)

Python
pip install archerdb
Node.js
npm install archerdb-node
Go
go get github.com/ArcherDB-io/archerdb/src/clients/go
Java (source checkout / local Maven install)

Until archerdb-java is explicitly published to Maven Central, use a source checkout:

./zig/zig build clients:java -Drelease
(cd src/clients/java && mvn --batch-mode --quiet install)

Then depend on the local Maven artifact version from src/clients/java/pom.xml, which is currently 0.1.0-SNAPSHOT:

<dependency>
    <groupId>com.archerdb</groupId>
    <artifactId>archerdb-java</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

Or Gradle:

implementation 'com.archerdb:archerdb-java:0.1.0-SNAPSHOT'
curl (no SDK)

Use curl directly - no installation needed.

Backup Configuration (Optional)

ArcherDB can stream every closed LSM block to S3, GCS, or Azure Blob storage as the cluster runs. Append the relevant flags to archerdb start:

# S3 / S3-compatible (MinIO, R2, Backblaze, LocalStack)
archerdb start \
  --backup-enabled \
  --backup-provider=s3 \
  --backup-region=us-east-1 \
  --backup-bucket=my-archerdb-backups \
  data.archerdb

# Azure Blob (SharedKey)
archerdb start \
  --backup-enabled \
  --backup-provider=azure \
  --backup-bucket=my-archerdb-container \
  --backup-access-key-id=<account-name> \
  --backup-secret-access-key=<base64-account-key> \
  data.archerdb

S3 credentials are picked up from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY when not provided on the command line. GCS uses the same flags as S3 with --backup-provider=gcs and an HMAC key issued via the Cloud Storage Interoperability console. Restore via archerdb restore from any of the supported providers; see Backup Operations and Disaster Recovery.

Hello World: Vehicle Tracking (~3 min)

This example demonstrates the core value of ArcherDB: track vehicles and find nearby pickups.

Scenario: A delivery vehicle needs to find pickup locations within 1km.

Step 1: Create Client

Python
import archerdb

client = archerdb.GeoClientSync(archerdb.GeoClientConfig(
    cluster_id=0,
    addresses=['127.0.0.1:3000']
))
Node.js
const { createGeoClient, createGeoEvent, id } = require('archerdb-node')

const client = createGeoClient({
  cluster_id: 0n,
  addresses: ['127.0.0.1:3000'],
})
Go
import (
    archerdb "github.com/ArcherDB-io/archerdb/src/clients/go"
    "github.com/ArcherDB-io/archerdb/src/clients/go/pkg/types"
)

client, err := archerdb.NewGeoClient(archerdb.GeoClientConfig{
    ClusterID: types.ToUint128(0),
    Addresses: []string{"127.0.0.1:3000"},
})
if err != nil {
    log.Fatal(err)
}
defer client.Close()
Java
import com.archerdb.geo.*;

GeoClient client = GeoClient.create(0L, "127.0.0.1:3000");
curl
# No client setup needed - use curl directly
BASE_URL="http://127.0.0.1:3000"

Step 2: Insert Delivery Vehicle

Insert the vehicle at SF city center (37.7749, -122.4194):

Python
vehicle_id = archerdb.id()
vehicle = archerdb.create_geo_event(
    entity_id=vehicle_id,
    latitude=37.7749,      # SF city center
    longitude=-122.4194,
    group_id=1,            # Fleet ID
)
client.insert_events([vehicle])
print(f"Vehicle {vehicle_id} inserted")
Node.js
const vehicleId = id()
const vehicle = createGeoEvent({
  entity_id: vehicleId,
  latitude: 37.7749,      // SF city center
  longitude: -122.4194,
  group_id: 1n,           // Fleet ID
})
await client.insertEvents([vehicle])
console.log(`Vehicle ${vehicleId} inserted`)
Go
vehicleID := types.ID()
vehicle, _ := types.NewGeoEvent(types.GeoEventOptions{
    EntityID:  vehicleID,
    Latitude:  37.7749,      // SF city center
    Longitude: -122.4194,
    GroupID:   1,            // Fleet ID
})
client.InsertEvents([]types.GeoEvent{vehicle})
fmt.Printf("Vehicle %s inserted\n", vehicleID)
Java
UInt128 vehicleId = UInt128.random();
GeoEvent vehicle = new GeoEvent.Builder()
    .setEntityId(vehicleId)
    .setLatitude(37.7749)      // SF city center
    .setLongitude(-122.4194)
    .setGroupId(1L)            // Fleet ID
    .build();

GeoEventBatch batch = client.createBatch();
batch.add(vehicle);
batch.commit();
System.out.println("Vehicle " + vehicleId + " inserted");
curl
curl -X POST $BASE_URL/insert \
  -H "Content-Type: application/json" \
  -d '{
    "events": [{
      "entity_id": "00000000-0000-0000-0000-000000000001",
      "lat_nano": 37774900000,
      "lon_nano": -122419400000,
      "group_id": 1
    }]
  }'

Step 3: Insert Nearby Pickup Locations

Add two pickup locations near the vehicle:

Location Coordinates Distance from Vehicle
Pickup 1 37.7751, -122.4180 ~200m east
Pickup 2 37.7760, -122.4200 ~150m north
Python
# Pickup 1: 200m east of vehicle
pickup1 = archerdb.create_geo_event(
    entity_id=archerdb.id(),
    latitude=37.7751,
    longitude=-122.4180,
    group_id=2,  # Pickups group
)

# Pickup 2: 150m north of vehicle
pickup2 = archerdb.create_geo_event(
    entity_id=archerdb.id(),
    latitude=37.7760,
    longitude=-122.4200,
    group_id=2,
)

client.insert_events([pickup1, pickup2])
print("2 pickup locations inserted")
Node.js
// Pickup 1: 200m east of vehicle
const pickup1 = createGeoEvent({
  entity_id: id(),
  latitude: 37.7751,
  longitude: -122.4180,
  group_id: 2n,  // Pickups group
})

// Pickup 2: 150m north of vehicle
const pickup2 = createGeoEvent({
  entity_id: id(),
  latitude: 37.7760,
  longitude: -122.4200,
  group_id: 2n,
})

await client.insertEvents([pickup1, pickup2])
console.log('2 pickup locations inserted')
Go
// Pickup 1: 200m east of vehicle
pickup1, _ := types.NewGeoEvent(types.GeoEventOptions{
    EntityID:  types.ID(),
    Latitude:  37.7751,
    Longitude: -122.4180,
    GroupID:   2,  // Pickups group
})

// Pickup 2: 150m north of vehicle
pickup2, _ := types.NewGeoEvent(types.GeoEventOptions{
    EntityID:  types.ID(),
    Latitude:  37.7760,
    Longitude: -122.4200,
    GroupID:   2,
})

client.InsertEvents([]types.GeoEvent{pickup1, pickup2})
fmt.Println("2 pickup locations inserted")
Java
// Pickup 1: 200m east of vehicle
GeoEvent pickup1 = new GeoEvent.Builder()
    .setEntityId(UInt128.random())
    .setLatitude(37.7751)
    .setLongitude(-122.4180)
    .setGroupId(2L)  // Pickups group
    .build();

// Pickup 2: 150m north of vehicle
GeoEvent pickup2 = new GeoEvent.Builder()
    .setEntityId(UInt128.random())
    .setLatitude(37.7760)
    .setLongitude(-122.4200)
    .setGroupId(2L)
    .build();

GeoEventBatch batch = client.createBatch();
batch.add(pickup1);
batch.add(pickup2);
batch.commit();
System.out.println("2 pickup locations inserted");
curl
curl -X POST $BASE_URL/insert \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "entity_id": "00000000-0000-0000-0000-000000000002",
        "lat_nano": 37775100000,
        "lon_nano": -122418000000,
        "group_id": 2
      },
      {
        "entity_id": "00000000-0000-0000-0000-000000000003",
        "lat_nano": 37776000000,
        "lon_nano": -122420000000,
        "group_id": 2
      }
    ]
  }'

Step 4: Find Pickups Within 1km of Vehicle

Query for pickups near the vehicle’s location:

Python
# Find all entities within 1km of vehicle
results = client.query_radius(
    center_lat=37.7749,
    center_lon=-122.4194,
    radius_m=1000,  # 1 kilometer
    limit=100,
)

print(f"Found {len(results.events)} entities within 1km of vehicle:")
for event in results.events:
    print(f"  Entity {event.entity_id} at ({event.latitude:.4f}, {event.longitude:.4f})")

Output:

Found 3 entities within 1km of vehicle:
  Entity 1234... at (37.7749, -122.4194)  # Vehicle
  Entity 5678... at (37.7751, -122.4180)  # Pickup 1
  Entity 9012... at (37.7760, -122.4200)  # Pickup 2
Node.js
// Find all entities within 1km of vehicle
const results = await client.queryRadius({
  latitude: 37.7749,
  longitude: -122.4194,
  radius_m: 1000,  // 1 kilometer
  limit: 100,
})

console.log(`Found ${results.events.length} entities within 1km of vehicle:`)
for (const event of results.events) {
  console.log(`  Entity ${event.entity_id}`)
}
Go
// Find all entities within 1km of vehicle
filter, _ := types.NewRadiusQuery(37.7749, -122.4194, 1000, 100)
results, _ := client.QueryRadius(filter)

fmt.Printf("Found %d entities within 1km of vehicle:\n", len(results.Events))
for _, event := range results.Events {
    fmt.Printf("  Entity %s at (%.4f, %.4f)\n",
        event.EntityID, event.Latitude(), event.Longitude())
}
Java
// Find all entities within 1km of vehicle
QueryResult results = client.queryRadius(
    QueryRadiusFilter.create(37.7749, -122.4194, 1000, 100)
);

System.out.println("Found " + results.getEvents().size() + " entities within 1km of vehicle:");
for (GeoEvent event : results.getEvents()) {
    System.out.println("  Entity " + event.getEntityId());
}
curl
curl -X POST $BASE_URL/query/radius \
  -H "Content-Type: application/json" \
  -d '{
    "center_lat_nano": 37774900000,
    "center_lon_nano": -122419400000,
    "radius_m": 1000,
    "limit": 100
  }'

Congratulations! You’ve completed the core ArcherDB workflow: insert locations and find nearby entities.

Additional Operations

Polygon Queries (Geofencing)

Find entities within a geographic boundary:

Python
# Define a polygon (counter-clockwise winding)
polygon = [
    (37.78, -122.42),  # NW corner
    (37.78, -122.40),  # NE corner
    (37.76, -122.40),  # SE corner
    (37.76, -122.42),  # SW corner
]

results = client.query_polygon(vertices=polygon, limit=1000)
print(f"Found {len(results.events)} entities in polygon")
Node.js
const polygon = [
  { lat: 37.78, lon: -122.42 },
  { lat: 37.78, lon: -122.40 },
  { lat: 37.76, lon: -122.40 },
  { lat: 37.76, lon: -122.42 },
]

const results = await client.queryPolygon({ vertices: polygon, limit: 1000 })
console.log(`Found ${results.events.length} entities in polygon`)
Go
vertices := [][]float64{
    {37.78, -122.42},
    {37.78, -122.40},
    {37.76, -122.40},
    {37.76, -122.42},
}

filter, _ := types.NewPolygonQuery(vertices, 1000)
results, _ := client.QueryPolygon(filter)
fmt.Printf("Found %d entities in polygon\n", len(results.Events))
Java
QueryPolygonFilter filter = new QueryPolygonFilter.Builder()
    .addVertex(37.78, -122.42)
    .addVertex(37.78, -122.40)
    .addVertex(37.76, -122.40)
    .addVertex(37.76, -122.42)
    .setLimit(1000)
    .build();

QueryResult results = client.queryPolygon(filter);
System.out.println("Found " + results.getEvents().size() + " entities in polygon");
curl
curl -X POST $BASE_URL/query/polygon \
  -H "Content-Type: application/json" \
  -d '{
    "vertices": [
      {"lat_nano": 37780000000, "lon_nano": -122420000000},
      {"lat_nano": 37780000000, "lon_nano": -122400000000},
      {"lat_nano": 37760000000, "lon_nano": -122400000000},
      {"lat_nano": 37760000000, "lon_nano": -122420000000}
    ],
    "limit": 1000
  }'

Polygon with Holes

Exclude regions (parks, lakes, restricted zones):

Python
# Outer boundary (counter-clockwise)
boundary = [
    (37.79, -122.42),
    (37.79, -122.39),
    (37.76, -122.39),
    (37.76, -122.42),
]

# Hole to exclude (clockwise winding)
park_hole = [
    (37.78, -122.41),
    (37.775, -122.41),
    (37.775, -122.40),
    (37.78, -122.40),
]

results = client.query_polygon(
    vertices=boundary,
    holes=[park_hole],  # Up to 100 holes
    limit=1000,
)
Node.js
const boundary = [
  { lat: 37.79, lon: -122.42 },
  { lat: 37.79, lon: -122.39 },
  { lat: 37.76, lon: -122.39 },
  { lat: 37.76, lon: -122.42 },
]

const parkHole = [
  { lat: 37.78, lon: -122.41 },
  { lat: 37.775, lon: -122.41 },
  { lat: 37.775, lon: -122.40 },
  { lat: 37.78, lon: -122.40 },
]

const results = await client.queryPolygon({
  vertices: boundary,
  holes: [parkHole],
  limit: 1000,
})
Go
boundary := [][]float64{
    {37.79, -122.42},
    {37.79, -122.39},
    {37.76, -122.39},
    {37.76, -122.42},
}

parkHole := [][]float64{
    {37.78, -122.41},
    {37.775, -122.41},
    {37.775, -122.40},
    {37.78, -122.40},
}

filter, _ := types.NewPolygonQuery(boundary, 1000, parkHole)
results, _ := client.QueryPolygon(filter)
Java
QueryPolygonFilter filter = new QueryPolygonFilter.Builder()
    .addVertex(37.79, -122.42)
    .addVertex(37.79, -122.39)
    .addVertex(37.76, -122.39)
    .addVertex(37.76, -122.42)
    .startHole()
    .addHoleVertex(37.78, -122.41)
    .addHoleVertex(37.775, -122.41)
    .addHoleVertex(37.775, -122.40)
    .addHoleVertex(37.78, -122.40)
    .finishHole()
    .setLimit(1000)
    .build();
curl
curl -X POST $BASE_URL/query/polygon \
  -H "Content-Type: application/json" \
  -d '{
    "vertices": [
      {"lat_nano": 37790000000, "lon_nano": -122420000000},
      {"lat_nano": 37790000000, "lon_nano": -122390000000},
      {"lat_nano": 37760000000, "lon_nano": -122390000000},
      {"lat_nano": 37760000000, "lon_nano": -122420000000}
    ],
    "holes": [[
      {"lat_nano": 37780000000, "lon_nano": -122410000000},
      {"lat_nano": 37775000000, "lon_nano": -122410000000},
      {"lat_nano": 37775000000, "lon_nano": -122400000000},
      {"lat_nano": 37780000000, "lon_nano": -122400000000}
    ]],
    "limit": 1000
  }'

Polygon constraints:

  • Outer boundary: counter-clockwise winding
  • Holes: clockwise winding
  • Maximum 100 holes per polygon
  • Holes must be fully contained within boundary

Get Latest Position

Python
event = client.get_latest(entity_id)
if event:
    print(f"Last seen at: ({event.latitude:.4f}, {event.longitude:.4f})")

# Batch lookup
events = client.get_latest_batch([entity_id1, entity_id2, entity_id3])
Node.js
const event = await client.getLatest(entityId)
if (event) {
  console.log(`Last seen at: (${event.latitude}, ${event.longitude})`)
}

// Batch lookup
const events = await client.getLatestBatch([entityId1, entityId2, entityId3])
Go
event, err := client.GetLatest(entityID)
if err == nil && event != nil {
    fmt.Printf("Last seen at: (%.4f, %.4f)\n", event.Latitude(), event.Longitude())
}

// Batch lookup
events, _ := client.GetLatestBatch([]types.UInt128{entityID1, entityID2})
Java
Optional<GeoEvent> event = client.getLatest(entityId);
event.ifPresent(e ->
    System.out.println("Last seen at: (" + e.getLatitude() + ", " + e.getLongitude() + ")")
);

// Batch lookup
List<GeoEvent> events = client.getLatestBatch(List.of(entityId1, entityId2));
curl
curl -X POST $BASE_URL/query/latest \
  -H "Content-Type: application/json" \
  -d '{"entity_ids": ["00000000-0000-0000-0000-000000000001"]}'

Delete Entities (GDPR Compliance)

Python
result = client.delete_entities([entity_id1, entity_id2])
print(f"Deleted: {result.deleted_count}")
Node.js
const result = await client.deleteEntities([entityId1, entityId2])
console.log(`Deleted: ${result.deleted_count}`)
Go
result, _ := client.DeleteEntities([]types.UInt128{entityID1, entityID2})
fmt.Printf("Deleted: %d\n", result.DeletedCount)
Java
DeleteResult result = client.deleteEntities(List.of(entityId1, entityId2));
System.out.println("Deleted: " + result.getDeletedCount());
curl
curl -X POST $BASE_URL/delete \
  -H "Content-Type: application/json" \
  -d '{"entity_ids": ["00000000-0000-0000-0000-000000000001"]}'

Error Handling

All SDKs provide typed errors:

Python
from archerdb import (
    ArcherDBError,
    InvalidCoordinates,
    ClusterUnavailable,
    OperationTimeout,
)

try:
    client.insert_events(events)
except InvalidCoordinates as e:
    print(f"Bad coordinates: {e}")
except ClusterUnavailable:
    print("Cluster down, retry later")
except OperationTimeout:
    print("Timeout - operation may have committed")
Node.js
import {
  InvalidCoordinates,
  ClusterUnavailable,
  OperationTimeout,
} from 'archerdb-node'

try {
  await client.insertEvents(events)
} catch (error) {
  if (error instanceof InvalidCoordinates) {
    console.error('Bad coordinates:', error.message)
  } else if (error instanceof ClusterUnavailable) {
    console.error('Cluster down, retry later')
  } else if (error instanceof OperationTimeout) {
    console.error('Timeout - operation may have committed')
  }
}
Go
import "github.com/ArcherDB-io/archerdb/src/clients/go/pkg/errors"

_, err := client.InsertEvents(events)
if err != nil {
    switch {
    case errors.IsInvalidCoordinates(err):
        log.Printf("Bad coordinates: %v", err)
    case errors.IsClusterUnavailable(err):
        log.Println("Cluster down, retry later")
    case errors.IsOperationTimeout(err):
        log.Println("Timeout - operation may have committed")
    }
}
Java
import com.archerdb.geo.exceptions.*;

try {
    client.insertEvents(events);
} catch (InvalidCoordinatesException e) {
    System.err.println("Bad coordinates: " + e.getMessage());
} catch (ClusterUnavailableException e) {
    System.err.println("Cluster down, retry later");
} catch (OperationTimeoutException e) {
    System.err.println("Timeout - operation may have committed");
}
curl
# HTTP status codes:
# 200 - Success
# 400 - Invalid request (bad coordinates, malformed JSON)
# 503 - Cluster unavailable
# 504 - Request timeout

Configuration Reference

Client Options

Option Default Description
cluster_id Required Cluster identifier
addresses Required List of replica addresses
connect_timeout_ms 5000 Connection timeout
request_timeout_ms 30000 Request timeout

Retry Options

Option Default Description
enabled true Enable automatic retry
max_retries 5 Maximum retry attempts
base_backoff_ms 100 Base delay for exponential backoff
max_backoff_ms 1600 Maximum backoff delay

See SDK Retry Semantics for detailed retry behavior.

What’s Next (~1 min)

Goal Resource
Complete API documentation API Reference
Deploy to production Operations Runbook
Understand architecture Architecture
Handle failures Troubleshooting
Set up backups Backup Operations
Plan for disasters Disaster Recovery

SDK Documentation

Edit this page