[Программирование, SQL, Алгоритмы, ERP-системы] Множественные источники данных в интерфейсе — client-side «SQL»
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Иногда в интерфейсе наших приложений СБИС возникает необходимость "сгруппировать" часть записей в некотором списке (например, служебные сообщения в чате, контакты и телефонные звонки).Хорошо, если все эти записи приходят с одного источника, а вот если из разных сервисов, да с навигацией по курсору - алгоритм реализации становится весьма нетривиальным.
Я целенаправленно не буду здесь приводить реализацию "в коде", а опишу исключительно алгоритмический подход к решению, чтобы при необходимости вы могли его самостоятельно смасштабировать на свои задачи. Итак...Постановка задачиУ нас есть два сервиса. Как бы может быть и больше, но, следуя предыдущей картинке, пусть для определенности это будут сервисы Звонков и Контактов.Спасибо коллегам из CRMза интересную задачу. Хотим в карточке организации в сквозном хронологическом порядке по дате выводить контакты и звонки, но все звонки между соседними записями контактов "схлопнуть" в единственную запись с указанием их количества.Но при этом интерфейс должен оставаться "живым" для пользователя - то есть не должно быть длительных пауз, когда мы чего-то ждем, но не рисуем в списке.
Группировка нескольких звонков в одну записьСразу договоримся, что сервисы работают с базой адекватно, есть все нужные индексы, и запросы строятся эффективно, примерно как я описывал в прошлых статьях:
- PostgreSQL Antipatterns: ударим словарем по тяжелому JOIN
- PostgreSQL Antipatterns: навигация по реестру
- SQL HowTo: пишем while-цикл прямо в запросе, или «Элементарная трехходовка»
Неудачное решение #1: "дай мне все"Понятно, что можно вычитать сразу вообще все и смержить на бизнес-логике, но это приведет к ожиданию интерфейсом хоть какой-то реакции, пока БЛ не проделает всю работу. А ее может быть реально много - одних только звонков могут быть сотни.Да и для сервиса задача "отдать все" совсем нелегка, если там данных за несколько лет работы. Но зачем нам "все", если пользователь дальше первой страницы листает очень редко?Неудачное решение #2: "частый гребень"Так, нам контакты группировать не надо?.. Давайте тогда запросим первую страницу (20 записей) с сервиса контактов, а для каждого интервала дат между "соседними" контактами спросим, что там есть в звонках - сразу и количество получим.А теперь давайте представим, что у нас все звонки (или очень много) оказались хронологически "над" первым же контактом - что будет? А будут те же самые тормоза в интерфейсе, что и в предыдущем варианте.Кроме того, мы или отправим на сервис много запросов (на каждый из интервалов), чем создадим избыточную нагрузку. Или отправим один запрос со списком всех интервалов, но он заведомо будет отрабатывать "долго".Удачное решение #1: "чтение сегментами"Набив шишек на предыдущих решениях, приходим к выводу, что нам надо запрашивать данные у обоих сервисов по курсору - сколько-то записей (пусть будет 20), начиная с какого-то индексного ключа.Что дальше делать с двумя упорядоченными сегментами данных, достаточно очевидно - сливаем (merge ordered) и отрезаем (limit) от упорядоченного все записи после ближайшего из "крайних" ключей от каждого из сервисов.
Например, в нашем примере получилось, что ключ времени "крайнего" звонка соответствует только 15 из 20 прочитанных контактов. Про порядок оставшихся 5 контактов и других звонков мы не можем ничего сказать, потому что "других звонков" как раз нету в обозримом пространстве - поэтому нарисовать их пока не можем.Неудачное решение #3: "One Ring to rule them all"Как любому разработчику хочется сразу упростить себе жизнь в предыдущем варианте? Конечно же, запомнить только один ключ - последней отрисованной записи, чтобы потом итерацию для следующего сегмента делать уже от него. Но при таком варианте часть данных будет вычитываться повторно раз за разом - ровно те, которые мы отбросили в предыдущей итерации и не отобразили.В особо клиническом случае, типа "сначала много-много звонков, потом уже начинаются контакты" один и тот же первый сегмент контактов может запрашиваться повторно много-много раз, с каждым сегментом звонков.
Неудачное решение #4: "два ключа на server-side"Обернем предыдущую проблему себе на пользу - будем запоминать независимые ключи, отдельно для каждого из источников. Это правильный ход, но он не избавит нас от проблемы повторной вычитки тех же данных, если нам их негде сохранить.Поскольку у нас stateless server-side БЛ, то либо мы их таки и не сохраним, или вынуждены будем городить где-то отдельное хранилище состояний. Сделать это можно, но совсем не просто:
- хранение сегментов должно происходить по уникальному ключу экземпляра-выборки-на-странице
- необходима политика инвалидации этих данных со временем, чтобы память не "текла"
- работа с этим хранилищем подразумевает дополнительные издержки на сериализацию-пересылку-десериализацию
Удачное решение #2: "два ключа на client-side"Собственно, а зачем нам уносить все это на сервер-сайд, если все данные нам нужны только на клиенте?.. Давайте их там и оставим.То есть ровно те данные, которые "не отрисовали" оставить храниться на клиенте (например, прямо в памяти вкладки, даже не в localStorage), пока нам не понадобится их нарисовать.В нашем предыдущем примере получится что-то вроде:
- прочитали параллельно 20 контактов и 20 звонков
- звонки "сгруппировали" в 5 записей
- нарисовали 5 "групповых" звонков + 15 контактов
- 5 ненарисованных конктактов оставили в хранилище
- до 20 чего-то не хватает? запрашиваем! (контакты и звонки по 20, параллельно от своих "крайних" ключей)
- "задача сведена к предыдущей", только у нас уже сразу 25 контактов на 20 звонков есть
Edge CasesФактически, единственный отрицательный эффект у такого решения - последняя нарисованная "групповая" запись может "крутить счетчиком", пока мы дочитываем "вливающиеся" в нее все новые и новые записи.К счастью, такая ситуация достаточно позитивно воспринимается в интерфейсе, поскольку мы показываем пользователю: "Эй, все хорошо, мы не умерли, не повисли, мы работаем!"
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, Программирование, GitHub, Игры и игровые приставки] Разработчик сделал Doom Captcha — теперь можно проходить тест на робота играя
- [Программирование, Управление разработкой, Управление персоналом, Карьера в IT-индустрии] Ловим бандерлогов в офисе
- [Программирование] Именуйте классы, переменные и функции для людей, а не для машин
- [Разработка систем связи, Программирование микроконтроллеров] Power-line communication. Часть 3 — Основные блоки устройства
- [Open source, Системное администрирование, PostgreSQL, IT-инфраструктура] Мониторинг 95+ метрик PostgreSQL с помощью плагина Zabbix Agent 2
- [PHP, Программирование, Проектирование и рефакторинг, ООП, Go] Prototype Design Pattern в Golang
- [Информационная безопасность, Программирование, Производство и разработка электроники, Гаджеты, Игры и игровые приставки] Часть 3: ESPboy2 — гаджет для ретро игр и экспериментов с IoT, новости проекта 2021
- [Системное программирование, Программирование микроконтроллеров, Компьютерное железо] Предельная скорость USB на STM32F103, чем она обусловлена?
- [Программирование, Компиляторы, Функциональное программирование, Искусственный интеллект] Тестирование синтаксиса языка программирования с необычной концепцией
- [Программирование, Java, Kotlin] Почему Kotlin лучше Java?
Теги для поиска: #_programmirovanie (Программирование), #_sql, #_algoritmy (Алгоритмы), #_erpsistemy (ERP-системы), #_sql, #_sql_tips_and_tricks, #_sbis (сбис), #_blog_kompanii_tenzor (
Блог компании Тензор
), #_programmirovanie (
Программирование
), #_sql, #_algoritmy (
Алгоритмы
), #_erpsistemy (
ERP-системы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:13
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Иногда в интерфейсе наших приложений СБИС возникает необходимость "сгруппировать" часть записей в некотором списке (например, служебные сообщения в чате, контакты и телефонные звонки).Хорошо, если все эти записи приходят с одного источника, а вот если из разных сервисов, да с навигацией по курсору - алгоритм реализации становится весьма нетривиальным. Я целенаправленно не буду здесь приводить реализацию "в коде", а опишу исключительно алгоритмический подход к решению, чтобы при необходимости вы могли его самостоятельно смасштабировать на свои задачи. Итак...Постановка задачиУ нас есть два сервиса. Как бы может быть и больше, но, следуя предыдущей картинке, пусть для определенности это будут сервисы Звонков и Контактов.Спасибо коллегам из CRMза интересную задачу. Хотим в карточке организации в сквозном хронологическом порядке по дате выводить контакты и звонки, но все звонки между соседними записями контактов "схлопнуть" в единственную запись с указанием их количества.Но при этом интерфейс должен оставаться "живым" для пользователя - то есть не должно быть длительных пауз, когда мы чего-то ждем, но не рисуем в списке. Группировка нескольких звонков в одну записьСразу договоримся, что сервисы работают с базой адекватно, есть все нужные индексы, и запросы строятся эффективно, примерно как я описывал в прошлых статьях:
Например, в нашем примере получилось, что ключ времени "крайнего" звонка соответствует только 15 из 20 прочитанных контактов. Про порядок оставшихся 5 контактов и других звонков мы не можем ничего сказать, потому что "других звонков" как раз нету в обозримом пространстве - поэтому нарисовать их пока не можем.Неудачное решение #3: "One Ring to rule them all"Как любому разработчику хочется сразу упростить себе жизнь в предыдущем варианте? Конечно же, запомнить только один ключ - последней отрисованной записи, чтобы потом итерацию для следующего сегмента делать уже от него. Но при таком варианте часть данных будет вычитываться повторно раз за разом - ровно те, которые мы отбросили в предыдущей итерации и не отобразили.В особо клиническом случае, типа "сначала много-много звонков, потом уже начинаются контакты" один и тот же первый сегмент контактов может запрашиваться повторно много-много раз, с каждым сегментом звонков. Неудачное решение #4: "два ключа на server-side"Обернем предыдущую проблему себе на пользу - будем запоминать независимые ключи, отдельно для каждого из источников. Это правильный ход, но он не избавит нас от проблемы повторной вычитки тех же данных, если нам их негде сохранить.Поскольку у нас stateless server-side БЛ, то либо мы их таки и не сохраним, или вынуждены будем городить где-то отдельное хранилище состояний. Сделать это можно, но совсем не просто:
=========== Источник: habr.com =========== Похожие новости:
Блог компании Тензор ), #_programmirovanie ( Программирование ), #_sql, #_algoritmy ( Алгоритмы ), #_erpsistemy ( ERP-системы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:13
Часовой пояс: UTC + 5