Заметки
Позднее Ctrl + ↑
Большая таблица на клиенте
Про нюансы работы со здоровенными таблицами на клиенте я уже пару раз писал (раз, два), но в конце прошлой недели коллеги подсказали ещё один любопытный способ оптимизации работы с относительно большими коллекциями данных на клиенте. Вкратце: стандарт советует работать с большими коллекциями только на сервере, однако если сразу обойти всю коллекцию на клиенте (например, в обработчике открытия формы) — она закешируется. В итоге последующие обходы, поиск строк и другая работа с данными не будут требовать дополнительных серверных вызовов. Суммарные расходы при таком подходе получаются ниже.
Погонял сейчас простые тесты — да, похоже, что так и есть. Метод выглядит отчасти костыльно и на серьезных нагрузках я его ещё не проверял — но, вероятно, его вполне можно иметь ввиду.
19 мая 2019 1С
Close to the Sun
Игрожуры и ютуберы соревнуются, кто задорнее обложит «Close to the Sun» в рецензиях, а мне игра неожиданно понравилась. Конечно, это не шедевр, но атмосфера огромного «летучего голландца» передана просто отлично. Происходящее местами напоминает Биошок, но я очень далек от того, чтобы валять в перьях любую историю, которая чем-то похожа на то, что я уже где-то видел.
А ещё во время финальных титров тут играет очень крутой сингл, который буквально продал мне игру пару дней назад и который можно слушать, к самой игре не прикасаясь.
14 мая 2019 видеоигры
Задержки PageLatch
Задержки вида PageIOLatch (я про них сегодня писал) легко встретить в любых системах, в том числе небольших. С задержками PageLatch дела обстоят наоборот — их трудно заметить, пока пользователей меньше нескольких тысяч.
Дело в том, что они возникают, когда страница находится в оперативной памяти и её пытаются изменить несколько пользователей. Пока один пользователь не завершит модификацию — прочие должны ждать. Время ожидания обычно ничтожно, однако если конкурирующих за страницу пользователей много — оно становится заметным.
Возможных причин две.
Горячее место в индексе
Такой расклад иногда называют хотспотом. Он возникает, когда мы массово пытаемся писать что-то на последнюю страницу индекса; в первую очередь речь идёт об индексе с монотонно возрастающим ключом — например, любые индексы по полям ссылочного типа (начиная с последних версий 8.2, платформа выдает последовательные GUID — это снижает фрагментацию диска и делает ключ индекса монотонно возрастающим).
Что такое «индекс с монотонно возрастающим ключом»? Для платформы 1С это, например, индексы регистров по периоду. Конечно, они растут не вполне монотонно — однако тут скорее важно, что данные мы всегда будем писать в конец. Сюда же относятся индексы по номеру документа и индексы по коду справочника — в обеих случаях мы выдаем некий новый номер, который пишется в конец индекса.
Решается проблема хотспота только архитектурно — нужно добиться того, чтобы данные записывались в разные места индекса, а не только в конец.
Системные страницы tempdb
Когда мы создаём или удаляем таблицу в базе данных, в ней обновляется ряд служебных страниц — IAM (Index Allocation Map), PFS (Page Free Space), GAM (Global Allocation Map), SGAM (Shared Global Allocation Map) и другие.
Почему это важно для tempdb? Для платформы 1С это база, в которой регулярно создается огромное количество таблиц. Служебные страницы при этом обновляются настолько часто, что при этом возникают задержки семейства PageLatch.
Вообще-то MS SQL Server умеет оптимизировать обновления системных страниц для tempdb, благодаря чему это делается реже, но иногда даже этого не достаточно. Особенно если в tempdb создаются таблицы с индексами — а это очень частый кейс для приложений на 1С.
У проблемы есть несколько возможных решений. Первый — если СУБД старше 2016-й, можно отключить смешанные экстенты через флаг трассировки 1118. При этом исчезнет необходимость сразу в двух служебных страницах — GAM и SGAM. Соответственно, ожиданий на их обновлении не будет. Экстент — это восемь страниц данных, т.е. 64 килобайта; если он содержит страницы одной таблицы — это нормированный экстент, если нескольких — смешанный.
Второй подход — разбивать tempdb (с 2016-й версии СУБД она, кстати, по умолчанию разбивается на восемь файлов). Дело в том, что служебные страницы ведутся в разрезе файлов; если их будет несколько — ожидания на обновлениях служебных страниц будут ниже.
Третий вариант — уменьшать количество временных таблиц с целью снизить нагрузку на tempdb. То есть переписывать наиболее частотные запросы так, чтобы они работали приемлемо быстро без временных таблиц.
Задержки PageIOLatch
Когда мы читаем страницу данных с диска в буферный кэш, она на очень короткое время блокируется (её нельзя писать, а в некоторых случаях — ещё и читать). Ожидание на завершении этого действия — и есть задержка PageIOLatch.
Причиной может быть вымывание буферного кэша — из-за небольшого объёма доступной ОЗУ или не оптимальных запросов, которые читают миллионы строк, а возвращают три. В итоге СУБД регулярно не находит нужных страниц в кэше и вынуждена снова и снова читать их с диска.
В общем, если в системе есть существенные задержки PageIOLatch — их скорее всего можно снизить, используя более быстрые диски, наращивая объём ОЗУ и оптимизируя запросы.
22 апреля 2019 MS SQL
Объектные блокировки
Во-первых, сразу, чтобы не путаться: объектные блокировки платформы никак не связаны с управляемыми блокировками и, тем более, блокировками СУБД. Во-вторых, различают два вида: пессимистические объектные блокировки и оптимистические.
Оба вида неплохо описаны на ИТС; ниже — просто краткая выжимка.
Пессимистические блокировки
Накладываются расширением формы, когда пользователь начинает редактировать объект — например, меняет значение поля. Если тот же объект попробует отредактировать в форме другой пользователь — форма, которую он открыл, тоже попробует наложить пессимистическую блокировку, не сможет этого сделать и пользователь получит ошибку «Не удалось заблокировать запись».
То есть платформа в данном случае делает своего рода пессимистичную оценку ситуации: мол, раз первый пользователь начал редактировать объект — скорее всего, он его запишет. Раз так, второму пользователю разрешать редактировать нельзя.
Пессимистическую блокировку также можно наложить методом объекта Заблокировать() и снять через метод Разблокировать(). Кроме того, можно воспользоваться методом глобального контекста ЗаблокироватьДанныеДляРедактирования() и методом управляемой формы ЗаблокироватьДанныеФормыДляРедактирования().
Оптимистические блокировки
Сперва немного теории: платформа хранит версии объектов ссылочного типа (справочников, документов и так далее). По сути это просто момент времени, в который объект был изменён последний раз. Когда объект считывается расширением формы или кодом — его версия считывается вместе с ним.
Так вот, в момент записи объекта платформа сверяет ту версию, что была получена при чтении объекта из базы данных и ту, что указана в базе данных в момент записи. Если версии различаются — возникает ошибка «Операция не может быть выполнена из-за несоответствия версии или отсутствия записи базы данных».
Это и есть так называемая «оптимистическая блокировка». Называют её так потому, что платформа тянет с проверкой до последнего — пока не произойдет реальной попытки записи.
Наложить оптимистическую блокировку объекта через код нельзя: версия объекта хранится в поле _Version таблицы данных объекта, заполнением которого занимается СУБД. Напрямую изменить это значение средствами платформы нельзя (можно, впрочем, записать объект — тогда его версия изменится).
22 апреля 2019 1С
Растолстевшие роли
Очень любопытная заметка про неприятности, которые можно нажить командой роли «Снять все права». Нет, никакого криминала, права-то она честно снимает — только вот её вызов может привести к тому, что объект роли на пустом месте сожрёт в несколько раз больше памяти, чем ему реально нужно.
Прочитал, задумался и пошел проверять, как обстоят дела с этим у нас в конфигурации. И что бы вы думали? Накопал десятка два ролей, доверху набитых «снятыми галочками». Выгрузка прав весила около трёхста мегабайт, а после оптимизации — усохла почти втрое.
Любопытно, как это до сих пор оставалось незамеченным. Скорее всего, из-за количества объектов — у нас сравнительно небольшая конфигурация. То есть проблемные роли потребляли не так много ресурсов, чтобы мы что-то заподозрили.
GRIS
Эту игру сравнительно сложно рекомендовать, но она и правда искусство в прямом смысле слова. Сходу даже не вспомню, когда в последний раз был заворожен настолько, что не мог оторваться.
То ли графикой, то ли музыкой, то ли тем, что авторы очень искренне передали то состояние, о котором хотели рассказать.
14 апреля 2019 видеоигры
Простая галочка
Пару недель назад мы добавили в справочник номенклатуры FirstBIT ERP параметр «Inactive». Задача была простой: если товар больше не нужен пользователю — он ставит галочку и тот исчезает отовсюду (из форм выбора, форм подбора остатков на складах и так далее).
Звучит несложно, правда? Техническая реализация тоже была простой — мы прошлись по всем объектам, в которых предполагается выбор номенклатуры, и добавили дополнительный параметр этого выбора.
Но пользователи начали жаловаться, что настройка не работает. Мы полезли разбираться и поняли, что забыли про историю ввода — этот механизм, как оказалось, параметры выбора просто игнорирует. То есть пользователи выбирали в инвойсе какой-то товар, потом делали его неактивным, возвращались и инвойс и… Снова видели в истории товар, который вроде только что отключили.
Мы принялись искать выход. Проблема в том, что история ввода хранится в системном хранилище и повлиять на неё программно нельзя. Можно разве что полностью удалить — но фактическая очистка истории происходит только при перезапуске клиента (и то через раз). Отключить историю вообще? Напоминает лечение простуды отсечением головы.
В какой-то момент мы наткнулись на информацию о том, что история ввода хранится не просто для конкретного поля, а ещё и в разрезе параметров выбора. То есть для каждого сочетания параметров выбора и их значений история выбора своя. Получается, если добавить некий дополнительный параметр выбора ко всем полям, где выбирается номенклатура — изменение значения этого параметра будет «чистить» историю (на самом деле, конечно, создавать новую — но пользователю-то какая разница).
В общем, мы создали такой параметр. Хранится в общем хранилище и транслируется в формы при их открытии через общий модуль, который программно добавляет параметр выбора. Если пользователь снимает или устанавливает флаг Inactive для любой номенклатуры — значение параметра меняется, а уже открытые формы получают его через механизм оповещений.
А ведь как все невинно начиналось, а?
Однако механизм неплохо работает, хотя его недостатки налицо: во-первых, системное хранилище будет постепенно пухнуть по мере появления всё новых и новых сочетаний реальных параметров выбора и нашего, фиктивного. Во-вторых, запись номенклатуры теперь потенциально узкое место: два пользователя не смогут одновременно записать номенклатуры, у которых изменены флаги Inactive (будет блокировка при записи нового значения нашего скрытого параметра выбора в общее хранилище).
Первую проблему можно решить очисткой хранилища по какому-то триггеру, вторую — записью нашего параметра выбора в разрезе пользователей (например, через регистр сведений). Впрочем, мы искренне надеемся, что 1С даст какой-то доступ к истории ввода до того, как нам придется городить дополнительные костыли к тем, что мы уже наворотили :-)
Дата запуска сервера MS SQL
Для чего может пригодиться дата запуска MS SQL Server? Например, мы разбираем задержки в работе СУБД и хотим определить, в течении какого периода времени наполнялась DMV-шка sys.dm_os_wait_stats. Её данные (как и любой другой DMV, впрочем) хранятся в оперативной памяти как раз с момента запуска СУБД.
Дату запуска сервера можно получить из DMV-шки sys.dm_os_sys_info:
SELECT sqlserver_start_time FROM sys.dm_os_sys_info
Есть и другой способ, связанный с tempdb. Эта база данных создаётся при запуске сервера, и дата её создания вполне может считаться датой запуска сервера. Значение можно вытащить из sys.databases:
SELECT create_date FROM sys.databases WHERE name = 'tempdb'
Нюанс: дату запуска сервера как точку начала сбора данных DMV-шек нужно рассматривать с осторожностью. Накопленная статистика могла быть очищена вручную — например, вот так:
DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR)
8 марта 2019 MS SQL
Задержки сервера MS SQL
Каждый раз, когда SQL-запрос может запуститься, но ожидает другого ресурса — он записывает сведения о причине задержки. Доступ к ним можно получить через представление sys.dm_os_wait_stats.
Для анализа представления можно использовать готовые скрипты:
- Скрипт из статьи Яна Стерка «Открытие скрытых данных для оптимизации производительности приложений», опубликованной в MSDN Magazine ещё в 2008-м году. Выводит список типов задержки, упорядоченный по времени — от самых частотных до наиболее редких.
- Cкрипт из статьи Пола Рэндала про анализ причин задержек в работе сервера MS SQL (на Хабре, кстати, есть перевод). Фильтрует задержки, которые возникают на сервере всегда и которые обычно можно игнорировать. Кроме того, к каждой задержке добавляется ссылка на страницу, где эта задержка детально описана (например, вот описание CXPACKET).
8 марта 2019 MS SQL
The Cursed Forest
Ничего не ждал от игры, но она, внезапно, удалась. Выглядит бодро; чувствуется внимание к деталям, да и в сумме — совсем неплохо. А ближе к концу — ещё и трогательно.
Садитесь играть по всем правилам — гасите свет, надевайте наушники и, конечно, выключайте душнилу.
6 марта 2019 видеоигры
Повторяющийся ключ
На Инфостарте вышла любопытная статья про ошибку СУБД «cannot insert duplicate key». Одна из возможных причин сбоя — использование уникального идентификатора объекта для сопоставления при обмене с другими информационными базами (то есть, без служебного регистра, сопоставляющего объекты базы данных с идентификаторами объектов внешней базы).
Такой жести, чтобы уникальные идентификаторы совпали в разных базах для одной и той же таблицы, у меня в практике пока не было. Однако от подхода «единого идентификатора» я отрекся окончательно после того, как делал обмен между FirstBIT ERP и GROTEM / Agent.
Архитектура пилотного решения была такая: данные из FirstBIT ERP выгружались сперва в служебную ИБ 1С, написанную разработчиками GROTEM'а, а уже оттуда — в основную базу данных сервера мобильных приложений.
Сопоставление было сделано очень просто — через идентификаторы объектов. Например, один и тот же документ в обеих базах имел один и тот же идентфикатор. Но были и более сложные схемы: например, контрагент FirstBIT ERP на стороне GROTEM'а превращался в три объекта: собственно контрагента, торговую точку и документ взаиморасчетов.
Да, в документ взаиморасчетов. И все три объекта имели один и тот же идентфикатор — идентификатор контрагента FirstBIT ERP. В общем, не то чтобы это было очень изящным решением, но для платформы такой расклад вполне адекватен и мы решили, что проблем быть не должно.
Однако обмен работать отказался.
Беглый анализ показал, что данные успешно выгружаются в промежуточную базу данных и проходят все возможные проверки на целостность и полноту, а не работает только финальный шаг — выгрузка в базу данных сервера мобильных приложений.
В итоге выяснилось, что:
- В этой самой базе есть таблица, где хранятся все интересующие нас объекты. Документы, элементы справочников, вот это всё.
- Эта таблица имеет уникальный индекс по GUID объекта.
В этом месте интрига закончилась. Ну да, первый же выгруженный контрагент создавал целых три сущности с одним и тем же GUID, которые прекрасно лежали в своих таблицах на стороне 1С, но не могли быть записаны в одну общую таблицу базы данных приложения.
Одиночество
Хороший ролик про одиночество, его причины и последствия (спойлер: все серьёзно, вплоть до биологического уровня). Рассказывают на английском, но если нужно — там есть русские субтитры.
(емкий твит о том же)
(а также я, походу, достаточно много наиграл в Starcraft II, чтобы название ролика у меня в голове произносилось исключительно голосом Зератула и под музыку из Alone)
1 марта 2019 тем временем видеоигры
Неудачная охота
При обновлении платформы до 8.13.12.1790 наткнулись на плавающий баг при переключении текущей страницы элемента на сервере. Конкретно на сервере оно срабатывает, но как только управление возвращается на клиент — текущей страницей становится та, что была до изменения. При этом ещё и происходит событие изменения текущей страницы (как будто пользователь сменил её вручную).
Неприятно, но, в общем, ничего страшного — запатчили баг, описали и зарепортили в 1С. Там пока думают. Но я, внезапно, даже как-то расстроен — когда расследуешь такие штуки, они всегда выглядят немного загадочно и кажется, что ты вот-вот найдешь что-то интересное! Например, новый аспект работы платформы или что-то вроде этого.
И вот, наконец, ты смотришь на проблему под правильным углом, а она — обычный жук. Возможно, полосатый или в крапинку, но жук как жук — сидит себе, шевелит усами. И ты такой: блин, это что — и есть моя добыча? :-)
Большие таблицы значений
Пока писал про поиск строк на клиенте, вспомнил ещё один нюанс, связанный с данными на управляемых формах — тоже про передачу между клиентом и сервером.
Допустим, нам по условиям задачи нужно оперировать сравнительно большими (тысячи строк) таблицами значений. Это могут быть данные, которые в общем случае не нужно показывать пользователю: например, параметры расчета цен, информация о продажах и закупках, что-то ещё. Логично создать такие таблицы в виде атрибутов формы — хотя бы потому, что можно использовать удобный конструктор.
Но когда платформа решит передать такую таблицу на сервер, начнутся проблемы. Дело в том, что передавать она будет не простой и компактный объект ТаблицаЗначений, а весьма насыщенный ДанныеФормыКоллекция. Разница колоссальна — на одном из наших проектов траты на такой обмен данными составляли порядка 40% от всего времени работы ключевой операции.
Решение достаточно очевидно — не использовать атрибуты формы вообще. Вместо этого создавать таблицы значений с помощью кода (например, в обработчике ПриСозданииНаСервере), закидывать их во временное хранилище, а в атрибутах формы хранить только адреса хранилища. Таким образом при необходимости поработать с таблицей на сервер отправляется только строка с адресом, и расходы на сериализацию и обмен данными начинают стремиться к нулю. На сервере извлекаем таблицу, читаем данные, изменяем их, если нужно, и сохраняем обратно во временное хранилище — по тому же адресу.
17 февраля 2019 1С
Эволюция
Говоря о развитии чего-либо (продукта, языка общения, языка программирования и так далее), люди иногда называют это «эволюцией». Мол, процессы похожи — неудачные решения точно так же постепенно вымываются, а удачные — остаются и усиляют сущность, к которой принадлежат. Так-то оно так, но я сейчас вспомнил не про позитивную сторону — фигня в том, что в ходе эволюции потомки, кроме полезных ништяков, получают все хреново выстроенные и зачастую бессмысленные механизмы, которые не смогли прикончить предка.
В итоге мы задерживаем дыхание, когда глотаем, путаемся между dissatisfied и unsatisfactory, а ещё программируем на чудесном языке JavaScript — который, при всём своем удобстве и распостраненности, вобрал в себя все мыслимые и немыслимые недостатки скриптовых языков.
Но это нам ещё везёт — есть, например, жирафы! У этих ребят гортанный нерв сначала спускается по шее на два метра вниз, а потом — поднимается обратно к мышцам гортани (ага, вместо того, чтобы пройти несколько сантиметров от мозга напрямую).
Зная всё это, мне чуть-чуть проще смотреть на то, во что иногда превращается мой код на очередном цикле разработки. Ну и что, что у этого хомячка отрос драконий хвост? Эволюционно, например, такой хвост очень полезен. Кто я такой, чтобы спорить с Дарвином?
16 февраля 2019 английский работа JavaScript
Найти строки на клиенте
Про то, что метод НайтиСтроки() для коллекций данных формы горазд под шумок наделать серверных вызовов, я узнал довольно давно, столкнувшись с неадекватно долгой прогрузкой формы (там он вызывался прямо в обработчике открытия, да ещё и в цикле). Так делать, конечно, не нужно, на что мягко намекает справка: calling the method executes a server call.
Однако сегодня писал код для задачи с похожей механикой и заметил, что серверных вызовов по факту нет. Удивился, полез на ИТС и, в общем, дело в следующем: при вызове НайтиСтроки() на клиенте поиск на нём и происходит, однако платформа не держит все данные больших коллекций на клиенте. Они передаются с сервера, и запрос этих данных с клиента — тот самый серверный вызов, на который ссылается справка.
Разработчики платформы расплывчато описывают объем коллекции, до которого клиенту не нужно обращаться к серверу (на ИТС пишут, что это порядка двадцати строк). Так что подход не меняется: делаем явный серверный вызов и не переживаем, что платформа налепит своих. Впрочем, всегда приятно лучше понимать, что внутри барабанчика :-)
12 февраля 2019 1С
Ай ду май бэст
Что здорово облегчает работу над английским языком — так это практика, которая буквально сама лезет в руки. Причем порой она полезнее каких-нибудь примеров с условного Cambridge Dictionary — просто потому, что лучше запоминается.
Например, вчера проходили на занятиях гипотетические формулировки и запоминали, как правильно понимать wish и if only; сегодня первое, что попалось в ленте — вот этот твит. Куда уж доходчивее :-)
Или, скажем, недавно освежали в памяти разницу между try и trying — а я её запомнил давным-давно по вымученному «I do my best» Фелисити из Borderlands: The Pre-Sequel. Ситуация там была довольно яркая, и этот оттенок невозможности выполнить задачу здорово въелся в память.
12 февраля 2019 английский видеоигры
Из чего сделаны наши девчонки?
На фоне скандальной рекламы «Reebok» (я в этой теме полный мимокрокодил, но взрывная волна даже до меня докатилась) на ютубе попался классный ролик их конкурентов. Ему уже пара лет, но он ровно про то же и, блин! Реклама здорового человека — не знаю, как лучше выразиться.
Показал мелкой — смотрела глазами по пять копеек. Покажу ещё раз, когда опять будет батониться вместо тренировки :-)
9 февраля 2019 семья тем временем
Внезапный барабанщик
Я пишу на платформе 1С довольно давно и повидал некоторое дерьмо, но такое, честно говоря, вижу первый раз.
Поначалу я даже не понял, с какого конца это жевать. Поискал перевод иероглифа — Википедия лишь сухо сообщила, что это такой тип чашки, а Google Translate вообще развёл руками.
Коллега заметил, что символ отдаленно похож на складского сортировщика — мол, перекладывает что-то там себе между коробками. Не знаю, не знаю. По-моему, больше напоминает барабанщика за работой. Но что, черт побери, это значит? Может быть, 1С пытается что-то мне сказать? Что-то про музыку?
Может, мне нужно было стать басистом, а не вот это всё.
9 февраля 2019 1С
Ранее Ctrl + ↓