Under construction 🚧
Built for performance. Designed for graphs.
Otter is a lightweight, purpose-driven proxy and query transpiler for Dgraph.
It intelligently balances traffic between Dgraph clusters and adds support for advanced query workflows — including Cypher-to-DQL translation (in progress).
Otter aims to serve as the foundation for future support of multiple graph languages, offering modular extensions, semantic enrichment, and introspection tools.
Read Why this software was created.
Current design.
- Round-robin and purpose-based balancing
- HTTP proxy for Dgraph
/queryand/mutate - WebSocket server with support for
query,mutation, andupsert - Simple token-based authentication
- Configurable via environment variables or YAML
- Otter now supports GraphQL queries via Ratel. Just enable the experimental feature
ratel-graphql: true
Requirements
-
Clone the repository
-
Docker
-
Docker Compose
-
(optional) make installed
make rundManual Docker Compose If you don't have make:
cd examples/cluster
docker compose up --buildBy default, Otter will load config from:
CONFIG_FILE=/app/manifest/config_docker.yamlIf you want to change the config:
manifest/config_docker.yaml
Or override with environment variables (see internal/config/config.go for supported vars)
{
"type": "upsert",
"query": "query { u as var(func: eq(email, \"[email protected]\")) }",
"mutation": "uid(u) <name> \"Test\" .",
"cond": "@if(eq(len(u), 1))",
"commitNow": true
}git clone https://github.com/OpenDgraph/Otter.git
cd Otterexport CONFIG_FILE=./manifest/config.yaml
go run cmd/proxy/main.goSet your balancer strategy inside config.yaml:
balancer_type: purposeful # or round-robin| Endpoint | Method | Description |
|---|---|---|
/query |
POST | Executes a DQL query |
/mutate |
POST | Executes a mutation |
Supported Content-Types:
application/jsonapplication/dql
Example request:
curl -X POST http://localhost:8080/query \
-H "Content-Type: application/json" \
-d '{"query": "{ data(func: has(email)) { uid name email } }"}'URL: ws://localhost:8089/ws
auth-> authenticateping-> keep connection alivequery/mutation/upsert→ require authentication
{
"type": "query",
"query": "{ data(func: has(email)) { uid name email } }",
"token": "banana",
"verbose": true
}Available types:
round-robin(default)defined(per-purpose: query/mutation/upsert)
To use defined, provide a YAML like this:
balancer_type: defined
groups:
query:
- localhost:9080
mutation:
- localhost:9081
upsert:
- localhost:9082- Automatic health checks
- Support for multiple Balancing strategies
- Graph model abstraction
- Become a framework
More purposeful Balancing strategies:
-
round-robinbasic round-robin -
round-robin-purposefulwith purpose -
round-robin-healthysupport -
round-robin-on-RWseparate readonly and write only -
round-robin-avoid-leadersavoid leaders -
round-robin-leaders-onlyleaders only -
round-robin-state-basedthis will check the state of the Alpha and check memory usage and coroutine count
