Introdução ao DGraph
O que é?
Dgraph é um banco de dados distribuído com estrutura nativa de grafo e suporte à linguagem GraphQL.
Sua arquitetura em cluster requer ao menos uma instância do tipo Zero e uma do tipo Alpha. Um cluster Dgraph possui os componentes:
- Zero: controla e armazena dados sobre o cluster. Responsável também por mover dados entre réplicas Alpha.
- Alpha: Servidor que recebe requisições dos clients, executa as queries e mutations e armazena os dados.
- Ratel: Interface web dos Alpha que permite executar queries e mutations usando a linguagem DQL.
Como funciona?
É obrigatório ter ao menos um Zero e um Alpha para o cluster funcionar. Ratel é opcional.
O Alpha possui uma porta HTTP para receber requisições GraphQL e uma porta gRPC para requisições dos clients.
Requisições via gRPC utilizam a linguagem DQL (Dgraph Query Language), uma linguagem própria do Dgraph inspirada no GraphQL.
O Alpha trabalha no esquema de Leader/Follower. Quando existe mais de um Alpha, um se torna leader e os outros followers. As requisições podem ser feitas para qualquer Alpha pois os dados são replicados entre eles.
É possível configurar um load balancer utilizando o Nginx para evitar sobrecarregar um determinado Alpha.
As portas utilizadas são:
- Zero:
5080
para comunicação interna do cluster e6080
para operações administrativas. - Alpha:
7080
para comunicação interna do cluster,9080
para comunicação gRPC com os clients e8080
para acesso HTTP. - Ratel:
8000
como servidor web do front-end.
Como rodar via Docker?
Standalone
Cria um container rodando um Zero, um Alpha e o Ratel. Em Julho/2021 o Ratel deixou de ser embutido na imagem standalone. Você pode acessar via https://play.dgraph.io/?latest.
Não é recomendado para produção.
docker run -d --name dgraph -p 6080:6080 -p 8080:8080 -p 9080:9080 -v $HOME/dgraph/standalone:/dgraph dgraph/standalone
Cluster
Cria um cluster mínimo contendo um Zero, três Alphas e o Ratel, todos rodando como container Docker.
Todos os containers estão rodando na mesma máquina, nesse exemplo. É recomendado que cada um rode em uma máquina separada em produção.
# Cria uma rede virtual para os containers
docker network create dgraph_default
# Executa 1 Zero
docker run -d --name zero -p 5080:5080 -p 6080:6080 --network dgraph_default --hostname zero -v $HOME/dgraph/zero:/dgraph dgraph/dgraph dgraph zero --my=zero:5080 --telemetry sentry=false --replicas 3
# Cria 3 containers do Alpha
for i in `seq 1 3`; do
docker run -d --name alpha${i} \
-p 908${i}:9080 \
-p 708${i}:7080 \
-p 808${i}:8080 \
--network dgraph_default \
--hostname alpha${i} \
-v $HOME/dgraph/alpha${i}:/dgraph \
dgraph/dgraph dgraph alpha --zero zero:5080 --my=alpha${i}:7080 --logtostderr --cache size-mb=2048 --telemetry sentry=false --telemetry reports=false --security whitelist=0.0.0.0/0 --limit query-timeout=500ms
done
# Ratel
docker run -d --name ratel -p 8000:8000 --hostname ratel dgraph/ratel dgraph-ratel -addr localhost:8081 -listen-addr 0.0.0.0
Segurança
O Dgraph open source não possui um mecanismo de autenticação baseado em usuário e senha, como na maioria dos bancos de dados. É possível passar o parâmetro --security token=senha
ao criar o Alpha, para restringir as operações administrativas, mas é ineficiente contra manipulação de dados. Nesse caso, as requisições gRPC precisam conter o header auth-token
e as requisições HTTP o header X-Dgraph-AuthToken
.
Diretiva @auth
Dgraph permite utilizar ACLs e a diretiva @auth
em consultas GraphQL. Saiba mais em:
- https://dgraph.io/docs/graphql/authorization/authorization-overview
- https://dgraph.io/blog/post/putting-it-all-together-part1
Checklist AWS:
- Bloquear via Security Group o acesso público às portas
5080
,6080
,7080
,8000
,8080
e9080
. - Liberar via Security Group as portas apenas para o Security Group da aplicação.
- Configurar um Nginx/Caddy com Auth Basic e SSL para fazer proxy com o Ratel e os Alphas.
- Configurar SSL para comunicação entre o client e o Alpha.
Monitoramento
É importante manter o monitoramento do banco para verificar qualquer problema. Dgraph possui uma API que retorna métricas no formato do Prometheus. Basta configurar o Prometheus para obter essas métricas e um dashboard no Grafana.
O exemplo abaixo executa o Prometheus, na mesma máquina que o cluster, para obter métricas do Zero e do Alpha:
cat > prometheus.yml <<EOF
scrape_configs:
- job_name: "dgraph"
metrics_path: "/debug/prometheus_metrics"
scrape_interval: "2s"
static_configs:
- targets:
- zero:6080 # For Dgraph zero, 6080 is the http endpoint exposing metrics.
- alpha1:8080 # For Dgraph alpha, 8080 is the http endpoint exposing metrics.
EOF
docker run -d --name prometheus --network dgraph_default --hostname prometheus -p 9999:9090 -v $PWD/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
Existe um template de dashboard para o Grafana. Basta executar o container do Grafana, criar um datasource apontando para o IP e porta do Prometheus e importar o template.
O download do template pode ser feito em https://raw.githubusercontent.com/dgraph-io/benchmarks/master/scripts/grafana_dashboard.json.
docker run -d --name grafana --network dgraph_default --hostname grafana -p 3000:3000 grafana/grafana
Tanto o Zero como Alpha expõem APIs para informações sobre o cluster:
curl "localhost:6080/state" # Zero
curl "localhost:8081/health?all" # Alpha
Também é possível obter algumas informações via GraphQL no endpoint /admin
:
# POST http://localhost:8081/admin
query {
health {
instance
address
version
status
lastEcho
group
uptime
ongoing
indexing
}
}
Gerenciamento do Cluster
Os comandos mais básicos para gerenciar um cluster são:
# Para desligar um Alpha
curl "localhost:8082/shutdown"
# Remove um Alpha do cluster
curl "localhost:6080/removeNode?id=2&group=1"
# Remove todos os dados e schemas
curl localhost:8080/alter -d '{ "drop_all": true }'
DQL e Schemas
Dgraph suporta criação de schemas e manipulação de dados usando GraphQL ou DQL. Vou focar na segunda, que é utilizada quando se usa o client oficial conectando via gRPC.
Sugiro utilizar GraphQL apenas para projetos que não necessitam de um back-end, onde as requisições partem direto do front-end para o banco de dados. É uma boa solução para criar protótipos interativos ou validar idéias.
Schema
Operações relacionadas à schemas, queries e mutations, são executadas na instância Alpha.
Uitlizamos o endpoint /alter
para manipular schemas na linguagem DQL.
Esse é um schema básico de ecommerce para demonstrar o uso de grafos:
cat > schema.dql <<EOF
name: string @index(term) .
amount: int .
price: int .
age: int .
country: uid @reverse .
products: [uid] @reverse .
customer: uid @reverse .
type Country {
name
}
type User {
name
age
country
}
type Product {
name
price
}
type Order {
products: Product
customer: User
amount
}
EOF
curl -XPOST "localhost:8080/alter" --data-binary '@schema.dql'
Mutations
Vou utilizar um dataset simulando um e-commerce bem simples.
Dgraph cria automaticamente os valores UID que identificam um nó no grafo. Podemos criar nós contendo um alias para o UID. O formato é _:nome_do_alias"
.
Reparem que Dgraph permite executar múltiplas queries e mutations na mesma requisição. Utilize a interface web Ratel ou curl
via terminal:
curl -H "Content-Type: application/json" -XPOST "localhost:8080/mutate?commitNow=true" -d '{
"set": [
{
"dgraph.type": "Country",
"uid": "_:brasil",
"name": "Brasil"
},
{
"dgraph.type": "Country",
"uid": "_:argentina",
"name": "Argentina"
},
{
"dgraph.type": "Country",
"uid": "_:uruguai",
"name": "Uruguai"
},
{
"dgraph.type": "User",
"uid": "_:gustavo",
"name": "Gustavo Henrique",
"age": 37,
"country": ":_brasil"
},
{
"dgraph.type": "User",
"uid": "_:carol",
"name": "Carol",
"age": 33,
"country": ":_argentina"
},
{
"dgraph.type": "Product",
"uid": "_:playstation5",
"name": "Playstation 5",
"price": 49900
},
{
"dgraph.type": "Product",
"uid": "_:system76",
"name": "Laptop System76 Lemur Pro 14\"",
"price": 129900
},
{
"dgraph.type": "Order",
"customer": { "uid": "_:gustavo" },
"products": [
{ "uid": "_:playstation5" },
{ "uid": "_:system76" }
],
"amount": 179800
},
{
"dgraph.type": "Order",
"customer": { "uid": "_:carol" },
"products": [ { "uid": "_:playstation5" } ],
"amount": 49900
}
]
}'
Queries
Exemplos de queries utilizando DQL.
Pedidos do usuário com UID 0x77
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
orders(func: uid(0x77)) {
uid
name
country {
name
}
order: ~customer {
uid
amount
products {
name
price
}
}
}
}'
Total de vendas do produto com UID 0x72
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
sales(func: uid(0x72)) {
uid
name
total_orders: count(~products)
}
}'
Países onde foram vendidos o produto com UID 0x72
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
sales(func: uid(0x72)) {
uid
name
orders: ~products {
customer {
country {
name
}
}
}
}
}'
Total de vendas do país com UID 0x74
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
var(func: uid(0x74)) {
~country {
orders as count(~customer @filter(gt(amount, 0)))
}
}
sales() {
total: sum(val(orders))
}
}'
Pedidos de clientes do Brasil
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
orders_from_brazil(func: type(Order)) @cascade {
uid
amount
products { name }
customer {
name
country @filter(eq(name, "Brasil")) {
name
}
}
}
}'
Outros produtos comprados por quem comprou o Playstation 5:
curl -H "Content-Type: application/dql" -XPOST "localhost:8080/query" -d $'{
PRODUCT as var(func: eq(name, "Playstation 5"))
var(func: type(Order)) @cascade {
products @filter(uid(PRODUCT))
also_bought: products @filter(NOT uid(PRODUCT)) {
PRODUCT_ID as uid
name
}
}
products(func: uid(PRODUCT_ID)) {
name
}
}'
Conclusão
Um banco de dados em grafo fornece flexibilidade para evolução de um projeto e fácil escalabilidade, sendo ideal para aplicações modernas ou sistemas distribuídos.
Dgraph é escrito em Go e seus clients utilizam conexão gRPC. Isso permite que o banco de dados rode com boa performance em máquinas com uma configuração modesta.
Agora deixo com vocês a tarefa de fazer um teste de carga e executar queries mais complexas para avaliar a performance e velocidade.