Dgraph — распределённая графовая база данных, которая хранит данные в виде узлов и связей и отвечает на запросы на языке DQL. Здесь не нужно писать JOIN: связи — первоклассные сущности, а обход графа выполняется одним запросом. Разберём запуск, схему, мутации, выборки, загрузку данных и эксплуатацию.
Как устроен Dgraph
Кластер состоит из двух ролей. zero хранит метаданные, распределяет предикаты по группам и управляет членством; alpha обслуживает запросы, хранит данные в Badger и реплицирует их через Raft. Предикаты шардируются по группам, поэтому один большой граф можно разнести по машинам.
Узлы адресуются 64-битными UID, а не первичными ключами. Схема описывает предикаты и индексы; типы (type Person) помогают с удалением и раскрытием полей, но не заменяют схему. Ядро — Apache 2.0, корпоративные функции — по отдельной лицензии; перед коммерческим использованием проверьте условия версии.
Быстрый запуск
services:
zero:
image: dgraph/dgraph:latest
command: dgraph zero --my=zero:5080
ports:
- "5080:5080"
- "6080:6080"
volumes:
- ./data/zero:/dgraph
alpha:
image: dgraph/dgraph:latest
command: dgraph alpha --my=alpha:7080 --zero=zero:5080 --security "whitelist=0.0.0.0/0"
ports:
- "8080:8080"
- "9080:9080"
volumes:
- ./data/alpha:/dgraph
Порты: 8080 — HTTP для запросов и мутаций, 9080 — gRPC, 5080 и 6080 — Zero. Whitelist 0.0.0.0/0 допустим только в локальной лаборатории: в продакшене ограничьте подсеть и включите TLS.
docker compose up -d
docker compose ps
curl -s http://localhost:8080/health?all
docker compose logs --tail=50 alpha
Запрос здоровья возвращает состояние Alpha и Zero. Если Alpha не поднялся, смотрите логи: обычно он не видит Zero или ему не хватает памяти.
Схема и индексы
Схема задаётся через /alter. Индекс нужен для фильтров: без него eq() и ge() не сработают. @reverse добавляет обратное ребро, @unique требует индекс и автоматически включает upsert-проверку.
curl -X POST localhost:8080/alter \
-H 'Content-Type: application/dql' --data-binary '
name: string @index(term) @lang .
age: int @index(int) .
email: string @index(exact) @unique .
friend: [uid] @reverse .
works_for: uid @reverse .
type Person {
name
age
email
friend
works_for
}'
@index(term) подходит для поиска по словам, exact — для точного совпадения, hash — для уникальных идентификаторов. Для полнотекстового поиска с языком используйте @lang, но помните, что язык влияет на токенизацию и должен быть одинаковым при записи и чтении.
Мутации
Данные можно писать в JSON или RDF. JSON удобнее для приложения: blank node _:alice превращается в UID, а вложенные объекты создают связи.
curl -X POST 'localhost:8080/mutate?commitNow=true' \
-H 'Content-Type: application/json' \
-d '{
"set": [
{"uid": "_:alice", "name": "Alice", "age": 34,
"email": "alice@example.org"},
{"uid": "_:bob", "name": "Bob", "age": 41,
"friend": [{"uid": "_:alice"}],
"works_for": {"uid": "_:acme"}},
{"uid": "_:acme", "name": "ACME"}
]
}'
Повторная отправка JSON создаст дубликаты. Для идемпотентной записи используйте upsert: найдите узел по уникальному email и обновите его.
curl -X POST localhost:8080/mutate?commitNow=true \
-H 'Content-Type: application/dql' --data-binary '
upsert {
query {
q(func: eq(email, "alice@example.org")) {
v as uid
}
}
mutation {
set {
uid(v) <age> "35"^^<xs:int> .
}
}
}'
Если узел не найден, uid(v) пуст и мутация ничего не сделает. Создание и проверку объединяйте в одном upsert-блоке с директивой @if: это защитит от дубликата при гонке клиентов.
Запросы на DQL
curl -s localhost:8080/query \
-H 'Content-Type: application/dql' --data-binary '
{
people(func: has(name), first: 10, orderasc: name)
@filter(ge(age, 30) AND lt(age, 50)) {
uid
name
age
friend {
name
}
}
}'
Корневая функция выбирает стартовый набор, фильтр сужает его, а вложенные блоки обходят связи. first и offset дают пагинацию, after — курсорную по UID. Для глубокого обхода используйте @recurse.
{
network(func: eq(name, "Alice")) @recurse(depth: 3) {
name
friend
}
}
@recurse может вернуть много данных: ограничивайте глубину и не запускайте его на плотном графе без необходимости. Для агрегатов используйте value-переменные или встроенные функции:
{
stats(func: has(age)) {
count(uid)
total: sum(age)
average: avg(age)
oldest: max(age)
}
}
DQL также поддерживает @groupby, @normalize и фасеты — пары «ключ-значение» на ребре. Фасеты не индексируются, поэтому фильтровать по ним в корне нельзя.
GraphQL-эндпоинт
Dgraph генерирует GraphQL API из SDL. Это не DQL: схема и резолверы создаются автоматически, а запросы идут на /graphql.
curl -X POST localhost:8080/admin/schema \
-H 'Content-Type: application/graphql' --data-binary '
type Person {
id: ID!
name: String! @search(by: [term])
age: Int @search
worksFor: Company @hasInverse(field: employees)
}
type Company {
id: ID!
name: String! @search(by: [term])
employees: [Person]
}'
После загрузки схемы появляется запрос queryPerson с фильтрами и пагинацией. GraphQL удобен для фронтенда, DQL — для аналитики и сложных обходов. У них разные схемы, поэтому не смешивайте языки в одном слое.
Загрузка данных и эксплуатация
Для разового импорта большого набора используйте bulk loader, для инкрементальной загрузки — live loader. Оба принимают RDF и схему.
dgraph live -f people.rdf -s schema.dql \
-a localhost:9080 -z localhost:5080
dgraph bulk -f people.rdf.gz -s schema.dql --reduce_shards=1
Bulk loader работает только на пустой базе и создаёт каталог out с данными для копирования. Live loader пишет в работающий кластер и обновляет узлы по UID.
Для резервной копии используйте экспорт через Admin API: он выгружает RDF и схему в каталог export внутри тома Alpha. Проверяйте, что копия действительно восстанавливается.
curl -s localhost:8080/admin/export
docker compose logs -f alpha
du -sh data/alpha
Alpha держит индексы и кэш, а Badger активно пишет value log. При OOM уменьшите кэш, добавьте RAM или разнесите предикаты по группам. Диск должен быть быстрым: случайные чтения чувствительны к задержкам.
Когда Dgraph уместен
- Социальный граф, рекомендации, связи между сущностями, где важны обходы на 2–4 уровня.
- Каталог с большим числом связей и частыми запросами «кто с кем связан».
- Нужен GraphQL API без ручных резолверов.
Для отчётов и небольших данных проще PostgreSQL или SQLite. Dgraph силён в распределённых графах и большой нагрузке.
Итог
Dgraph — это графовая база для связей, а не универсальная замена SQL. Запустите Zero и Alpha, опишите предикаты с индексами, загрузите данные через live loader и проверьте типовые обходы через /query. Дальше следите за памятью и диском, делайте экспорт и не открывайте HTTP-порт в интернет без TLS и ограничения по подсети.