Підхід игродела до створення сучасних веб додатків

Нудне вступ
Не так давно, мені довелося брати участь у розробці якогось програмно-апаратного комплексу для однієї американської компанії. Розробляв я бекенд, трохи фронтенд, сращивал пристрою з хмарою (IoT то пак). Стек технологій був позначений чітко. Ні в право, ні в ліво — enterprise, одним словом. В певний момент мене перекинули на допомогу на фронтенд POS (Point of Sale) веб-додатки.

Проблема. Стає цікавіше
Все б нічого, але веб додаток розроблялося для роботи в 6 тис. офісах по всій території Америки (для початку). Де, як виявилося, з інтернетом можуть бути проблеми. Так-так, тієї самої, просунутої Америці! Проблеми з покриттям не тільки проводового інтернету, але і мобільним зв'язком! Тобто поганий інтернет канал (часто, мобільний) — цілком звичайна історія для невеликих американських міст.

А це ж POS… Тут, розумієш, клієнти стоять, треба інвойс швидко роздрукувати… Гальм бути не повинно! І livesearch… Були обговорення, прикидки, у підсумку — не стали вантажити бекенд запитами (трафік, знову ж таки). Зійшлися на тому, що веб додаток має по-максимуму довантажувати дані і робити, той же пошук, локально. Мова йде, звичайно, про даних, розмір яких дозволяє це зробити.

Даних фронтенд тягнув багато, з різних сервісів. Як наслідок — великий трафік і довге завантаження сторінок. Загалом біда.
Частина проблем вирішується бекендом (стиснення, гео-кластеринг і тп), але це — окрема історія, зараз тільки про фронтенде.

Кеш
Перше, що було зроблено (і це логічно) — кешування та зберігання отриманих даних локально. По можливості — довго, наскільки дозволяє сек'юріті. Для будь-яких даних краще використовувати localStorage, для інших — sessionStorage, а що не поміщається — можна просто зберігати в пам'яті. Ми використовували ангуляр, тому нам цілком підійшов для цих цілей angular-cache.

Почасти, це вирішило проблему — при першому зверненні до якій-небудь сторінці, завантажувались необхідні їй ресурси. Далі, ресурси вже бралися з кешу.



Зробили, звичайно, инвалидацию кеша і тп. Трафік скоротився, але відгук при первинному зверненні до сторінок залишався неприпустимо великим.

Фонова завантаження ресурсів
Наступним логічним кроком стала фонова завантаження ресурсів. Поки що ми перебуваємо на головній сторінці, дивимося повідомлення, алерти і тп — відбувається фонове завантаження ресурсів для прихованих розділів. Ідея в тому, що коли користувач перемикається на потрібний розділ, дані (або хоча б їх частину) будуть вже в кеші, завантаження не потрібно, відгук сторінки зменшиться. Перший варіант самий простий:

$q.all([
Service1.preLoad(),
Service2.preLoad(),
...
ServiceN.preLoad()
]);

Де Service.preLoad() — функція, що повертає promise ресурсу сторінки.

Але є проблема — в цьому випадку всі промисы виконуються одночасно, тобто одночасно тягнуться всі 100500 ресурсів. А канал-то — не гумовий! Плюс у нас мега-запит до стороннього сервісу, який довго і повільно хитав багато даних. У підсумку всі паралельні запити виконувалися по часу майже стільки ж, скільки і цей мега-запит.

Окей, завантажимо по-порядку:

Service1.preLoad()
.then().Service2.preLoad()
...
.then().ServiceN.preLoad();

Воно, по воді, як стало краще, але не дуже — якщо користувач відразу йшов до «неправильний» розділ — будемо чекати прогрузки черзі, поки не доберемося до ресурсів цього розділу. Загалом, тут нескінченна евристика. Що-то краще пачкою завантажувати, щось окремо в паралелі, і тд і тп… Хотілося, однак, якогось більш системного підходу.

Чергу
І знову логічний крок — покласти завантаження всіх ресурсів в динамічну чергу. Пріоритетну. Якщо заходимо в розділ, а його дані (або їх частина) ще в черзі — ми підвищуємо їх пріоритет і вони завантажуються в першу чергу. У підсумку відгук <= ніж було. Дрібниця, а приємно.



Так то воно так, тільки є ще одне, не гумове місце — розмір кеша.

Система ресурсів
Чим глибше занурювався в цю проблему, тим сильніше виникало дежавю — я це вже проходив! Обмежені ресурси пам'яті, фонове завантаження, вивантаження, пріоритети… це ж… це ж — типова система ресурсів ігрового движка! Їдеш на машинці — локації підвантажуються, що далеко позаду — вивантажуються. Ще термін спеціальний був для ігрових движків — підтримка стримминга… 5 Років життя я провів у геймдеве, це було круто…

Так ось. Виходить, веб додаток, по суті — аналог ресурсномісткою гри. Тільки тут у нас не локації — а сторінки/розділи. Сторінки тягнуть ресурси і складають в кеш, а він — обмежений в розмірі, треба щось робити. Тобто аналогія доречна.



Проблема веб додатки, що і в геймдеве — передбачити де буде користувач, щоб завантажити ресурси заздалегідь. Рішення №1 — дизайн, звичайно. Направити користувача з прогнозованого (а інколи, і єдиним) маршрутом. Що не вдається вирішити дизайном — статистика + евристика.

Вивантаження — те ж саме. Тільки вже треба зрозуміти, що вивантажити в першу чергу. Ставимо пріоритети самим ресурсів, вивантажуємо ресурси з найбільш низьким пріоритетом. Або грохаем цілими пачками.

Варіантів реалізації — маса. Самий передбачуваний — предпроцессинг, ручний або автоматичний. Ми повинні вказати, які ресурси в локації/розділі нам будуть потрібні, а якимось знизити пріоритет.

LOD
І тут Остапа понесло… В сенсі, в голову полізли геймдев-трюки, застосовні в веб-розробці. Один з них — LOD (Level of Detail), рівень деталізації. У геймдеве він застосовується для текстур і моделей. Можна відразу прогрузить світ з мінімальним рівнем деталізації, а стриммить вже деталізовані моделі текстури. І гравець завжди щось бачить, і навіть, може грати.

Тобто потрібна система LOD для завантажуваних даних! Для веб підходить самий примітивний варіант — два рівня деталізації. Спочатку вантажимо початкові дані, які бачить користувач (перші сторінки таблиць, наприклад).



Даних виходить мало, вантажаться швидко. А бекграундом… бекграундом вантажаться вже LOD'и «важче».

Компресія
Впихнути невпихуєме — майже стандартна задача игродела. Ну так давайте раздвинем кордону localStorage! Беремо якийсь LZ-компресор і вперед! Так, але localStorage може зберігати тільки рядки… Ну, тоді згодиться, наприклад, lz-string.js. Компресія вже не та, але навіть -20%, коли в розпорядженні всього 5Мб — це зовсім не погано! І як бонус — секьюрные справи, в localStorage буде не відкритий текст, а китайські знаки.

А далі, що?
Далі… далі думка лине до незвіданих глибин. У пам'яті спливає VFS (Virtual File System) — прошарок між ресурсної системою ігри і файловою системою операційної системи. Зазвичай, все крутиться навколо data-файлу, до якого можна звернутися як до файлової системи. Прочитати файл, записати… А що якщо зробити VRC (Virtual REST Call)!? Адже тоді можна підтримати роботу веб додатка взагалі при відсутності інтернет з'єднання! В якоюсь мірою, звичайно, але все ж.

Контролери спілкуються з менеджером ресурсів. Він що може віддати — віддає відразу, всі інші запити віддаються VRC. А він, у свою чергу, вже самостійно синхронізує свій стан з бекендом і, по мірі завантаження, інформує про це.



Коли говорять про офлайнову роботу веб-додатки, обов'язково прослизає Meteor. Круто, звичайно, але ми перебували в жорстких рамках стека розробки. Пропонований варіант, можна реалізувати практично будь-фреймворку. З застереженнями, звичайно, але можна.

Але стаття конкретно не про це. А про те, як часом несподівано, спливає досвід давно минулих справ…

Приємного кодування, друзі!
Джерело: Хабрахабр

0 коментарів

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