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

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

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

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

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

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

//...

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

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

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

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

См. также

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

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

Если Счет = СчетаПоКодам["20321"] Тогда
	//
КонецЕсли;
Показать
4. Роман Уничкин (unichkin) 766 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) 766 06.12.17 17:12 Сейчас в теме
(13)
- "доступ к элементу соответствия по ключу происходит почти со скоростью доступа к массиву или элементу структуры!" - и что? Тут больше дело привычки. У соответствия нет метода "Свойство" - нельзя проверить, существует ли параметр.
- в приведенном коде все кэширование выполняется локально, что раздувает метод, и дублирует код. Инициализация кэша, поиск в кэше, помещение в кэш - все в одном месте. Если это модуль объекта, лучше обособить, как мне кажется - будет читабельнее.

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

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

КонецЦикла

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