Pull to refresh

Comments 45

Извиняюсь, не совсем понял комментарий, что не так?
Все так! Очень приятно, что разработка на PL/SQL не умерла!
От себя, я бы подумал использовать systimestamp вместо sysdate.
Штука полезная, благодаря ей в свое время благодаря этому находили странные ошибки
Все так! Очень приятно, что разработка на PL/SQL не умерла!

Думаете это хорошо? Я пол жизни хотел поработать с ораклом, поработал, не понравилось, в том числе изза огромного легаси которое по репозиторий даже не слышало. Классный способ выстрелить себе в ногу.

Кроме того видел очень много ораклистов, которые так заигрались в pl/sql что даже вещи которые можно сделать проще (например check) заворачивают тудаже.

А уж в какую прелесть превращается переиспользование кода, мммм… Функции вызываемые построчно, функции в условии, каскад процедур функий, ням ням.
Я долго занимался разработкой на pl/sql. Все выше это не проблемы языка. Можно все то же самое сказать про любой язык программирования. Это проблема организации процесса разработки.

И код пере использовали и делали апи для вызова заказчиками. Много чего было сделано и работает очень хорошо.
Сложность интеграции с гит это именно проблемы языка. Остальное да, культура разработки.
Ну как бы и Java не умеет работать с гитом :)
А так, нет там сложности с интеграцией.
PL/SQL Developer и Oracle SQL Developer умеют работать с git. В конце концов можно и файлы сохранять.
Для дистрибутива лучше использовать ликвид.
Не, норм все. Весь вопрос в процессе
Ну вот как раз яву в гит сунуть проблем нет, а вокруг SQL танцы с бубном.
Ликвид конечно вариант, но весьма костыльный.

Скажу по секрету что гитора взламывается на раз два(потому что анвраппер). И при этом можно активировать типа платные вип-фичи для понтового просмотрщика репозитория. Но только тсс))) И там еще и свои болячки тоже есть у него принципиальные, по которому оно для прода не готово вообще. Побаловаться в тестовых средах еще может быть...

Спасибо за подсказку, если честно даже и не знал, что есть такой готовый продукт, но этот продукт не рассматривал. Конечно можно внедрить уже готовый проект, я же хотел показать о способе создания «своего» лога на этапе построения БД. Более того, сейчас чуть внимательнее посмотрел на реализацию «log4plsql» и в принципе есть схожести. Но если я смогу дописать весь цикл статей, то наверное моё логирование буде содержать функционал которого нет в «log4plsql».

Используем plog для не очень важных/нагруженых мест и где не так сильно много логов надо писать.
Потому что логи в таблицу — это фу.

Используем plog для не очень важных/нагруженых мест и где не так сильно много логов

Я об этой ситуации написал в статье:
Также, часто в командах появляются разработчики, которые говорят: «Зачем логировать все процедуры (функции)? Давайте вести логирование только важных и нужных процедур (функций)!».

У всех разработчиков разное понимание «не очень важных/нагруженых мест» и если использовать такой критерий логирования, то тогда наверное такое логирование будет бесполезным.

Используем plog ...

Это все хорошо до тех пор, пока вы не попадете в крупную ИТ-компанию, например Сбер, ВТБ. На коммерческий продукт для логирования ошибок в Oracle в проекте никто никогда не закладывает бюджет, а использовать оперсоурсные по, библиотеки и прочее вам скорее всего не дадут «безопасники».

Потому что логи в таблицу — это фу

По своему опыту использования таблиц для логов, могу сказать, что в среднем в партицию за один квартал прилетает примерно 5-6 тыс строк. В период активного внедрения нового функционала кол-во ошибок возрастает, но со временем цифра усредняется. Зачем изобретать велосипед и если не в Oracle, то тогда где вести лог ошибок? В файлах на удаленном сервере? И ведь такой «лог» ошибок практически бесполезен, я не говорю о том, что рано или поздно файлы лога забьют свободное место на дисках. Контролировать наполнение таблицы лога вы всегда можете своими силами (силами отдела разработки или сопровождения), а вот файловый сервер это как правило зона ответственности администраторов. И по своему опыту в таком банке могу сказать, что такой лог ошибок часто «ронял» сервер.
partition by range (insertdate)

Это подразумевает не только использование Enterprise Edition, но ещё и «Extra cost option». Значит, без нарушения лицензионного соглашения, Ваш метод не подойдёт для других редакций.
В текущей статье способ партицирования выбран для примера. Я специально не стал подробно описывать способы индексирования т.к. в разных компаниях, в разных командах разработчиков свои взгляды. Большей проблемой при создании логирования в компании, является сам факт создания и поддержание логирования в актуальном состоянии (о чем я пытался довести в статье).
Мне кажется, об этом аспекте стоило упомянуть. А то скопипастят же не глядя.
Спасибо за совет. Добавил пояснение в статье.
Не хватает иерархиии (ссылки сообщения на родительское). В сложных системах когда вызов какого-то действия порождает сотни-тысячи записей в лог, при этом система сильно параллельна, то лог превращается в жуткую кашу, которую не разобрать. Иерархия позволяет вытаскивать только лог нужного вызова, а также удобно смотреть весь стек вызова.
Вы говорите про существенное усложнение модели логирования, которая сложна как разработке так и поддержании в актуальном состоянии. В статье приведен пример простейшего логирования и зачастую в большинстве компаниях вообще нет и такого логирования ошибок (событий). В дальнейших статьях постараюсь описать способ выявления «критичных» ошибок из общего пулла записей в таблице лога.
Нет особого усложнения, один раз пишется иерархия, а при использовании вообще почти никакой разницы. Зато можно с прома получать нормальные логи, которые позволяют разобраться.
Спасибо, наверное стоит подумать. Сначала попробую на своей базе реализовать и чуть позже опишу в дополнении к действующему функционалу.
А ротация записей? А обслуживание роста таблицы? А быстрый доступ к конкретным сообщениям, типам сообщений? Через 3-4 года работы (когда записей может стать десятки миллионов на нагруженной системе) этот вопрос станет ребром.
Это уже тонкости реализации. Обычно все гораздо проще: при наступлении нового квартала партиция с данными предыдущего квартала остается, все что старее удаляется таким образом мы храним в истории события предыдущего квартала. Локальные индексы внутри партиции можно создать, но это уже тема отдельного обсуждения. Повторюсь, что всё очень индивидуально в разных компаниях разные цели и задачи от функционала логирования событий.
За весь кровавый не скажу. Но обычно. Глубины около месяца хватает. Самое важное это после релиза. Плюс уровень логирования то же должен меняться динамически

Во первых, я бы рекомендовал захинтовать вставку при помощи APPEND NOLOGGING, чтоб ускорить и не генерить redo для этих данных. А ещё покурите асинхронный коммит, например https://www.orafaq.com/node/93. Я вижу у вас партиции, следовательно это решение предназначено для прода а не для разработки.


А не лучше ли в таком случае вместо хранения логов в базе слать их во внешнюю систему? Можно написать простейший Java source пакетик (или или готовый байткод загрузить в базу), и через него стримить наши логи в Apache Kafka или в любую другую шину? Оттуда направлять это куда душе угодно, в любую time-series database, или в старый добрый Prometheus, потом их можно будет видеть в системе мониторинга вкупе с остальными метриками и делать выводы, например в Grafana Loki. Заодно и retention policy для логов проще управлять. И можно алерты настроить чтоб по мейлу или прям в телегу получать важные оповещения.

Спасибо за комментарий. Все commit выполняются в автономной транзакции. Хинт «APPEND» можно использовать только (желательно) при insert в рамках одной сессии. Если выполнять insert из разных сессий с хинтом «APPEND», то текущий insert будет блокировать таблицу для других сессий.
И в целом, мне кажется вы чуть-чуть преувеличиваете важность таблицы логирования событий. Чтоб вы понимали, за весь вчерашний день 31.03.2021 в таблице лога на прод сервере у нас появилось 124 строчки (3 с типом Err и 121 с типом Msg).
По поводу хранения логов во внешней среде, то я уже видел подобные реализации. Во-первых, основная проблема это в дальнейшем использовании этих логов (неудобно анализировать их, искать конкретную ошибку и т.д.). Во-вторых, файлы периодически заполняют все свободное место.
В дальнейших статьях я постараюсь описать как мы настроили мониторинг событий в самом Oracle. Мы в текущей компании используем qlikview для визуализации событий.

Я вижу у вас партиции, следовательно это решение предназначено для прода а не для разработки.

Вот здесь если честно я не понял о чем речь. У нас на всех стендах от развернуто подобное логирование. На данный момент могу сказать, что им пользуются разработчики на dev-стенде, тестировщики на своем (прелайв) стенде и пользователи используют лог на прод-сервере.
Ничего не имею против предложенного решения.
Но текущая реальность несколько иная.

У нагруженных систем, логов обычно не 100 или 1000 в день, а от десятков миллионов.
В свете этого всякие ES, CH и прочие, не зря свой хлеб едят.
Традиционные БД итак, обычно, узкое место, еще и логи писать потоком.

При этом и выбор в plsql не очень большой. Либо в табличку писать, либо по сети через java pkg. Из табличек, понятно, все равно потом нужно данные переливать в ES, CH. Можно, конечно, работу с табличками оптимизировать — batch, partition, no index, multiple tables, etc и дожать до 4-10 тыс с сек на хорошем железе. Но если будет больше, придется съезжать на другой стек.
нагруженных систем, логов обычно не 100 или 1000 в день, а от десятков миллионов.

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

Обработка when others без raise внутри — отличный способ выстрелить себе в ногу. Вызванная процедура отработала вхолостую, вызывающая об этом ничего не знает и продолжает как ни в чем не бывало.
Архитекторы у вас боятся пустых полей, зато не боятся лишнего insert. Очень странные архитекторы.
А ещё rollback без savepoint, который откатывает весь dml, сделанный в вызывающей процедуре. Такая мина заложена, что когда она взорвется, уйдет не один час на то, чтобы понять, почему данные не сохраняются в базу.
Вероятность 50/50, что программист при добавлении параметра в процедуру забудет добавить его в строку p_paramvalue и в самый неподходящий момент выяснится, что его значение не залогировано, а оно-то как раз и необходимо.
Хороший набор антипаттернов для включения в каждую процедуру!

Приведенные примеры показывают как использовать логирование ошибок в момент возникновения самой ошибки (exception), то как будет вести алгоритм далее после передачи управления во внешний блок это уже совсем другая история. Описанные вами примеры использования raise и savepoint необходимо использовать в конкретных ситуациях, описание которых это отдельная тема для разговора.

Вероятность 50/50, что программист при добавлении параметра в процедуру забудет добавить его в строку p_paramvalue и в самый неподходящий момент выяснится, что его значение не залогировано, а оно-то как раз и необходимо.

Да, есть такая вероятность и даже сам с этим сталкивался. А какая может быть альтернатива? Пока что остается одно — вручную вносить список параметров в p_paramvalue.
Мое мнение такое, что when others then… raise и rollback to savepoint в обработчике ошибок общего назначения — это как раз должно использоваться всегда, а вот отказ от них возможен в редких частных случаях. Такая практика поможет избежать множества сложновылавливаемых ошибок. Приведенные примеры показывают, как, создавая вроде бы полезную фичу для борьбы с ошибками, фактически добавлять новые ошибки, которые будут «стрелять» при обработке исключительных ситуаций. Как маскировать реальные причины проблемы.

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

Список параметров каждой процедуры есть в системных view. Можно написать функцию проверки наличия всех параметров в тексте пакета/процедуры и запускать эту функцию по триггеру в момент компиляции пакета/процедуры на тестовом или разработческом стенде.
Тогда программист не сможет забыть указать параметр.
Мне понятна ваша позиция, но я с ней не согласен от слова совсем.
Мое мнение такое, что when others then… raise и rollback to savepoint в обработчике ошибок общего назначения — это как раз должно использоваться всегда, а вот отказ от них возможен в редких частных случаях.

За свою практику работы с Oracle я наблюдаю диаметрально противоположную картину — во многих компаниях используется концепция: «если транзакция падает с ошибкой (типа «when others then»), то данную транзакцию завершают полным откатом изменений т.е. либо алгоритм отрабатывает без ошибок и сохраняем результат, либо завершаем алгоритм и не сохраняем вообще ничего».

Такая практика поможет избежать множества сложновылавливаемых ошибок.

Я бы сказал, что все наоборот. Такая практика только усложняет понимание корректности полученных данных. Поясню на примере, у вас есть функция которая рассчитывает процент по кредиту для клиента. Внутри этой функции вызовы множества процедур, которые рассчитывают различные атрибуты, параметры которые необходимы при расчете итогового процента по кредиту. Но вот в одной из процедур возникла неизвестная «when others then» ошибка и что делать тогда? В нашем случае мы уроним весь расчет и залогируем ошибку с параметрами запуска процедуры в которой произошла ошибка. После того, как исправят данную ошибку, то можно будет перезапустить расчет процента для указанного клиента. В вашем же случае, произойдет откат до сохраненной точки, а дальше что? Вы продолжите рассчитывать остальные атрибуты и на основании их посчитаете процент по кредиту? Т.е. у вас есть некий итог работы функции, но вот как понять что он корректный?

Это я все к тому, что способы написания кода бывают разные, у вас свои взгляды и нас свои. Вопросы rollback, raise и savepoint я в статьях не затрагиваю т.к. смысл статей не в этом. Еще раз раз повторюсь, я лишь показываю как можно отловить ошибку с её параметрами в момент её возникновения. Что вы (либо другой читатель) будете делать после возникновения ошибки это дело непосредственно ваше.

Список параметров каждой процедуры есть в системных view. Можно написать функцию проверки наличия всех параметров в тексте пакета/процедуры и запускать эту функцию по триггеру в момент компиляции пакета/процедуры на тестовом или разработческом стенде.

Если я так сделаю на каком-либо проекте, то меня будут вспоминать проклиная и ненавидя… На самом деле это не такая частая проблема, да иногда забывают и ничего не мешает добавить позже, все решается обычным кодревью.
«если транзакция падает с ошибкой (типа «when others then»), то данную транзакцию завершают полным откатом изменений т.е. либо алгоритм отрабатывает без ошибок и сохраняем результат, либо завершаем алгоритм и не сохраняем вообще ничего».

Так как раз ваш предлагаемый подход в КАЖДОЙ процедуре глушить все иск.ситуации и делать rollback противоречит этой здравой концепции.
Предположим у нас есть управляющая процедура Main (в ней одна транзакция, в конце процедуры commit), которая вызывает по очереди три процедуры, которые делают какие-то этапы общей транзакции, выполняют dml. Назовем их Step1, Step2, Step3. Если в Step2 произойдет exception, то произойдет откат dml, выполненных в Step1 и Step2. Далее, поскольку raise не сделан, управление перейдет в Step3, и затем общий commit в Main. В итоге мы получили в базе не «все или ничего», а только dml из Step3, что при разборе крайне загадочно и поставит разработчика в тупик.
Если же использовать rollback to savepoint Step2 и raise, управляющая процедура получит информацию, что Step2 не выполнен и сможет далее принять решение, можно ли переходить к Step3 или нужно аварийно завершиться без commit. Если в Main программист не предусмотрел блок обработки ошибок, то после exception в Step2 Main завершится аварийно без commit и мы получим «все или ничего».
Не надо судить о всей статье, о всей предложенной концепции, об архитектуре БД и архитекторе только по нескольким примерам данной статьи. Это только пример, делайте как хотите. Еще раз говорю, это примеры написанные «на коленке». То, как будет вести себя алгоритм до и после ошибки это уже отдельная история.
Я не сужу о всей статье, и подход в целом здравый. Я критикую конкретный код, потому что вся суть предложенной в статье концепции логирования всех исключительных ситуаций — это обработчик этих ситуаций в каждой процедуре и логирование в нем.
Куда логировать, в таблицы или во внешний инструмент, как потом мониторить, это уже детали. Суть в правиле — каждый exception должен логироваться в той процедуре, в которой он произошел. И типовой обработчик — это не просто пример, а важнейшая часть этой концепции, которую нужно один раз написать как шаблон, а потом постоянно вставлять в код всем разработчикам. Ошибки в этом шаблоне, раскопированные по всему коду, могут просто похоронить проект.
На самом деле шаблон такой процедуры есть (часть 2), но что-то мне подсказывает, что он вам не понравится.
Также, прошу обратить внимание на выходные параметры процедуры-шаблона
p_errcode out number
p_errtext out varchar2
обычно на них идет условие дальнейшего выполнения алгоритма, либо его завершения, но в примерах текущей статьи я их не использовал.
Если имеется ввиду код под «спойлером» «Исходный код демонстрационной процедуры, то там уже лучше, так как в блоке when others нет rollback.
Но все равно недостаточно хорошо, потому что там не инициализируются p_errcode и p_errtext и при этом нет raise. Таким образом вызывающая процедура не знает о том, что в данной процедуре произошла непредвиденная ситуация. Что приведет к сложновылавливаемым ошибкам.
Ну и в принципе под when others подпадает и ошибка отсутствия нужной партиции для вставки записи, и ora-600 и много других непредвиденных системных сбоев, которые должны, просто обязаны останавливать исполнение всей последовательности вызовов и „орать“ в мониторинг о срочном вмешательстве поддержки. Поэтому в when others должен быть raise.
Спасибо за комментарий! Добавил raise в исключение в блоке when others и присвоил значения исходящим параметрам
p_errcode и p_errtext.
Если я так сделаю на каком-либо проекте, то меня будут вспоминать проклиная и ненавидя…
Почему? Например, компиляцию нельзя выполнить, если в коде есть синтаксическая ошибка. Это же не расстраивает программиста, а наоборот, помогает.
Если у вас на проекте имеются требования к оформлению кода, то автоматический контроль соблюдения этих требований упростит жизнь программистам. За это они спасибо скажут. Главное делать этот контроль оптимально, чтобы только реальные проблемы не допускал, а не вставлял палки в колеса.

На самом деле это не такая частая проблема, да иногда забывают и ничего не мешает добавить позже, все решается обычным кодревью.

Я часто делаю код ревью. При этом предлагаете пересчитывать на глаз количество параметров в заголовке процедуры и в ее хвосте, которые могут быть за сотни строк друг от друга? А параметров может быть штук 10. Или так ПО КАЖДОЙ процедуре? И на это будет тратить свое время самый ценный трудовой ресурс команды (обычно ревью делает тимлид)?
Почему? Например, компиляцию нельзя выполнить, если в коде есть синтаксическая ошибка. Это же не расстраивает программиста, а наоборот, помогает.

Мой опыт работы в команде разработчиков говорит мне только о том, что большинство разработчиков (к сожалению) только пишет код, не думая о том как с этим кодом будут работать другие в будущем. Заставлять людей делать что-то «принудительно» не работает, либо работает до тех пор пока есть кому требовать и контролировать выполнение требований. Да, наверное так можно сделать, но я такой метод использовать не стал бы.

Я часто делаю код ревью. При этом предлагаете пересчитывать на глаз количество параметров в заголовке процедуры и в ее хвосте, которые могут быть за сотни строк друг от друга? А параметров может быть штук 10. Или так ПО КАЖДОЙ процедуре?

Да, в каждой процедуре вручную вставлять параметр и это удобно делать когда у вас есть контроль версий (Tortoise, git и прочее). Со временем уже на автомате видишь новый параметр и ниже смотришь его в блоке исключений.
Если вы никогда не использовали данный метод это не значит, что он не работоспособный.

Мы используем Logger, который рекомендовал Том Кайт. Единственное что, мы секционировали таблицу по дням, и чистим с помощью дропа партиций, а не с помощью удаления строк. То есть мы сделали изменения относительно оригинала, но они минимальные.

Из плюсов:

  • эта штука поддерживает переключение, например, с уровня info на уровень debug на лету;

  • при использовании log_error в эксепшен-блоке, в лог автоматом вставится стек трейс, то есть нам достаточно только словами описать, что у нас не получилось сделать, а остальное за нас сделает логгер;

  • если мы в начале процедуры положили входные параметры в специальную коллекцию, то в случае log_error мы их тоже увидим.

Но главный плюс, конечно, в том, что мы не стали тратить время на написание собственного логгера на pl/sql.

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

А почему обязательно все? Почему не ловить exception только в процедурах верхнего уровня, которые вызываются пользовательской сессии или из джоба?
Ну или хотя бы только в процедурах, которые есть в заголовке пакета, и потенциально могут быть вызваны пользовательской сессии или из джоба?
Есть такая поговорка: «Если правило не обязательно к исполнению, то оно не будет работает!». Примерно также можно сказать и к логированию. Если вы логируете не все, а только «в нужных местах», «в важных процедурах» или «в процедурах верхнего уровня», то в момент возникновения очень и очень критичной ошибки вы можете обнаружить, что у вас есть логирование ошибок но нет информации о конкретной ошибке (которая вам так необходима) в таблице лога. И как правило после таких инцидентов весь функционал логирования умирает.
Моя позиция такая, что проще вставить кусок кода в блок exception в момент написания процедуры один раз и навсегда, нежели его не писать или рассуждать о том нужен он или нет. И суть данного метода логирования ошибок состоит в том, чтобы отловить максимально те параметры на которых возникла ошибка, чтобы в дальнейшем использовать их для воспроизведения ошибки на тестовом стенде и быстрого исправления. Но тут уже выбор каждой команды как им вести или вообще не вести логирование ошибок.
Sign up to leave a comment.

Articles