SpiceDB у PHP-проєктах із високим навантаженням: нетривіальний підхід до керування доступом
Вітаю! Мене звати Костянтин і я вже понад 10 років працюю з PHP та фреймворком Laravel.
Сьогодні хочу поділитись своїм досвідом впровадження досить нетривіальної системи по менеджменту доступів до ресурсів проєкту, будь це доступ до певних компонентів системи або до інформаційних об’єктів (файлів, папок і так далі).
В першу чергу хочу зазначити, що ця стаття є не тільки оглядовою, а буде містити приклад одного критичного кейсу траблшутінгу, котрий оснований на багатьох годинах дебагу. Назва цієї системи SpiceDB і вона Open Source!
Поїхали!
Мета
Очевидна мета будь-якої системи авторизації — чітко визначити, що користувач А має доступ, скажімо, до методів M1 і M2 та файлів F1, F2. Та в цій статті я хочу показати, що це можна робити технологічно, швидко і масштабно. Мова про обсяги зв’язків, котрі можуть займати понад 1 ТБ у PostgreSQL, але при цьому відповідати за мілісекунди. Круто, правда?
Що таке SpiceDB
SpiceDB — це Open Source рушій авторизації, фактично база даних прав доступу, натхненна внутрішньою системою Google під назвою Zanzibar. Він використовує модель контролю доступу на основі відносин (Relation-Based Access Control, ReBAC), тобто права визначаються через відносини між об’єктами і суб’єктами (користувачами). Основна ідея — побудувати глобальний сервіс авторизації, здатний обробляти та зберігати мільярди зв’язків та відповідати за лічені мілісекунди.
І як додатковий плюс він будує дуже красивий граф взаємозв’язків, схожий з взаємодією нейронів в нашому мозку :)
Тож як це працює «під капотом»
SpiceDB забезпечує гранульований контроль доступу (fine-grained permissions) і може виражати різні моделі: ролі (RBAC), атрибути об’єктів (ABAC) та інші — усе через призму відносин. Наприклад, замість перевіряти «чи користувач має доступ до ресурсу?» або писати умовні перевірки в коді, у SpiceDB ми ставимо запитання: «Чи має суб’єкт певне відношення, яке дає йому право на ресурс?» Розробник визначає схему (структуру) даних дозволів, записує відносини (хто до чого має доступ) і виконує запити на перевірку дозволів через API (gRPC/HTTP) саме для авторизації. Наприклад, «чи може X зробити Y з ресурсом Z?» централізовано.
Які проблеми хочемо вирішити? Контрольованість-Консистентність-Масштабованість
Контрольованість логіки прав. Звичайний підхід — перевірки доступу розкидані по коду через Policy-класи, Gate тощо, у випадку моноліту, або дублювання цієї логіки в різних сервісах у разі більш розподіленої системи. Чи може ускладнити це підтримку? Звісно. Особливо, якщо ці сервіси розроблені на різних мовах програмування або фреймворках.
Тож в першу чергу ми маємо створити централізовану авторизацію: уся інформація про дозволи зберігається і обробляється у одному місці. Це гарантує єдине джерело істини для політик доступу.
Консистентність та цілісність політик. SpiceDB забезпечує сильні гарантії консистентності даних про права. Завдяки спеціальним токенам версій кожна перевірка може бути прив’язана до конкретного знімка бази дозволів, уникаючи проблеми «хтось щойно забрав право, а інший сервіс ще цього не бачив».
Масштабованість перевірок прав. При великому RPS (нехай від 1000 запитів на секунду) традиційні підходи (напряму через БД або кешування на кожному сервісі) можуть стати «вузьким місцем», адже окрім визначення прав доступу необхідно займатись й іншою бізнес-логікою в системі, і ми не маємо права перешкоджати цьому. SpiceDB спроєктовано для високої пропускної здатності: його розподілена графова машина здатна обробляти тисячі і навіть мільйони запитів на секунду із затримкою лише кілька мілісекунд. Наступна стаття буде про перфоменс-тести SpiceDB. Тому залишу всі цифри та порівняння на неї :)
Розглянемо декілька прикладів
Візьмемо за приклад управління доступами, якби ми хотіли реалізувати свервіс, схожий на Jira:
Сутність (Object) |
Відносини (Relations (→ Subject)) |
Дозволи (Permissions) |
user | - | - |
group |
member → user | - |
project |
admin, developer, viewer → ( user | group#member ) |
browse, manage |
issue |
parent_project → project reporter, assignee, commenter → user |
view, edit, delete |
Object — тип ресурсу, до якого застосовуються права.
Relations — іменовані зв’язки, що поєднують ресурс із суб’єктом (користувачем або іншою сутністю).
Permissions — похідні дозволи, які ми визначили в схемі SpiceDB.
Приклад схеми:
// Базові сутності definition user {} definition group { relation member: user // Користувач є членом групи } // Проєкт як контекст Jira definition project { relation admin: user | group#member relation developer: user | group#member relation viewer: user | group#member permission browse = viewer + developer + admin permission manage = admin } // Задача (issue) definition issue { relation parent_project: project // приналежність до проєкту relation reporter: user relation assignee: user relation commenter: user // Перегляд дозволений усім, хто має право browse на проєкт, // + індивідуальні учасники задачі permission view = parent_project->browse + reporter + assignee + commenter // Редагування: розробники або адміні проєкту, репортер, асайн permission edit = parent_project->developer + parent_project->admin + reporter + assignee // Видаляти можуть лише адміні проєкту permission delete = parent_project->admin }
Приклади кортежів (даних про відносини)
# --- групи --- group:backend#member@user:bogdan # Богдан у «Backend Devs» group:qa#member@user:oksana # Оксана у «QA» # --- ролі в проєкті PROJ --- project:PROJ#admin@user:alina # Аліна — адміністратор project:PROJ#developer@group:backend#member # уся група backend — деви project:PROJ#viewer@group:qa#member # QA має роль viewer # --- задача PROJ-1 --- issue:PROJ-1#parent_project@project:PROJ # належить проєкту PROJ issue:PROJ-1#reporter@user:dmytro # Дмитро — автор issue:PROJ-1#assignee@user:bogdan # Богдан — виконавець
Кожен рядок — це tuple, який зберігається в SpiceDB; вони можуть надходити з PHP-події (створення задачі, зміна ролі тощо).
Запити перевірки (CheckPermission):
# --- групи --- group:backend#member@user:bogdan # Богдан у «Backend Devs» group:qa#member@user:oksana # Оксана у «QA» # --- ролі в проєкті PROJ --- project:PROJ#admin@user:alina # Аліна — адміністратор project:PROJ#developer@group:backend#member # уся група backend — деви project:PROJ#viewer@group:qa#member # QA має роль viewer # --- задача PROJ-1 --- issue:PROJ-1#parent_project@project:PROJ # належить проєкту PROJ issue:PROJ-1#reporter@user:dmytro # Дмитро — автор issue:PROJ-1#assignee@user:bogdan # Богдан — виконавець POST /v1/permissions/check Host: spicedb.local Content-Type: application/json { "resource": { "objectType": "issue", "objectId": "PROJ-1" }, "permission": "edit", "subject": { "object": { "objectType": "user", "objectId": "bogdan" } }, "consistency": { "minimizeLatency": true } }
Що таке object, subject та relation у SpiceDB-кортежі? У SpiceDB кожен запис-кортеж (relationship tuple) має вигляд:
resource_type:resource_id#relation@subject_type:subject_id
Зв’язує два об’єкти (resource ↔ subject) через relation.
document:readme#editor@user:emilia readme - object editor - relation user:emilia - subject
Саме по такому ланцюжку об’єктів та відносин SpiceDB «гуляє» графом, щоб відповісти: *«Чи дозволено суб’єкту X дію Y над ресурсом Z?»
Приклад траблшутінг
Виглядає ж не складно? Де ж тут траблшут? На перший погляд усе зрозуміло, але розгляньмо реальну ситуацію:
- Сценарій: маємо високонавантажений, утім швидкий сервіс на Laravel.
- У межах одного PHP-запиту: створюємо новий взаємозв’язок у SpiceDB; трохи нижче одразу перевіряємо право доступу в режимі minimize_latency.
- У межах одного PHP-запиту: створюємо новий взаємозв’язок у SpiceDB; трохи нижче одразу перевіряємо право доступу в режимі minimize_latency.
- Проблема: отримуємо FALSE: зв’язок «не знайдено», доступ «не дозволено».
- Причина проста: кеш вузла ще не встиг оновитися — ми звернулися до старої ревізії.
- На низькому навантаженні це майже непомітно, але щойно SpiceDB починає споживати > 50% CPU, проблема стає критичною.
- Можлива помилка: першою реакцією може бути перехід одразу на fully_consistent, але такий режим обходить кеш і лише збільшує навантаження, погіршуючи ситуацію.
- Правильний підхід — каскад «fallback»-ів.
Крок |
Режим |
Коментар |
1 |
minimize_latency |
найшвидший варіант |
2 |
at_least_as_fresh (передаємо WRITE-токен) |
перевірка на тому ж або свіжішому знімку |
3 |
at_exact_snapshot |
звертаємося до конкретної ревізії |
4 |
fully_consistent |
крайній випадок, коли потрібна абсолютна актуальність |
Такий каскад мінімізує кількість «дорогих» запитів і водночас гарантує коректний результат. Каскадний підхід дозволяє зберегти баланс між продуктивністю та консистентністю: спершу використовуємо найшвидші режими, а до найважчого (fully_consistent) вдаємося лише за необхідності.
Висновок
А ви вже чули, що 21 червня DOU Mobile Day?
SpiceDB показує, що авторизацію можна зробити одночасно централізованою, гнучкою та швидкою. Переносячи всю логіку доступів у єдину схему, ви позбавляєтеся дублювання політик, отримуєте гарантовану консистентність через ZedToken і зберігаєте мілісекундну латентність навіть під мільйонами перевірок за секунду.
Коли класичні RBAC-плагіни вичерпують себе й починають гальмувати розвиток проєкту, SpiceDB дає запас міцності «на виріст», не вимагаючи переписувати бізнес-код.
І як «вишенка на торті» — підтримка від ком’юніті в Discord-каналі!
Дякую за увагу!
6 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів