Pull to refresh

Comments 30

Предлагаю один из «странных», но рабочих вариантов оптимизации (кому-то этот способ может показаться быдлокодерским). Поскольку в клиент-серверных системах данные на клиенте вне транзакций можно всегда считать неактуальными (на сервере они уже могли измениться), то на клиенте можно реализовать логику «не запрашивать заданный справочник у сервера чаще чем 1 раз в N секунд» (ну или миллисекунд — в зависимости от того, как часто клиентский код может запросить актуальную версию справочника у сервера).
Такой вариант позволяет в клиентском коде не беспокоиться (не думать) о сокращении количества обращений за актуальной версией справочника — слишком частые вызовы просто будут проигнорированы промежуточным слоем клиента, обращений к серверу при этом не будет вообще.
Ключевой недостаток такого способа, на мой взгляд, — может появиться масса глюков, т.к. справочники оказываются в несинхронном состоянии. Нафиг-нафиг :)
А такая банальная вещь, как загрузка в параллель, у вас не рассмотрена?
Имеется ввиду одновременная (в разных потоках) загрузка нескольких справочников? Или вынесение кода загрузки в отдельный поток? Если второе, то это мало чем поможет для кода вида «запросить справочник» -> «обработать на его основе значения». Ждать всё равно придется, а и трафик, и латентность не сократятся.
Имеется в виду — начать грузить справочники раньше, чем они стали нужны.

Ну и да, я раньше не заметил/не подумал, но информацию о версии справочника нужно хранить в самом справочнике, тогда вы избавитесь от значительной части проблем.
«Имеется в виду — начать грузить справочники раньше, чем они стали нужны.»
Наиболее объемные справочники подгружаются при старте программы. Но соль в том, что с момента старта и до момента начала работы они могут измениться. А это критично (пользователь гораздо больше доверяет системе, да и надежность выше, когда данные всегда актуальны).

«Ну и да, я раньше не заметил/не подумал, но информацию о версии справочника нужно хранить в самом справочнике, тогда вы избавитесь от значительной части проблем.»
С точки зрения клиентской части (у меня трехзвенка) это без разницы. Для нее справочник и его версия выглядят как одно целое. Похоже, я не понял Вашу идею.
«Наиболее объемные справочники подгружаются при старте программы. Но соль в том, что с момента старта и до момента начала работы они могут измениться.»
Ну так загрузить diff существенно дешевле.

«С точки зрения клиентской части (у меня трехзвенка) это без разницы.»
Это правда. Но вот с точки зрения реализации апп-сервера разница есть. Никаких логов, транзакций и прочего мусора.
«Но вот с точки зрения реализации апп-сервера разница есть. Никаких логов, транзакций и прочего мусора.»
Если у меня записи в таблице вида +<строка>, то как в этой таблице хранить версию?
Также получается, что хранение неунифицировано для справочников разных видов (есть справочники вида +<число>; +<дата> и т.п.). Или я не понял идею?
Хабр вырезал слова ID перед плюсами :) (т.к. я поставил их в <>)
Добавить поле «версия», очевидно. Это стандартное решение.
Это уже обсуждалось в ветке ниже. Тяжеловесно получается.
Такое решение хорошо, когда каждое значение часто обновляется и имеет ценность независимо от других значений (прошу не бить за формулировку ;) ). Но это уже обычно не справочник.
«Это уже обсуждалось в ветке ниже. Тяжеловесно получается.»
Ничего тяжеловесного в этом нет. Это плата за производительность. У вас же проблема с производительностью? Она (почти) всегда решается нарушением концептуальной целостности системы.

«Такое решение хорошо, когда каждое значение часто обновляется и имеет ценность независимо от других значений (прошу не бить за формулировку ;) ). Но это уже обычно не справочник.»
Это вполне может быть справочник контрагентов, например.
Наверное, разработчики каждой крупной системы проходят эти шаги. :)

А запист лога изменений и timestamp у вас происходят разве не в одной транзакции с изменениями самих данных?
Лог изменений я тогда решил хранить в оперативной памяти на сервере приложений. Поэтому транзакции потребовалось контролировать вручную.
«Наверное, разработчики каждой крупной системы проходят эти шаги. :)»
Да, вероятно. Оттуда и возникло желание написать статью об этом :)
UFO landed and left these words here
Да, согласен. Ваш способ отлично подходит также для двухзвенки. Сам я не стал нагружать сервер БД «лишними» запросами и триггерами, переложив эту функцию на сервер приложения.
Про версию для каждой записи: мне кажется, это чересчур тяжеловесно для компактных, редко изменяющихся записей (справочник изменяется часто, но т.к. записей очень много вероятность изменения каждой отдельной записи очень мала).
UFO landed and left these words here
Я, наверное, банальную вещь скажу.
Добавьте поле modified к каждому справочнику. Получайте с загрузкой данных текущее время и при обновлении грузите только записи с более поздней датой изменений. Без всяких дополнительных таблиц.

А вообще зависит от того, как вы хранение справочников организовали.
Про хранение на хабре вроде уже обсуждалось.

Грузите данные в фоне. Грузите данные по частям. 100000 записей в справочнике — это много. Неужели необходимо часто делать выбор из всего списка? Может проще поиск на сервере делать и возвращать только результат?

Спасибо за ссылку на аналогичную статью! Действительно, очень похоже. :) Но разница в том, насколько я понимаю, что там решения оптимальные для двузвенки, а меня — для трехзвенки.

«Неужели необходимо часто делать выбор из всего списка? Может проще поиск на сервере делать и возвращать только результат?»
Согласен, зачастую это рациональней.
Но в моей системе у многих справочников особенность UI — они должны быть полностью показаны пользователю. Не сказать, что особо рациональная особенность, но сделано по образу и подобию окна старого поиска в КонсультантПлюс.
А у вас кто-то кроме сервера приложений может обращаться к базе? Или есть несколько несвязанных способов изменить данные в бд через сервер приложений? Например интерфейс пользователя, и интерфейс оператора, редактирующего справочники. Оба лезут через сервер приложений, но кэш у них разный.

Не видел, к сожалению, консультант плюс. Но вряд ли на клиенте пользователю показывается сразу сто тысяч записей. Думаю, можно попилить выдачу. но это уже конкретное решение нужно смотреть.
«А у вас кто-то кроме сервера приложений может обращаться к базе?»
Никто. Весь обмен идет только через сервер приложения.

«Но вряд ли на клиенте пользователю показывается сразу сто тысяч записей.»
Сейчас посмотрел справочник юридических номеров документов. В нем 174858 значений. Фильтрация этого справочника происходит на лету в момент ввода пользователем фильтра.
Перечитал четвертый шаг. Получается вы несколькими «руками» параллельно лезете в бд.

Вижу 2 места для оптимизации (если она, конечно, там нужна).
1. Первичная загрузка справочника на клиент.
1.1. Можно сделать поэтапную загрузку. Справочник разбить по первым буквам. Требовать ввести первые три буквы, прежде чем грузить справочник. Сделать select TOP 20 после ввода первой буквы, select TOP 20 после второй итд — то есть каждый раз грузить только несколько первых подходящих записей итп.
1.2. Можно попробовать ужать передаваемые на клиент данные. Свой протокол придумать более сжатый. Ужать данные логически. Например, вместо datetime кидать разницу с предыдущим значением. Ужать данные физически — заархивировать и передать.
2. Последующие обновления данных на клиенте
Можно вообще нужные справочники закэшировать на сервере приложений. Изменения отслеживать там и скидывать в лог, по которому асинхронно обновлять бд.

1.1. Да, пожалуй. Вероятно к этому и приду, когда справочники разрастутся сверх всякой меры. В моем случае порции данных будут ощутимо больше, но суть от этого не меняется.
1.2. Протокол сейчас сжимается GZip'ом. Все пакеты на нижнем уровне (на уровне чуть выше сокетов в моем случае) пакуются, если их размер превышает 500 байт. Для меньшего размера издержки на паковку по времени превышают выигрыш, хотя это достаточно умозрительно и скорее игрища — практический смысл от такого ограничения я не тестировал.
Паковка с учетом специфики данных в каждом справочнике — это интересно :)
2. Мысль понял. Спасибо за идею! Хотя пока в моем случае запросы на модификацию не настолько частые.
Про «несколькими руками лезете в БД» мысль не понял. В моем случае несколько десятков пользователей одновременно работают над одним набором данных. Поэтому несколько рук — да, конечно. Но все «руки» контролируются сервером приложения.
Я имел в виду, что с сервера приложений в базу идут параллельные запросы, каждый из которых в своей транзакции. То есть разруливание конкурентного доступа происходит на уровне базы, а не сервера приложений
Я наверное ещё более банальную вещь скажу — перестаньте изобретать велосипед. Переходите на REST (если ещё не перешли) и главное (!) — используйте «родные» возможности HTTP для решения этих проблем.

Навскидку, вам подойдёт Last-Modified (со стороны сервера), If-Modified-Since (со стороны клиента), и архивирование (compress) ответов (со стороны сервера, естественно) вместе с применением локальных прокси (промежуточные звенья к дистанционным пользователям). Если сильно заморочиться — сюда можно даже Range-запросы прикрутить, в которых вместо указания байт — указывать диапазоны затребованных идентификаторов из справочника. Но это будет идти вразрез со спецификацией HTTP, как мне кажется. Чтоб не терять много времени на установление соединения — используйте HTTP/1.1 и pipe-line запросы.

Поверьте, HTTP не дураки разрабатывали, и проксирование/кеширование весьма неплохо проработано. Будет глупо терять время и переизобретать всё заново.
А кто-то вообще говорил про HTTP? Клиент-серверные приложения — это не обязательно веб.
Тут от организации поиска зависит. По-моему, лучше не кешировать весь справочник. Например, у поисковиков автозаполнение так работает. Там в голову никому не придет кешировать все варианты запроса (их слишком много). Вариант с автодополнением очень хорош и удобен — данных передается мало, работает быстро. Конечно запрос там не так быстро идёт как если бы работало только на клиенте, но всё же вполне достаточно.
Согласен. Но организация такой подгрузки (чтобы было быстро и удобно) — отдельная, довольно немаленькая задача. В случае с поисковиками у них выбора нет — все значения не передать в принципе. А у нас передача одного справочника целиком занимает доли секунды. Почему бы не передать…
Было бы здорово почитать об алгоритме такой динамической подгрузки. Интуитивно вроде всё понятно, но чует сердце, что подводных камней там в избытке :)
Sign up to leave a comment.

Articles