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?»

Приклад траблшутінг

Виглядає ж не складно? Де ж тут траблшут? На перший погляд усе зрозуміло, але розгляньмо реальну ситуацію:

  1. Сценарій: маємо високонавантажений, утім швидкий сервіс на Laravel.
    • У межах одного PHP-запиту: створюємо новий взаємозв’язок у SpiceDB; трохи нижче одразу перевіряємо право доступу в режимі minimize_latency.
  2. Проблема: отримуємо FALSE: зв’язок «не знайдено», доступ «не дозволено».
    • Причина проста: кеш вузла ще не встиг оновитися — ми звернулися до старої ревізії.
    • На низькому навантаженні це майже непомітно, але щойно SpiceDB починає споживати > 50% CPU, проблема стає критичною.
  3. Можлива помилка: першою реакцією може бути перехід одразу на fully_consistent, але такий режим обходить кеш і лише збільшує навантаження, погіршуючи ситуацію.
  4. Правильний підхід — каскад «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-каналі!

Дякую за увагу!

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Ще кілька подібних статей, і мені прийдеться писати свою: «Що таке cache та як його готувати»...

Якщо для визначення прав доступу нам треба ходити в базу, то це вже фіаско, як на мене.

Розгляньмо сценарій, у якому в системі є функціонал по завантаженню користувацьких даних, з можливістю шерити до них доступ іншим користувачам із різними рівнями прав. Додаємо до цього створення користувацьких груп та керування пов’язаними ролями. Усі ці взаємозв’язки мають бути надійно збережені, тож база даних є невід’ємною складовою.

Все, що ви описали, робиться набагато простіше через підходи на кшталт access keys, коли для кожного елемента ви створюєте за певними правилами набір ключів доступу, потім визначаєте аналогічні ключі для користувачів, і робите всю логіку доступу максимально тупою: на будь-який запит ви мусите віддати тільки ті дані, в яких хоча б один ключ співпадає. Мінімальна кількість записів на необмежену кількість читань та дуже мало калькуляцій.

Ви праві, пласка модель справді легша в реалізації, але втрачає гнучкість, щойно з’являються вкладені ресурси, делегування прав (особливо делегування через інший ресурс, а не пряме делегування) чи нетривіальні умови доступу. Саме таких сценаріїв і стосується SpiceDB. Коли система потребує великої гнучкості та масштабованості з котрими пласка модель вже не може впоратись.

Немає втрати гнучкості від слова зовсім. Будь-яке нове правило — це лише додатковий ключ доступу. Масштабованість підходу, що я описав, не обмежена взагалі нічим.

Підписатись на коментарі