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 --versionmacOS (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 --versionmacOS (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 --versionBuild from Source
git clone https://github.com/ArcherDB-io/archerdb.git
cd archerdb
./zig/download.sh
./zig/zig build
# Binary at ./zig-out/bin/archerdbChoose 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, ultraStarting 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.archerdbYou 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.dbSDK Installation (~1 min)
Python
pip install archerdbNode.js
npm install archerdb-nodeGo
go get github.com/ArcherDB-io/archerdb/src/clients/goJava (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.archerdbS3 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 timeoutConfiguration 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 |