Використання Github в якості сховища даних

Зображення коміта
Вибираючи сервіс для зберігання моїх даних, важливою складовою є те, як довго такий сервіс буде жити. Від нього потрібно, щоб я зміг хоча б прочитати збережені дані навіть якщо ентузіазм авторів проекту закінчиться разом з грошима для оплати хостингу і бази даних. З таким підходом для свого проекту я шукав сервіси баз даних, які могли б зберігати дані користувача безкоштовно. Багатообіцяючим проектом був Parse.com, про який я вже писав раніше в статті «Сайт без бек-ендом». Але в січні 2016 ми дізналися, що Parse.com проживе тільки один рік і буде закритий. У зв'язку з цим я вирішив перевести зберігання даних користувачів в git-репозиторій, який опубліковано на Github.
Схема роботи сервісу
Користувач <=> GitHub Pages <=> Окремий API на платному VDS <=> git-репозиторій на SSD на тому ж VDS <=> репозиторій на GitHub
Істотним недоліком по вищеописаному критерієм тут є API на платному VDS, який одного разу може стати недоступним назавжди. Але, завдяки всьому ланцюжку, отримати свої дані як у людському форматі, так і в машиночитаемом можна буде на Github. Так як користувач на Github Pages спілкується з API через javascript, то головна сторінка проекту буде як і раніше доступна. У разі, якщо з ланки потрібно буде виключити Github з якої-небудь причини, то можна перенести всю роботу на інший хостинг репозиторіїв, наприклад, Bitbucket.
Чому б не використовувати систему pull-request'ів замість API? Я знайшов 3 причини:
  1. Користувачі повинні бути знайомі з git і з системою PR на Github.
  2. Потрібно розробляти систему авторизації з перевіркою, що користувач PR торкнувся тільки свої файли.
  3. Все одно Потрібен окремий сервер, який буде перезбирати markdown файли. Не відмовлятися ж від них.
В результаті, я прийшов до висновку, що рішення з кодом на своєму сервері не уникнути. Спілкування такої серверної частини з Github відбувається тільки через git. Використання ssh-ключа для виштовхування на Github робить процес простим.
Правомірність
Подумавши про структурі таких даних, я вивчив питання легітимності використання Github з такою метою. Розглянемо докладніше пункти Terms of Service, які безпосередньо стосуються питання.
A. 2
You must be a human. Accounts registered by "bots" or other automated methods are not permitted.
Аккаунт для виштовхування репозиторію на Github потрібно створити вручну (при цьому на діючу електронну пошту).
A. 4
Your login may only be used by one person — i.e., a single login may not be shared by multiple people — except that a machine user's actions may be directed by multiple people. You may create separate logins for as many people as your plan allows.
A. 7
One person or legal entity may not maintain more than one free account, machine and one user account that is used exclusively for performing automated tasks.
Усі дії щодо фіксації (commit) і виштовхування (push) робляться від одного аккаунта. Але такі дії підходять під опис
machine user
, тому що вони автоматизовані.
G. 12
If your bandwidth usage significantly exceeds the average bandwidth usage (as determined solely by GitHub) of other GitHub customers, we reserve the right immediately to disable your account or throttle your file hosting until you can reduce your bandwidth consumption.
У проекті використовуються текстові файли, а виштовхування відбувається за розкладом в короткі проміжки часу. Практично, цей пункт не повинен бути порушений.
Інші рекомендації
Github в деяких матеріалах повідомляє додаткову інформацію.
We recommend repositories be kept under 1GB each. This limit is easy to stay within if large files are kept out of the repository. If your repository exceeds 1GB, you might receive a polite email from GitHub Support requesting that you reduce the size of the repository to bring it back down.
In addition, we place a strict limit of files exceeding 100 MB in size.
Теоретично, ця частина може стати проблемою. За 2 роки життя сервісу обліку прочитаних книг було збережено близько 8000 записів. При цьому розмір сховища становить близько 7 МБ. Самий великий файл має розмір близько 500 КБ — це службовий файл з індексом записів. Враховуючи, що в сервісі є обмеження на довжину переданих текстів, використовуючи сервіс за призначенням, ліміт буде перевищено нескоро. Надалі можна розглянути варіант шардирования.
Там же Github нам повідомляє, що вони не проектувалися (як і сам git) як засіб зберігання резервних копій або засіб зберігання великих sql-файлів. Ми не будемо використовувати Github як засіб зберігання sql-файлів, у нас інша структура даних. А так як дані передбачаються бути читабельними на самому Github не тільки машинами, але і людьми, то назвати таку структуру чистою копією теж не можна.
Структура даних
Створити функціональну, але при цьому не надлишкову структуру даних, яку можна було б зберігати в git-репозиторії, можна тільки в рамках конкретного проекту. Напевно, можна було б описати якийсь універсальний підхід до проектування цього аспекту, але я опишу кінцевий варіант, залишивши лише найнеобхідніше.
Отже, для проекту Книгопись потрібно зберігати інформацію про користувача: id, псевдонім, дата останнього оновлення і, головне, списку прочитаних ним книг. Про прочитаної книзі ми будемо зберігати: id, заголовок, автор, дата прочитання, примітки користувача, дата останнього оновлення. Задум проекту в тому, що користувач вказує книгу вільним введенням, так, як йому хочеться. Це дозволяє нам не використовувати реєстр всіх існуючих книг. Іншими словами, кожна запис про книгу має відношення тільки до користувача, який її створив.
В якості формату даних вибрано json за свою ненадмірність і хорошу сумісність з ідей git-сховища. Якщо зберігати кожне значення json на окремої рядку, це дозволить отримати наочний diff на Github.
Так як крім користувачів і їхніх книг нам поки нічого зберігати не треба, ми створюємо для кожного користувача окрему директорію з ім'ям id користувача. Всередині цієї директорії зберігаємо json-файл з базовою інформацією про користувача. Тут же розташована директорія books, і всередині неї зберігаються окремі json-файли на кожну книгу з id книги як ім'я файлу.
Розглянемо допоміжні файли. Незважаючи на те, що кожна запис про прочитаної книги має відношення до конкретного користувача, для API знадобилося швидке отримання книги за її id. Я пішов по шляху створення допоміжного файлу — індексу книг. Це csv-файл, який містить id і повний шлях до запису про книгу. Створення такого файлу можна було б уникнути, якщо робити пошук книг в контексті конкретного користувача (тоді треба було б додатковий час на пошук файлу в директорії користувача) або робити складовою id, частина якого мав би id користувача (надмірність і неатомарность id).
Наступними допоміжними файлами є latest_books.json і latest_books_with_notes.json. Вони зберігають інформацію про фіксований кількості останніх внесених книгах, а також latest_users.json з фіксованою кількістю останніх зареєстрованих користувачів. Завдяки їм на сервісі можна показати останні додані книги з примітками і останніх активних користувачів.
Так як ми використовуємо Github, то деяку інформацію ми можемо відобразити в самому репозиторії, використовуючи markdown. Для цього будемо перезбирати при кожному внесення нової інформації файли README.md і окремо latest_books_with_notes.md на основі вищеописаних json-файлів. А головне, ми можемо перезбирати самі страницы користувачів зі списком прочитаного ними.
Директорії з користувачами ми згрупували за початковим символів id для уникнення утворення занадто великої кількості об'єктів на одному рівні шляху.
Аутентифікація і авторизація
На відміну від Parse.com тут немає можливості зберігати паролі, але навіть тоді я використовував uLogin для аутентифікації, який об'єднує десятки соціальних мереж і сайтів, при цьому не вимагає реєстрації користувача. Робота з uLogin дуже проста. Він передає токен доступу клієнту після успішного входу, цей токен потрібно відправити на сервер, де зробити запит до сервера uLogin для валідації токена і отримання певної корисної інформації, наприклад, назва мережі-провайдера і id користувача в ній. З допомогою цієї інформації можна прив'язати дані користувача безпосередньо до його облікового запису в обраній їм соціальної мережі. Це означає, що в разі закінчення роботи uLogin, його можна буде замінити на аналогічний сервіс (в тому числі своїм). Тому в якості id користувача я вирішив використовувати комбінований id виду id-provider, наприклад,
83820536-yandex
. Такий підхід дозволив уникнути обмеження на зберігання де-небудь непублічних даних.
Плануючи сервіс я передбачив сценарій втрати користувачем доступу до соціальної мережі. Такий сценарій реалізувався нещодавно у зв'язку з блокуванням в РФ LinkedIn. Користувач звернувся з проханням про допомогу. У проекті з'явилася функція «скопіювати записи з іншого облікового запису». Так як всі дані є загальнодоступними, то немає ніякої шкоди від того, що хоч хтось може скопіювати собі список хоч кого. Розумно додати деякі обмеження, щоб користувач «не прострелив собі ногу». В результаті, користувач скористався функцією копіювання і отримав доступ до своїх записів, хоча і входить на сервіс тепер через VK.
Тепер до питання авторизації користувача при роботі з API. На перших етапах розробки, я створював випадковий токен доступу, до якого прив'язував користувацький id після аутентифікації (сама зв'язок зберігалася в кеші програми), а токен повертав клієнту. Передбачалося, що він повинен буде включати його в кожен запит до API. Але надалі я придивився до звичного механізму сесій і кук (cookies). У cookies виявилися непогані переваги. По-перше, можна встановити HttpOnly. Не сказати, що це перевага виключило всі XSS-атаки, але хоча б на один сценарій стало менше. По-друге, куки передаються автоматично, якщо клієнт на js в браузері, а це якраз наш випадок.
Багато якою фреймворк для серверної частини дозволяє дуже легко реалізувати механізм «запам'ятати мене» з допомогою довгоживучої куки. По ній він піднімає сесію користувача. Подальша процедура авторизації робиться в рамках фреймворка. Потрібно, звичайно, визначити сутності і механізми збереження даних у файлову систему, але це сильно залежить від структури даних. Скажу лише, що потрібно передбачити подобу транзакцій для git-фіксацій. Це дозволить об'єднати зміна декількох сутностей в один комміт.
Переваги
  • Безкоштовно
  • Історія всіх змін
  • Готова система відкоту змін
  • Можливість швидко отримати повну копію всіх даних
  • Можна отримати фрагменти даних в обхід API, використовуючи raw-дані GitHub
  • Імовірно, довговічність
Недоліки
Швидкість читання і запису. Хоча використання SSD на хостингу скрашує ситуацію. Рекомендую ssd-хостинг DigitalOcean: реф. посилання з бонусами.
Можуть виникнути складнощі з масштабуванням. Виштовхування і витягування не дуже швидкі операції, щоб синхронізувати сховище в реальному часі. Можливо, допоможе шардінг за користувачам.
Запитання пошукової оптимізації відкритий. Проект на Github Pages + Angular, пошукові системи таке не бачать. Можливо, markdown файли потраплять в індекс, або будуть проіндексовані сторінки в соціальних мережах, куди відбувається експорт записів.
Реалізація пошуку вимагає додаткових зусиль.
Підтримка локалізації для markdown-сторінок на Github відсутня. Може допомогти дублювання даних, але це не так красиво.
Немає звичних мов запиту. Отримання та запис потрібно реалізовувати самостійно на досить низькому рівні.
Висновок
Проект з використанням Github в якості сховища даних вдалося перевірити часом. На дату публікації статті він працює більше 3 місяців. Можливо, все не розвалилося, тому що не було серйозних навантажень або спритних хлопців. А можливо, ідея життєздатна, хоча б, для невеликих проектів.
активність на Github
Якщо ви ще не знаєте, де можна зберегти список прочитаних книг, але хочете скласти по ньому звіт за минулий рік, то ласкаво просимо.
Вихідні коди всіх частин проекту:
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.