Skip to content

Your graph in S3

The headline use case. Your graph database lives in your S3 bucket.

  1. Install the client

    Terminal window
    pip install namidb
  2. Export AWS credentials (or rely on an EC2 / EKS / Lambda IAM role)

    Terminal window
    export AWS_ACCESS_KEY_ID=AKIA...
    export AWS_SECRET_ACCESS_KEY=...
    export AWS_DEFAULT_REGION=us-east-1

    The only IAM permissions NamiDB needs on the bucket are s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket. That’s it. No DynamoDB lock table, no separate metadata service.

  3. Open (or bootstrap) a namespace

    import namidb as tg
    client = tg.Client("s3://my-bucket/data?ns=prod&region=us-east-1")
  4. Write Cypher

    client.cypher("CREATE (a:Person {name: 'Alice', age: 30})")
    client.cypher("CREATE (b:Person {name: 'Bob', age: 25})")
    client.cypher(
    "MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) "
    "CREATE (a)-[:KNOWS {since: 2020}]->(b)"
    )
    result = client.cypher(
    "MATCH (p:Person) WHERE p.age >= $min RETURN p.name AS name, p.age AS age",
    params={"min": 18},
    )
    print(result.to_pandas())
  5. Restart the process. Open a notebook on another machine with the same URI.

    The graph is still there. The bucket is the database.

Why this works

  • Conditional writes (If-Match / If-None-Match) on S3 replace external consensus. The manifest object is mutated by compare-and-swap, so two writers can race and only one wins — the loser retries against the new version.
  • Single-writer-per-namespace with epoch fencing. Multiple readers scale freely; only one mutator at a time per namespace.
  • Durability is whatever S3 gives you: 99.999999999% (11 nines) of object durability, multi-AZ replication.
  • Cost scales to zero when no client opens the namespace — no compute is running, no DynamoDB table is provisioned.

Use Cloudflare R2 for zero egress

R2 charges no egress and has full S3-compatible conditional writes. Same scheme, with the R2 endpoint and region=auto:

import namidb as tg
client = tg.Client(
"s3://my-bucket?ns=prod"
"&endpoint=https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
"&region=auto"
)

If you’re running NamiDB outside AWS — on Cloudflare Workers, Fly.io, your VPS, your laptop — R2 is almost always the right call.

What about backups?

Terminal window
aws s3 sync s3://my-bucket/data/ ./backup-2026-05-19/

That’s it. NamiDB never writes to anywhere else.

Multi-tenancy

Each namespace is a folder under your bucket prefix:

s3://my-bucket/data/
├── tenant-acme/
│ ├── manifest.json
│ ├── wal/
│ └── sst/
├── tenant-globex/
│ ├── manifest.json
│ ├── wal/
│ └── sst/
└── tenant-initech/
├── ...

Each ?ns=… opens an isolated namespace. Operationally: tenants are folders.

Next