Як ми прискорили завантаження складних даних в інтерфейсі користувача на NodeJS у 20 разів

🚩 Проблема: завантаження та відображення списку останніх авторів занадто повільне —
1–3 секунди на кожен запит.

Рішення: розумно кешувати список останніх авторів після того, як він буде отриманий.

🥳 Вплив: швидкість завантаження зменшена на 95%
з 2000 мілісекунд до 100 мілісекунд.

Як? Читайте історію.

Хочете отримати подібні результати? Lincoln спеціалізується на Full-Stack JavaScript: сервери NodeJS, фронтенд на ReactJS та база даних MongoDB.
Зв'яжіться з [email protected] для безкоштовної консультації
щодо прискорення ваших мобільних та веб-додатків.

pic

Діаграма автора: стек для доставки складного веб-додатку, прискореного кешами.

Це буде довга історія, але кожен інженер, який хоче покращити свої навички роботи з веб-сервісами, повинен її прочитати. Будуть розглянуті різні варіанти та компроміси.

ManyStories має тисячі авторів, які діляться своїми історіями. Найпопулярніший автор має понад 570 підписників. Щоб потрапити до топ-100 авторів за кількістю підписників, потрібно мати більше 90 підписників. Але досягти цих цифр не так просто, особливо якщо ніхто не бачить вас. На ManyStories ми хочемо, щоб всі наші автори були помічені, коли діляться своїми історіями.

Проблема

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

pic

Скриншот з ManyStories.com з мережею часу завантаження списку останніх авторів. 2,6 секунди

Це працює дуже добре. Читачі можуть відкривати для себе нових авторів і їхні численні історії, а автори отримують більше підписників. Всі виграють. Але… що, якщо карусель не завантажиться достатньо швидко, поки користувач прокручує сторінку?

Налаштування бази даних

Перш ніж говорити про відповідь (RESTful) API, потрібно обговорити налаштування бази даних.

Отримання списку користувачів, які нещодавно поділилися історією, здається простим завданням, але насправді воно може бути досить складним, особливо в налаштуваннях реляційної бази даних. Це може призвести до того, що час відповіді API для цього кінцевого точки становитиме від 1 до 3 секунд. Це неприпустимо.

Звичайно, розмір даних має значення, і їх вага відносна до ресурсів, виділених для сервера бази даних. Наразі ми тримаємо витрати на базу даних на мінімумі.

Ми використовуємо MongoDB як нашу (NoSQL) базу даних, але я вірю у переваги реляційних систем баз даних, тому всі наші дані не просто зберігаються в об'єкті JSON.

Кожен користувач має запис User у базі даних. Кожна історія має запис Story; історія — це по суті посилання на опублікований контент в інтернеті, де автор вирішив його опублікувати. Щоб зв'язати користувача з історією, ми маємо запис StoryUserRelation.

pic

Коли користувач публікує історію на ManyStories, він вважається автором; для цього запису ставиться прапорець isSharer: true. Якщо користувач просто взаємодіє з історією як (потенційний) читач, все одно створюється запис для цього зв'язку, щоб відслідковувати стан, наприклад, чи сподобалась історія або чи була додана в закладки, але прапорець isSharer встановлюється в значення false.

Для зв'язків, де isSharer має значення true, також є поле дати isSharerUpdatedAt, щоб ми могли відслідковувати — ви вже здогадались — коли користувач поділився історією.

pic

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

Отже, ми налаштували базу даних. Все досить зрозуміло. Тепер де ж "вузьке місце" у відповіді API? Сповільнення виникає через необхідність об'єднати різні таблиці.

🚩 Проблема: завантаження та відображення списку останніх авторів занадто повільне —
1–3 секунди на кожен запит.

Рішення: розумно кешувати список останніх авторів після того, як він буде отриманий.

🥳 Вплив: швидкість завантаження зменшена на 95%
з 2000 мілісекунд до 100 мілісекунд.

Як? Читайте історію.

Хочете отримати подібні результати? Lincoln спеціалізується на Full-Stack JavaScript: сервери NodeJS, фронтенд на ReactJS та база даних MongoDB.
Зв'яжіться з [email protected] для безкоштовної консультації
щодо прискорення ваших мобільних та веб-додатків.

pic

Діаграма автора: стек для доставки складного веб-додатку, прискореного кешами.

Це буде довга історія, але кожен інженер, який хоче покращити свої навички роботи з веб-сервісами, повинен її прочитати. Будуть розглянуті різні варіанти та компроміси.

ManyStories має тисячі авторів, які діляться своїми історіями. Найпопулярніший автор має понад 570 підписників. Щоб потрапити до топ-100 авторів за кількістю підписників, потрібно мати більше 90 підписників. Але досягти цих цифр не так просто, особливо якщо ніхто не бачить вас. На ManyStories ми хочемо, щоб всі наші автори були помічені, коли діляться своїми історіями.

Проблема

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

pic

Скриншот з ManyStories.com з мережею часу завантаження списку останніх авторів. 2,6 секунди

Це працює дуже добре. Читачі можуть відкривати для себе нових авторів і їхні численні історії, а автори отримують більше підписників. Всі виграють. Але… що, якщо карусель не завантажиться достатньо швидко, поки користувач прокручує сторінку?

Налаштування бази даних

Перш ніж говорити про відповідь (RESTful) API, потрібно обговорити налаштування бази даних.

Отримання списку користувачів, які нещодавно поділилися історією, здається простим завданням, але насправді воно може бути досить складним, особливо в налаштуваннях реляційної бази даних. Це може призвести до того, що час відповіді API для цього кінцевого точки становитиме від 1 до 3 секунд. Це неприпустимо.

Звичайно, розмір даних має значення, і їх вага відносна до ресурсів, виділених для сервера бази даних. Наразі ми тримаємо витрати на базу даних на мінімумі.

Ми використовуємо MongoDB як нашу (NoSQL) базу даних, але я вірю у переваги реляційних систем баз даних, тому всі наші дані не просто зберігаються в об'єкті JSON.

Кожен користувач має запис User у базі даних. Кожна історія має запис Story; історія — це по суті посилання на опублікований контент в інтернеті, де автор вирішив його опублікувати. Щоб зв'язати користувача з історією, ми маємо запис StoryUserRelation.

pic

Коли користувач публікує історію на ManyStories, він вважається автором; для цього запису ставиться прапорець isSharer: true. Якщо користувач просто взаємодіє з історією як (потенційний) читач, все одно створюється запис для цього зв'язку, щоб відслідковувати стан, наприклад, чи сподобалась історія або чи була додана в закладки, але прапорець isSharer встановлюється в значення false.

Для зв'язків, де isSharer має значення true, також є поле дати isSharerUpdatedAt, щоб ми могли відслідковувати — ви вже здогадались — коли користувач поділився історією.

pic

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

Отже, ми налаштували базу даних. Все досить зрозуміло. Тепер де ж "вузьке місце" у відповіді API? Сповільнення виникає через необхідність об'єднати різні таблиці.

Досвід користувача стрічки

Коли користувач відвідує ManyStories.com, йому показується наступна стрічка:

  1. Сітка рекомендованих історій — вибірка найпопулярніших старих і нових історій
  2. Сітка історій членів — історії від платних користувачів платформи
  3. Список останніх історій — історії, поділені всіма авторами
  4. Карусель останніх авторів — завантажується за 1–3 секунди 🚨
  5. Рекомендовані історії — на основі історії читання
  6. Інші останні та рекомендовані історії

Більшість відвідувачів не дійдуть до позиції 4 у стрічці, оскільки вони, ймовірно, лише пасивні користувачі платформи. Можливо, вони відкрили сайт вперше або потрапили сюди через рекомендацію автора чи читача, якого поважають. Вони навіть можуть не дістатися до позиції 3 стрічки. У таких випадках ми не хочемо завантажувати дані з нашого API, якщо користувач ніколи не прокрутить до цього місця в інтерфейсі.

pic

Ось чому наша стрічка завантажує дані тільки за потребою. Коли користувач наближається до кінця стрічки, ми запитуємо більше даних для заповнення стрічки. Тобто, якщо вони прокручують до кінця позиції 2, ми завантажуємо позицію 3. Якщо вони доходять до середини позиції 3, ми завантажуємо позицію 4 і так далі.

Це працює добре, якщо користувач витрачає час на взаємодію з кожною позицією в стрічці під час прокручування. Затримка їхнього прокручування дає нашому API-серверу час обробити кожний запит. Ідеально, кожна позиція в стрічці повинна оброблятися протягом кількох сотень мілісекунд або менше, але це не завжди так, особливо на повільних мережах.

Також, що робити, якщо користувач прокручує швидко? Він просто хоче швидко переглянути стрічку, щоб побачити, про що йдеться. Це може стати великою проблемою для нашого досвіду користувача (UX) через затримки в API.

Повільна відповідь API

Завантаження позиції 4 в стрічці, що є каруселлю
Для кожного userId завантажте запис User.
4. Для кожного користувача: 1) підрахуйте кількість історій, які він поділився, та 2) отримайте його запис UserUserRelation щодо поточного користувача API, щоб ми могли дізнатися, чи він вже підписаний. Також ми завантажили інші дані для кожного користувача, але вони не є релевантними тут.
5. Поверніть список профілів користувачів у вигляді відповіді API.

pic

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

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

Пошук рішення

Наша мета — зменшити час відповіді для цього кінцевого точка з середніх 2000 мілісекунд (2 секунди) до 100 мс.

Щоб досягти цього, нам потрібно зрозуміти контекст проблеми, яку ми намагаємося вирішити. Коли ми зрозуміємо контекст, нам потрібно вирішити, які компроміси ми готові прийняти, який ризик кожного компромісу, і як ми будемо зменшувати кожен ризик. Як інженер, це більша частина вашої роботи. Більшість решти — це правильне називання.

pic

Графік історій, поділених авторами на ManyStories.com за останні 365 днів.

Контекст проблеми

Ми хочемо показати список найновіших авторів, які поділилися історіями.

Коли користувач стає автором? Коли він ділиться історією, перейшовши за посиланням ManyStories.com/share. Ми знаємо, що в хороший день десятки наших користувачів діляться історією або двома. Іноді один користувач може вирішити поділитись великим обсягом свого архіву на ManyStories; це зазвичай трапляється після того, як ми схвалимо користувача з черги очікування, яка досі активна.

Добре, ми знаємо, коли користувач стає автором. Чи може він припинити бути автором? Більшість часу — ні. Єдине, як вони можуть припинити бути автором, це якщо вони скасують публікацію всіх своїх історій або видалять свій акаунт. Це рідкісне явище.

Користувач може поділитись кількома історіями, правда? Так. Тому нам потрібно пам'ятати про їх останню поділену історію, ігноруючи попередні.

Цілком нормально, що всі відвідувачі бачать однаковий список останніх авторів.

Чи добре налаштована наша база даних з огляду на наші фінансові ресурси? Так. База даних працює справно.

Чи є у нас необхідні індекси на таблицях? Так. Тут ми вже все зробили, і немає багато того, що можна було б покращити.

Добре, це достатньо контексту для зараз. Ось попередній перегляд кінцевого результату, до якого ми прагнемо. Подивіться на час 92,46 мс зліва, поруч із зеленим стовпцем, підписаним як "Waiting for server response" (Очікування відповіді сервера). Це час, який наш сервер витрачатиме на відповідь на запити списку останніх авторів. Це те, що ми хочемо досягти — значно краще, ніж попередній час у 2,61 секунди.

pic
Це кінцевий результат, до якого ми прагнемо.

Варіанти розв'язання проблеми

У нас є багато варіантів, але потрібно врахувати час і ресурси, які ми маємо в своєму розпорядженні. І тих, і інших у нас мало, тому треба оптимізувати.

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

pic

Це може працювати, але є дві потенційно дорогі проблеми. Перша — це дублювання даних, що призводить до фрагментації і, отже, потенційної непослідовності. Якщо ім’я або зображення користувача змінюється, нам потрібно пам’ятати, щоб синхронізувати цю нову таблицю, оновлюючи відповідні поля запису. Те саме стосується кожного разу, коли користувач ділиться або скасовує публікацію історії. Якщо ми не будемо це синхронізувати, ми можемо показувати застарілу інформацію про користувача в нашому UI, а це не дуже добре.

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

Наш другий варіант — дозволити браузеру кешувати нашу відповідь API.

Перш ніж одразу відкинути це, дозвольте пояснити чому. Коли ви дозволяєте браузерам кешувати відповіді вашого API, ви втрачаєте контроль над своєю системою. Якщо ваші дані змінюються, дуже важко інвалідовувати кеш браузера, оскільки у вас немає контролю над браузером, зовнішнім клієнтом. Як результат, ваші користувачі можуть отримати застарілі дані з кешу браузера наступного разу, коли вони відвідають вашу сторінку. Інвалідовувати кеш уже достатньо важко. Давайте не ускладнювати це, обмежуючи себе.


pic

Схема технологічного стеку веб-додатку автором.

Третій варіант — кешувати відповідь на рівні даних.

Це хороший варіант, але я вже реалізував це. Це все ще досить повільно, як видно із середнього часу 2000 мс, замість верхньої межі 3000 мс. Це через налаштування нормалізованої бази даних, яке ми обговорювали в нашому першому варіанті. Навіть якщо ми кешуємо запити кожної таблиці, нам все одно потрібно приєднувати 3+ таблиці на рівні сервісу. Це повільний процес.

Четвертий варіант — використовувати конвеєр агрегації бази даних з пошуками (lookups),

щоб приєднувати таблиці безпосередньо в системі бази даних. Це швидше, оскільки обробка відбувається ближче до пристроїв зберігання даних, і система запитів бази даних оптимізована для таких робочих процесів. Це зменшить час, необхідний для приєднання таблиць, і мінімізує обсяг даних, які передаються з бази даних на наш сервер API, що зменшить навантаження на сервер і зменшить латентність. Це, ймовірно, скоротить наш середній час відповіді хоча б на 50%, але цього недостатньо, коли ми починаємо з 2 секунд.

П’ятий варіант — кешувати оброблені дані на рівні сервісу.

Якщо ми використовуємо кеш, що працює за підмілісекундний час, наприклад DragonflyDB, альтернативу Redis, ми можемо кешувати результат всіх цих дорогих з’єднань бази даних, щоб не виконувати цей часозатратний процес кожного разу, коли відвідувач приходить. Це призведе до майже миттєвого часу відповіді, не враховуючи іншу латентність від накладних витрат API, таких як авторизація сесії.

pic

Графіка Лінкольна В. Даніела

Існують лише дві важкі речі в комп’ютерних науках: інвалідовування кешу і надання імен.

— Філ Карлтон

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

Обране рішення

Як ви вже могли здогадатися, рішення складається з двох частин: 4-го та 5-го варіантів з нашого списку. Це тому, що насправді є дві проблеми, які треба вирішити для максимального використання ресурсів нашої основної бази даних:

  1. Перша проблема полягає в тому, що спосіб, яким ми зчитуємо дані з бази, є не оптимальним.
  2. Друга проблема полягає в тому, що ми занадто часто зчитуємо дані з бази для цієї важливої функції.

Варіант рішення #4 (конвеєр агрегації бази даних) вирішує першу проблему. Варіант рішення #5 (кешування на рівні сервісу) вирішує другу проблему. Нижче я навів зображення рішення.

Давайте розглянемо його покроково.

pic

Рішення проблеми. Покращення швидкості завантаження в 20 разів.

Більшість роботи укладена в утиліту pageAggregation(), яку я створив, оскільки мені набридло мати справу з пагінацією та складними етапами конвеєра агрегації MongoDB. Деталі не настільки важливі, але я розгляну загальний підхід того, що вона робить.

Рішення проблеми #1

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

Ми можемо поліпшити цей процес, замінивши його на використання конвеєра агрегації бази даних з пошуками (lookups) для з’єднання таблиць в системі бази даних. MongoDB полегшує це за допомогою етапів агрегації. Я вже використовував агрегацію на деякому етапі цього процесу, але все можна виконати повністю в конвеєрі бази даних, що зберігає нашу локальну пам'ять і CPU.

pic

Нижче наведено конвеєр агрегації, до якого я здійснив рефакторинг, і саме він обробляється згаданою утилітою pageAggregation():

  1. $match: Запитуємо таблицю StoryUserRelation для всіх записів, де {isSharer: true}.
  2. $sort: сортуємо групи відносин за спаданням поля isSharerUpdatedAt. Це неприємна необхідність для точного отримання останнього значення isSharerUpdatedAt для кожного користувача на наступному етапі. Але це вже оптимізовано, оскільки це поле індексується в базі даних, тому буде швидким.
  3. $group: групуємо відносини за userId. Також, під час групування, додаємо до кожної групи останнє поле isSharerUpdatedAt та кількість записів цієї групи як поле numSharedStories. Це поверне список, що складається лише з об'єктів, які містять ці три згадані поля. По суті, ми отримуємо об'єкти, які представляють унікальних користувачів, які поділилися історією на ManyStories.
  4. $sort: сортуємо групи відносин за спаданням поля isSharerUpdatedAt.
  5. $limit: беремо перші 15 записів із відсортованого списку груп відносин. Це представляє 15 найактивніших користувачів, які поділилися історією.
  6. $lookup: з 15 згрупованих відносин отримуємо всіх відповідних користувачів за userId з таблиці User бази даних.
  7. Замінюємо 15 згрупованих відносин, що представляли користувачів, на фактичні об'єкти User, але зберігаємо та додаємо поля numSharedStories та isSharerUpdatedAt як lastSharedAt до кожного користувача.

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

Але основну частину економії часу ще належить досягти.

Рішення проблеми #2

Тепер, коли ми маємо список користувачів, які останніми поділилися історіями на ManyStories, ми можемо відправити його як відповідь на запит API. Наша робота завершена! Ну, не так швидко… чи повільно. Нам потрібно подумати про майбутнє. Це велика кількість роботи, яку ми виконали для агрегації цих даних. Ми доволі сильно навантажили нашу базу даних. Що якщо прийде інший відвідувач, який захоче побачити цей список? А якщо тисячі відвідувачів щодня? Ми не хочемо щоразу повторювати всю цю роботу, чи не так? Вірно. Ми можемо зробити краще і бути швидшими одночасно.

Кеш на допомогу!

pic

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

pic

А на початку, коли приходить інший запит, ми перевіряємо, чи можемо ми використовувати кеш. Якщо можемо, ми намагаємося отримати попередньо агрегований результат з кешу і повернути його. Якщо його немає, ми знову агрегуємо результат з бази даних. Ось як це виглядає:

pic

Це чудово. Оскільки ми використовуємо кеш, сумісний з Redis, який використовує RAM для зберігання наших даних, додавання і зчитування результатів з кешу проходить дуже швидко. Ми офіційно скоротили час завантаження списку останніх користувачів, які поділилися історіями, з 2000 мілісекунд до всього лише 100 мілісекунд в середньому.

Це чудово!

pic
Кожного разу, коли перезавантажується домашня сторінка ManyStories, помітте майже миттєве завантаження та відображення списку останніх користувачів у каруселі.

Але ми створили ще одну проблему. З великим кешем приходить велика відповідальність за його інвалідизацію.

pic

Коли ми кладемо щось у кеш, ми повинні бути обережними з ризиками та витратами.

Перший ризик полягає в тому, що ми можемо показати застарілі дані нашим користувачам або іншим частинам нашої системи. На щастя, список останніх користувачів, які поділилися історіями, не має строгого графіка або природного очікування свіжості від користувачів, крім того, щоб він не був назавжди застарілим. Тільки якщо наш список оновлюється в інтерфейсі користувача з помірною швидкістю, не занадто довго після того, як автор поділився історією, ми можемо терпіти певну затримку.

Зважаючи на це, ми встановлюємо час життя (TTL) цього запису в кеші на дуже високий рівень — 6 годин. Це означає, що, поки ми отримаємо 1 користувача протягом шести годин і кеш не переповниться, наш список має бути доступним для нових відвідувачів, щоб швидко отримати його з кешу.

Крім того, нам не важливо видаляти запис з кешу в будь-який момент, тому що наявність списку, готового до використання, краще, ніж відсутність, навіть якщо він трохи застарілий.

Отже, якщо ми не видаляємо запис з кешу, чи не буде він завжди застарілим? Не зовсім. Перш ніж ми відповімо на це, давайте знову подивимося на цю діаграму нашої системи:

pic

Оскільки наш кеш запису фактично є вічним, нам потрібно оновлювати його якимось чином. Інакше ми завжди будемо отримувати кешовану версію і ніколи не відтворимо список з основної бази даних. Це не добре, оскільки ми не хочемо показувати нашим користувачам той самий список авторів назавжди.

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

Оскільки агрегація даних для списку останніх користувачів, які поділилися історіями, є важкою і повільною, її виконання на нашому основному сервері API значно погіршить його продуктивність.

Кращий підхід — це створити подію в черзі PubSub для обробки нашими фоновими серверами-робітниками. Вони оснащені ресурсами для обробки таких важких навантажень. Коли фоновий робітник забирає задачу з черги — я використовую AWS SQS з повторними спробами в разі невдачі — він викликає ту ж саму функцію, що й раніше (getRecentSharers()). Єдине, що нам потрібно зробити, це сказати йому пропустити перевірку кешу. Це змусить його знову отримати новий список з основної бази даних і замінити старий список на новий, з актуальними авторами для наших читачів.

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

От і все, ми замкнули коло.

А оскільки дані маленькі, їх зберігання в кеші не коштує дорого.

Висновок

Ого, це було довго! Радий, що ви дійшли до цього моменту. Тепер ви знаєте, як зробити часи відповіді вашого API швидшими, використовувати агрегації бази даних для створення складних даних, кешувати дані, отримувати кешовані дані та оновлювати кеш. І все це при збереженні навантаження на ваші сервери.

Консультації

Якщо у вас є стартап, який страждає від повільного отримання даних або високих витрат на базу даних, звертайтеся до мене для консультації з різкого покращення продуктивності вашого мобільного чи веб-додатку за нижчими витратами. Просто надішліть лист на [email protected]

Перекладено з: How We Speed Up Complex UI Data Loading in NodeJS by 20X

Leave a Reply

Your email address will not be published. Required fields are marked *