Программные перечисления, ч.2: приемы кэширования при разработке

Программирование - Практика программирования

62
Все знают что такое кэш, и зачем он нужен. Но в 1С разработчик обычно использует кэширование только на уровне конфигурации, а в какой-нибудь обработке скорее ломает голову над запросом - как получить все данные за один заход... Хочется рассказать о том, как можно добиться хороших результатов с стратегией "разделяй и властвуй".

В своей первой статье Использование программных перечислений, ч.1: строковые константы я рассказал о способе, который избавит от неприятностей, связанных с сравнением в коде на строку. Здесь на базе этого-же приема рассмотрена задача по оптимизации загрузки данных из внешнего источника. Расскажу о случае, который неоднократно наблюдал в разных его ипостасях. Стоит задача по загрузке данных в БД из внешнего файла, заказчик предоставил пару примеров, программист успешно все реализовал\протестил, сдал работу.. И тут через месяц заказчик грузит файл раз эдак в 50 превышающий пример.. Все не просто тупит, а зависает на ~2 часа. Заказчик недоволен. После работ по оптимизации время загрузки снизилось с 2 часов до 30 минут (из которых только 5 это синхронизация, а все остальное - запись данных в базу).. В чем же была ошибка? При синхронизации данных не использовалось кэширование, и поиск ссылок по одним и тем же ключевым полям выполнялся многократно. После того как был добавлен кэш все залетало.

Началась оптимизация с того, что я добавил глобальную переменную типа "Структура", которая и выполняет роль кэша:

Перем мСтруктураКэшДанных;

//...

Процедура ЗагрузитьДанные(ТаблицаДанныеФайла)
	//...	
КонецПроцедуры

//...
мСтруктураКэшДанных = Новый Структура;

Расскажу далее о нескольких универсальных методах, которые полезно применять в подобных случаях.

 
 1. Синхронизация данных
 
2. Выборка из результата запроса, и метод "НайтиСледующий()"
 
 3. Работа с планом счетов
62

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. mifka186 6 30.10.17 09:32 Сейчас в теме
Есть еще ситуации, когда надо грузить много однотипных файлов. В таком случае я поиск данных скидываю в кэш. Получается, что все возможные значения для загрузки находятся на первых 2-3 файлах, а самих файлов может быть 70-80. Дальше заполнение данных идёт уже из кэша.
2. guy_septimiy 30.10.17 13:32 Сейчас в теме
(1) Аналогично. Вчера делал таким образом загрузку данных о операциях по мобильной связи. Файл в миллион строк с кешированием грузится где-то за 2 минуты. Если не использовать предварительное кеширование и обновление кеша, то время подскакивает раз в 20.
3. vano-ekt 1129 02.11.17 08:00 Сейчас в теме
КлючСчета = "сч" + СтрЗаменить(Выборка.Код, ".", "_")

они же предопределенные, зачем их кэшить?
А если кэшить, чем соответствие не угодило?
Запрос = Новый Запрос("ВЫБРАТЬ
                      |	Бюджетирование.Код,
                      |	Бюджетирование.Ссылка
                      |ИЗ
                      |	ПланСчетов.Бюджетирование КАК Бюджетирование");
Выборка = Запрос.Выполнить().Выбрать();
СчетаПоКодам = Новый Соответствие;
Пока Выборка.Следующий() Цикл
	СчетаПоКодам.Вставить(Выборка.Код,Выборка.Ссылка);
КонецЦикла;

Если Счет = СчетаПоКодам["20321"] Тогда
	//
КонецЕсли;
Показать
4. unichkin 1104 02.11.17 12:59 Сейчас в теме
Счет может и не быть предопределенным, а быть пользовательским. Если использовать соответствие нужно получать значение по ключу, напр.
СчетаУпр.Получить(<КодСчета>) или СчетаУпр[<КодСчета>]- долго, проще написать СчетаУпр.сч58. Кроме того у соответствия нет такого полезного метода как "Свойство".
Кэшить их - затем чтобы применять в люом месте программы, не объявляя новый поиск счета.
13. kasper076 19 06.12.17 14:57 Сейчас в теме
(0)
КэшСсылок = Новый Соответствие;
КэшСсылок.Вставить("Объект1",		Новый Соответствие);
КэшСсылок.Вставить("Объект2",		Новый Соответствие);
КэшСсылок.Вставить("Объект3",		Новый Соответствие);

Пока ПолучитьСледующийЭлемент() Цикл

	Объект1Ссылка = КэшСсылок["Объект1"][Ключ1];
	Если Объект1Ссылка = Неопределено Тогда
		Объект1Ссылка = ПолучитьОбъект1(Ключ1); //Функция выполняет запрос 
		КэшСсылок["Объект1"][Ключ1] = Объект1Ссылка;
	КонецЕсли;
	
	Объект2Ссылка = КэшСсылок["Объект2"][Ключ2];
	Если Объект2Ссылка = Неопределено Тогда
		Объект2Ссылка = ПолучитьОбъект2(Ключ2); //Функция выполняет запрос 
		КэшСсылок["Объект2"][Ключ2] = Объект2Ссылка;
	КонецЕсли;

	Объект3Ссылка = КэшСсылок["Объект3"][Ключ3];
	Если Объект3Ссылка = Неопределено Тогда
		Объект3Ссылка = ПолучитьОбъект3(Ключ3); //Функция выполняет запрос 
		КэшСсылок["Объект3"][Ключ3] = Объект3Ссылка;
	КонецЕсли;

КонецЦикла
Показать


(4) Эффективная обработка данных в оперативной памяти за счет использования коллекции "соответствие"
(5) Загрузка ТаблицыЗначений в TempDB производится построчно. Если таблица будет большая, то это будет не оптимально.
14. unichkin 1104 06.12.17 17:12 Сейчас в теме
(13)
- "доступ к элементу соответствия по ключу происходит почти со скоростью доступа к массиву или элементу структуры!" - и что? Тут больше дело привычки. У соответствия нет метода "Свойство" - нельзя проверить, существует ли параметр.
- в приведенном коде все кэширование выполняется локально, что раздувает метод, и дублирует код. Инициализация кэша, поиск в кэше, помещение в кэш - все в одном месте. Если это модуль объекта, лучше обособить, как мне кажется - будет читабельнее.

Пока ПолучитьСледующийЭлемент() Цикл

    Объект1Ссылка = ОбъектКэша(Ключ1);
    Объект2Ссылка = ОбъектКэша(Ключ2);
    Объект3Ссылка = ОбъектКэша(Ключ3);
    

КонецЦикла

Функция ОбъектКэша(Ключ) 
	                                
	Если НЕ мСтруктураКэшДанных.Свойство("СоответствиеОбъектыКэша") Тогда
		мСтруктураКэшДанных.Вставить("СоответствиеОбъектыКэша");
	КонецЕсли;
	
	ЗначениеКэша = мСтруктураКэшДанных.СоответствиеОбъектыКэша.Получить(Ключ);
	Если ЗначениеКэша = Неопределено Тогда
		ЗначениеКэша = ПолучитьОбъект(Ключ);		
		мСтруктураКэшДанных.СоответствиеОбъектыКэша.Вставить(Ключ, ЗначениеКэша);		
	КонецЕсли; 
	
	Возврат ЗначениеКэша;
		
КонецФункции
Показать
5. NN2P 303 02.11.17 13:33 Сейчас в теме
Роман, можете уточнить, почему при загрузке данных извне Вы не рассматриваете следующий прием: загрузить данные в таблицу значений из внешнего источника.Передать ее в запрос как параметр. В запросе соединиться со всеми нужными таблицами(справочниками, регистрами и пр.). Затем проверить наличие ссылок или записей регистров в результате соединения с таблицами. Затем по несопоставленным пройтись и создать, по сопоставленным обновить данные, если есть различия.
u_n_k_n_o_w_n; Sam13; creatermc; bulpi; +4 Ответить
8. bulpi 137 07.11.17 23:13 Сейчас в теме
(5)
Истину глаголешь! :)
Идеи, предлагаемые автором весьма и весьма спорны. Как по производительности, так и по читабельности.
6. unichkin 1104 02.11.17 18:54 Сейчас в теме
Я рассматриваю, см. п 2.1
15. Synoecium 412 27.04.18 13:45 Сейчас в теме
(6) можно исходную таблицу нарезать порциями (например по 10к элементов) и в цикле загружать их как описано в 2.1. На мой взгляд наиболее универсальный и быстрый способ.
7. jONES1979 03.11.17 09:03 Сейчас в теме
Cпасибо за качественное оформление блоками! Очень удобно просматривать!
jif; О.Ж; JohnConnor; Serg O.; +4 Ответить
9. Serg O. 155 08.11.17 00:38 Сейчас в теме
Идея клёвая... а как продолжение... Можно ли сохранить кэш в хранилище значений? Если такие обмены раз в сутки делаются после перезагрузки севера. Или в файл придется сохранять...
10. unichkin 1104 08.11.17 11:00 Сейчас в теме
(9) Да, тогда промежуточное хранилище необходимо. Возможно имеет смысл задействовать подсистему присоединенных файлов, чтобы был удобный доступ к кэшу
11. klel 08.11.17 11:52 Сейчас в теме
Спасибо идея классная, сам пользуюсь при обменах между базами. Ускоряет работу в разы.
12. Scorpion4eg 101 23.11.17 08:01 Сейчас в теме
Хорошая статья. Вот только при действительно больших объемах кэширование скорее враг. Недавно пришлось поставить x64 платформу. Потому что 1с выедала 4,5 оперативнки. Так получилось что в кэш попало 500 тыс уникальных значений.
16. pavlovsv 08.05.18 16:17 Сейчас в теме
(12) Согласен с Вами, тем не менее, идея очень здравая. Позволяет существенно снизить количество обращений к БД в случае обработки больших объемов одних и тех же данных, которые необходимо получать "через точку".
Что же касается механизма для "нормализации" кэша, контроля за его размерами, управление жизненным циклом кэшированных данных... Думаю, что можно попробовать самостоятельно развить этот концепт.

Хотел обратить внимание, что в случае использования ТаблицыЗначений для хранения кэшированных значений, может быть полезным организация индекса по ключевым полям.
В конкретном примере из этой статьи, наверное, это избыточно(так как по условию задачи записей будет всего 10).
Однако, в случае, когда количество строк несколько тысяч и ключ для поиска составной - индекс становится полезным.

1. Синхронизация данных -> Как стало
Функция НайтиСотрудникаОрганизации(Организация, ФизЛицо)

// На входе два ссылочных реквизита - соответствие использовать неудобно, в качестве кэша применяется таблица значений:
Если НЕ мСтруктураКэшДанных.Свойство("ТаблицаСотрудников") Тогда
ТаблицаСотрудников = Новый ТаблицаЗначений;
ТаблицаСотрудников.Колонки.Добавить("Организация");
ТаблицаСотрудников.Колонки.Добавить("ФизЛицо");
ТаблицаСотрудников.Колонки.Добавить("Сотрудник");

ТаблицаСотрудников.Индексы.Добавить("Организация, ФизЛицо"); //добавим индекс по ключевым полям

мСтруктураКэшДанных.Вставить("ТаблицаСотрудников", ТаблицаСотрудников);
КонецЕсли;
17. unichkin 1104 08.05.18 18:33 Сейчас в теме
(16)
Однако, в случае, когда количество строк несколько тысяч и ключ для поиска составной - индекс становится полезным.

это вроде "и так понятно".. Об о всем не расскажешь)
Индексировать стоит от 1000 строк Сортировка строк таблиц значений - ИТС в тему)
Оставьте свое сообщение