Приходилось ли вам задумываться как поменять время внутри базы данных? Легко? Ну в некоторых случаях да, несложно — linux команда date и дело в шляпе. А если нужно поменять время только внутри одного экземпляра бд если их на сервере несколько? А для отдельно взятого процесса базы данных? А? Эээ, так-то, дружок, в этом-то все и дело. Кто-то скажет, что это очередной сюр, не связанный с реальностью, который периодически выкладывают на Хабре. Но нет, задача вполне реальная и продиктована производственной необходимостью — тестированием кода. Хотя соглашусь, кейс тестирования может быть достаточно экзотический — проверить как ведет себя код на определенную дату в будущем. В данной статье я рассмотрю подробно как эта задача решалась, и заодно захватим немного сам процесс организации тестовых и dev-стендов для базы Oracle. Впереди длинное чтиво, устраивайтесь поудобнее и прошу под кат.
Предпосылки
Начнем с небольшого вступления для того, чтобы показать зачем это нужно. Как уже было анонсировано, мы пишем тесты при реализации правок в базе данных. Система, под которую эти тесты делаются, разрабатывалась в начале(а может и слегка до начала) нулевых, поэтому вся бизнес-логика находится внутри базы данных и написана в виде хранимых процедур на языке pl/sql. И да, это приносит нам боль и страдания. Но это legacy, и с этим приходится жить. В коде и табличной модели есть возможность задавать то, как параметры внутри системы эволюционируют с течением времени, проще говоря задавать активность с какой даты и по какую дату они могут применяться. Чего далеко ходить — недавнее изменение ставки НДС яркий тому пример. И чтобы такие изменения в системе можно было проверить заранее, базу данных с такими правками нужно перенести на определенную дату в будущее, кода параметры в таблицах станут активными на «текущий момент». И ввиду специфики поддерживаемой системы нельзя использовать мок-тесты, которые бы просто изменили возвращаемое значение текущей системной даты в языке при запуске сессии тестирования.
Итак, мы определили зачем, дальше нужно определить то как именно достигается цель. Для этого я сделаю небольшую ретроспективу вариантов построения тестовых стендов для разработчиков и то как в каждом варианте происходил запуск сессии тестирования.
Каменный век
Давно-давно, когда деревья были маленькими, а мейнфреймы большими, был только 1 сервер для разработки и он же проведения тестов. И в принципе всем этого хватало(640К хватит всем!)
Минусы: для проведения задачи смены времени нужно было привлекать много смежных отделов — системных администраторов(делали смену времени на сервере субд от root), администраторов СУБД(делали перезапуск базы данных), программистов(нужно было уведомить что произойдет смена времени, потому что часть кода переставала работать, например, веб-токены, ранее выданные для вызова api методов, переставали быть валидными и это могло стать неожиданностью), тестировщиков(само проведение тестирования)… При возврате времени в настоящее все повторялось в обратном порядке.
Средние века
Со временем число разработчиков в отделе росло и в какой-то момент 1 сервера хватать перестало. В основном из-за того, что разные разработчики хотят менять один и тот же pl/sql пакет и проводить для него тестирование(даже и без смены времени). Все чаще были слышны возмущения: «Доколе! Хватит это терпеть! Фабрики — рабочим, землю — крестьянам! Каждому программисту — по базе данных!» Однако, если у вас продуктовая база данных несколько терабайт, а разработчиков 50-100, то положа руку на сердце прямо в таком виде требование не очень-то реальное. А еще все хотят чтобы тестовая и dev-база не очень сильно отставали от прода и по структуре, и самим данным внутри таблиц. Так появился отдельный сервер для тестирования, назовем его pre-production. Строился он из 2х одинаковых серверов, куда с прода делалось восстановление базы данных из RMAN бакапов и занимало это примерно 2-2.5 дня. После восстановления в базе делалось обезличивание персональных и других важных данных и на данный сервер подавалась нагрузка с тестовых приложений(а также и сами программисты всегда работали с недавно восстановленным сервером). Обеспечение работы с нужным сервером происходило с помощью кластерного ip-ресурса, поддерживаемого через corosync(pacemaker). В то время как все работают с активным сервером, на 2 ноде запускается снова восстановление базы данных и через 2-3 дня они опять меняются местами.
Из очевидных минусов: нужно 2 сервера и в 2 раза больше ресурсов(в основном дисковых), чем прод.
Плюсы: операция смены времени и проведение тестов — может проводиться на 2м сервере, на основном сервере в это время живут разработчики и занимаются своими делами. Смена серверов происходит только когда база готова, и даунтайм тестовой среды минимален.
Эпоха научно-технического прогресса
При переходе на базу данных 11g Release 2 мы вычитали про интересную технологию, которую предоставляет Оракл под названием CloneDB. Суть заключается в том, что бакапы продуктовой базы данных(там прямо битовая копия продуктовых датафайлов) складируются на специальный сервер, который потом через DNFS(direct NFS) публикует этот набор датафайлов на в принципе любое число серверов, при этом на сервере не требуется иметь тот же объем дисков, потому что реализуется подход Copy-On-Write: база использует для чтения данных в таблицах сетевую шару с датафайлами с сервера бакапов, а изменения пишутся в локальные датафайлы на самом dev-сервере. Периодически для сервера делается «обнуление сроков», чтобы локальные датафайлы не очень сильно росли и место не кончалось. При обновлении сервера также делается деперсонализация данных в таблицах, при этом все обновления таблиц попадают в локальные датафайлы и те таблицы читаются с локального сервера, все остальные таблицы читаются по сети.
Минусы: серверов все так же 2(для обеспечения плавности обновления с минимальным простоем для потребителей), но зато теперь объем дисков сильно сокращается. Для хранения бакапов на nfs-шаре нужно еще 1 сервер по размеру +- как прод, но сокращается само время выполнения обновлений(особенно при использовании инкрементальных бакапов). Работа по сети с nfs-шарой заметно замедляет IO-операции чтения. Для использования технологии CloneDB база должна быть редакции Enterprise Edition, в нашем случае нам нужно было проводить на тестовых базах всякий раз процедуру upgrade. К счастью, тестовые базы попадают в исключения по лицензионной политике Oracle.
Плюсы: операция восстановления базы из бакапа занимает меньше 1 дня(точное время уже не вспомню).
Смена времени: без особых изменений. Хотя к этому времени уже были сделаны скрипты для изменения времени на сервере и рестарта базы данных, чтобы выполнять это не привлекая
Эра Новой истории
Для того чтобы еще больше экономить дискове пространство и сделать чтения данных не по сети мы решили реализовать свой вариант CloneDB(с флешбеком и снапшотами), используя файловую систему со сжатием. Во время проведения предварительных тестов выбор пал на ZFS, хотя официальной поддержки для неё нет в ядре Linux(цитата из статьи). Для сравнения смотрели еще BTRFS(b-tree fs), которую продвигает сам Оракл, но коэффициент сжатия был меньше при сравнительно одинаковом же потреблении CPU и RAM в тестах. Для включения поддержки ZFS на RHEL5 было собрано свое ядро на основе UEK(unbreakable enterprise kernel), а на более новых осях и ядрах можно просто использовать уже готовое UEK ядро. В основе реализации такой тестовой базы тоже лежит механизм COW, но уже на уровне снапшотов файловой системы. На сервер подается 2 дисковых устройства, на одном делается zfs pool, куда через RMAN делается дополнительный standby базы данных с прода, и поскольку мы используем сжатие, то раздел занимает меньше, чем продакшн.
На второе дисковое устройство ставится система и остальное что нужно для работы сервера и самой базы, например разделы для undo и temp. В любой момент времени из zfs пула можно сделать снапшот, который потом открывается как отдельная база данных. Создание снапшота занимает пару секунд. It's magic! И таких баз данных можно наклонировать в принципе достаточно много, лишь бы на сервере хватало RAM под все экземпляры и самого размера zfs pool(под хранение изменений в датафайлах при деперсонализации и при жизненном цикле клона базы данных). Основное время обновления тестовой базы начинает занимать операция деперсонализации данных, но и она укладывается в 15-20 минут. Налицо значительное ускорение.
Минусы: на сервере нельзя поменять время просто переводом системного времени, потому что тогда сразу все экземпляры базы данных, работающие на этом сервере, попадут в это время. Решение для этой проблемы было найдено и будет описано в соответствующим разделе. Забегая вперед скажу, что оно позволяет менять время внутри только 1 экземпляра базы данных(подход смены времени per instance) при этом не затрагивая остальные на этом же сервере. И время на самом сервере тоже не меняется. Так отпадает необходимость в рутовом скрипте для смены времени на сервере. Также на этом этапе реализована автоматизация смены времени для экземпляров через Jenkins CI и пользователям(условно говоря команды разработчиков), которые владеют своим стендом, даны права на джобы, через которые они могут сами менять время, обновлять стенд до актуального состояния с прода, делать снапшоты и восстановление(откат) базы до ранее созданного снапшота.
Эра Новейшей истории
С приходом Oracle 12c появилась новая технология — pluggable databases и как следствие контейнерные базы данных(cdb). С этой технологией внутри 1 физического экземпляра можно сделать несколько «виртуальных» баз, которые разделяют общую область памяти экземпляра. Плюсы: можно сэкономить память для сервера(и повысить общую производительность нашей базы данных, потому что всю память, которую занимали до того например 5 разных экземпляров, можно отдать в общее пользование для всех развернутых pdb-контейнеров внутри cdb, и они будут ее использовать только когда им это реально понадобится, а не так как было на предыдущей фазе, когда каждый экземпляр «блокировал» выданную ему память под себя и при низкой активности какого-то из клонов память не использовалась эффективно, проще говоря простаивала). Датафайлы разных pdb все также лежат в zfs pool и при разворачивании клонов используют все тот же мех-м zfs-снапшотов. На этом этапе мы приблизились достаточно близко к возможности давать почти каждому разработчику свою базу данных. Смена времени на этом этапе не требует перезапуска базы данных и работает совсем точено только для тех процессов, которым смена времени необходима, все остальные пользователи, работающие с этой бд никак не затрагиваются.
Минус: нельзя использовать подход со сменой времени per instance из предыдущей фазы, потому что инстанс у нас сейчас один. Однако, решение и для этого случая было найдено. И именно оно и послужило толчком для написания это статьи. Забегая вперед, скажу, что оно представляет собой подход смены времени per process т.е. в каждом процессе базы данных можно установить вообще свое уникальное время.
Типовая сессия тестирования в этом случае сразу после подключения к бд задает в начале своей работы нужное время, проводит тесты и в конце возвращает время обратно. Возврат времени нужен по одной простой причине: часть процессов базы Оракл не завершаются при дисконнекте клиента базы данных от сервера, это серверные процессы под названием shared servers, которые в отличие от dedicated процессов, запускаются при старте сервера базы данных и живут практически бесконечно(в идеальной картине мира). Если оставить время измененным в таком серверном процессе, то потом другое подключение, которое в этом процессе будет обслуживаться, будет получать неправильное время.
В нашей системе shared servers используются много, т.к. до 11g адекватного решения для нашей системы выдерживать highload нагрузку практически не существовало(в 11g появился DRCP — database resident connection pooling). И вот почему — в субд есть лимит на общее число серверных процессов, которое она может создать как в dedicated, так и в shared режиме. Dedicated процессы порождаются медленнее, чем база может выдать уже готовый shared процесс из пула разделяемых процессов, а значит при постоянном поступлении новых подключений(особенно если процесс делает еще какие-то медленные операции) общее число процессов будет расти. При достижении лимита сессий/процессов база данных перестает обслуживать новые соединения и наступает коллапс. Переход на использование пула разделяемых процессов позволил уменьшить число возникающих новых на сервере процессов при подключениях.
На этом обзор технологий построения тестовых баз закончен и мы можем приступать наконец к реализации самих алгоритмов смены времени для базы данных.
Подход «fake per instance»
Как поменять время внутри базы данных?
Первое что приходило в голову — создать в схеме, которая содержит весь код бизнес логики, свою функцию, которая перекрывает функции языка, работающие с временем(sysdate, current_date и т.д.) и при определенных условиях начинает давать другие значения, например можно было задавать значения через контекст сессии в начале запуска тестов. Не вышло, встроенные функции языка не перекрыть пользовательскими.
Затем были проверены системы легкой виртуализации (Vserver, OpenVZ) и контейнеризации через docker. Тоже не работает, они используют то же самое ядро что и хост-система, а значит используют те же значения системного таймера. Снова отпадает.
И тут нам на помощь приходит не побоюсь этого слова гениальное изобретение мира Linux — переопределение/перехват функций на этапе динамической загрузки shared objects. Многим оно известно как трюки с LD_PRELOAD. В переменной окружения LD_PRELOAD можно указать библиотеку, которая загрузится раньше всех остальных, которые нужны процессу, и если в этой библиотеке есть символы с тем же именем как например в стандартной libc, которая загрузится позднее, то таблица импорта символов для приложения будет выглядеть как если функцию предоставляет наш подменный модуль. И именно это и делает библиотека проекта libfaketime, которую мы стали использовать для того, чтобы запустить базу данных в другом времени отдельно от системного. Библиотека пропускает через себя вызовы, которые касаются работы с системным таймером и получения системного времени и даты. Управлять тем, на сколько сместится время относительно текущей даты сервера или от какого момента времени время должно пойти внутри процесса — все управляется переменными окружения, которые нужно задать вместе с LD_PRELOAD. Для реализации смены времени у нас был реализован job на сервере Jenkins, который заходит на сервер базы данных и перезапускает СУБД либо с установленными переменными окружения для libfaketime либо без них.
Примерный алгоритм запуска базы данных с подменным временем:
export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so
export FAKETIME="+1d"
export FAKETIME_NO_CACHE=1
$ORACLE_HOME/bin/sqlplus @/home/oracle/scripts/restart_db.sql
И если выдумаете, что все сразу заработало, то глубоко заблуждаетесь. Потому что оракл, как оказалось, валидирует те библиотеки, которые загружаются внутрь процесса при старте СУБД. И в алертлоге начинает возмущаться о замеченном подлоге, при этом база не стартует. Сейчас уже точно не помню как избавиться, есть какой-то параметр, которым можно отключить таки выполнение sanity-проверки при старте.
Подход «fake per process»
Общая идея с изменением времени только внутри 1 процесса оставалась той же — использовать libfaketime. Мы запускаем базу данных с предзагруженой в нее библиотекой, но ставим нулевое смещение времени при запуске, которое пропагандируется потом во все процессы СУБД. А потом внутри тестовой сессии выполнить установление переменной окружения только для этого процесса. Пфф, делов-то.
Однако, для тех кто близко знаком с pl/sql языком сразу понятна вся обреченность этой идеи. Потому что язык сильно ограничен и в основном годится для задач высокоуровневых. Никакого системного программирования там не реализовать. Хотя некоторые низкоуровневые операции(например работа с сетью, работа с файлами) присутствуют в виде предустановленых системных dbms/utl-пакетов. За все время работы с Ораклом я несколько раз делал реверс-инжиниринг предустановленных пакетов, код некоторых из них скрыт от глаз посторонних(они называются wrapped). Если тебе чего-то запрещают смотреть, то соблазн узнать как оно устроено внутри только возрастает. Но часто даже после анвраппера не всегда есть что посмотреть, потому что функции таких пакетов реализуются как c interface к so-библиотекам на диске.
Итого, мы подошли к 1 кандидату на реализацию — технология c external procedures.
Оформленная специальным образом библиотека может экспортировать методы, которые потом база оракл может вызывать через pl/sql. Кажется многообещающим. Всего один раз я встречался с этим на курсах по Advanced plsql, поэтому помнил очень отдаленно как это готовить. И это значит нужно читать документацию оракла. Прочитал — и сразу приуныл. Потому что загрузка такой пользовательской so-библиотеки идет в отдельном процессе-агенте через листенер базы данных, и общение с этим агентом идет через дблинк. А значит плакала наша идея установить переменную окружения внутри самого процесса базы данных. И сделано это все из соображений безопасности.
Картинка из документации, которая показывает как это работает:
Тип библиотеки so/dll — не так важно, но картинка почему-то только под винду.
Возможно кто-то заметил тут еще 1 потенциальную возможность. Да-да, это Java. Оракл позволяет писать код хранимых процедур не только на plsql, но и на java, которые тем не менее экспортируются все равно как plsql методы. Периодически я делал такое, поэтому с этим проблем возникнуть не должно. Но тут был спрятан очередной подводный камень. Java работает с копией энвайромента, и позволяет только получать переменные окружения какие были у JVM процесса при старте. Встроенная JVM наследует переменные окружения процесса базы данных, но на этом все. Я видел в интернете советы как поменять readonly map через reflection, но что толку, ведь это все равно только копия. То есть баба осталась опять у разбитого корыта.
Однако Java это не только ценных мех. С ее помощью можно порождать процессы изнутри процесса базы данных. Хотя все небезопаные операции нужно разрешать отдельно через механизм java grants, которые делаются с помощью пакета dbms_java. Изнутри plsql кода можно получить process pid текущего серверного процесса, в котором код выполняется, с помощью системных представлений v$session и v$process. Дальше мы можем породив какой-то дочерний процесс от нашей сессии что-то с этим pid сделать. Для начала я вывел просто все переменные окружения, которые есть внутри процесса базы данных(для проверки гипотезы)
#!/bin/sh
pid=$1
awk 'BEGIN {RS="\0"; ORS="\n"} $0' "/proc/$pid/environ"
Ну вывел, а дальше-то что. Поменять переменные в файле environ все равно нельзя, это данные которые были переданы в процесс при его старте и они read only.
Я поискал решение в Интернете на stackoverflow «как поменять переменную окружения в другом процессе». Большая часть ответов была что это невозможно, но был один ответ, который описывал эту возможность как нестандартный и грязный хак. И этим ответом был
Вот так реализуется установление переменной окружения в чужом процессе(вызывать нужно от root из-за используемого сискола ptrace):
#!/bin/sh
pid=$1
env_name=$2
env_val="$3"
out=`gdb -q -batch -ex "attach $pid" -ex 'call (int) setenv("'$env_name'", "'"$env_val"'", 1)' -ex "detach" 2>&1`
Также и чтобы посмотреть переменные окружения внутри процесса gdb тоже годится. Как было уже сказано ранее файл environ из /proc/pid/ показывает только переменные, которые существовали на старте процесса. А если процесс создал что-то в ходе своей работы, то это увидеть можно только через отладчик:
#!/bin/sh
pid=$1
var_name=$2
var_value=`gdb -q -batch -ex "attach $pid" -ex 'call (char*) getenv("'$var_name'")' -ex 'detach' | egrep '^\$1 ='`
if [ "$var_value" == '$1 = 0x0' ]
then
# variable empty or does not exist
echo -n
else
# gdb returns $1 = hex_value "string value"
var_hex=`echo "$var_value" | awk '{print $3}'`
var_value=`echo "$var_value" | sed -r -e 's/^\$1 = '$var_hex' //;s/^"//;s/"$//'`
echo -n "$var_value"
fi
Итак, решение уже у нас в кармане — через яву мы спауним процесс отладчика, который заходит на породивший его процесс и ставит ему нужную переменную окружения и потом завершается(мавр сделал свое дело — мавр может уйти). Но было ощущение, что оно какое-то костыльное. Хотелось чего-то более элегантного. Как-то бы все-таки заставить сам процесс базы данных устанавливать себе переменные окружения без внешнего рукоприкладства.
Яйцо в утке, утка в зайце...
И тут на помощь нам приходит кто, да, вы правильно догадались, снова Java, а именно JNI(java native interface). JNI подзволяет вызывать native методы, написаные на си, внутри JVM. Код оформляется специальным образом в виде shared object библиотеки, которую потом загружает JVM, при этом методы, которые были в библиотеке, мапятся на методы java внутри класса, объявленные с модификатором native.
Ну ок, пишем класс(на самом деле это только заготовка):
public class Posix {
private static native int setenv(String key, String value, boolean overwrite);
private static native String getenv(String key);
public static void stub()
{
}
}
После этого компилируем его и получаем сгенерированый h-файл будущей библиотеки:
# компилируем исходник
javac Posix.java
# создаст файл Posix.h в котором описаны декларации нативных методов для JNI
javah Posix
Получив заголовочный файл напишем тело для каждого метода:
#include <stdlib.h>
#include "Posix.h"
JNIEXPORT jint JNICALL Java_Posix_setenv(JNIEnv *env, jclass cls, jstring key, jstring value, jboolean overwrite)
{
char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);
int err = setenv(k, v, overwrite);
(*env)->ReleaseStringUTFChars(env, key, k);
(*env)->ReleaseStringUTFChars(env, value, v);
return err;
}
JNIEXPORT jstring JNICALL Java_Posix_getenv(JNIEnv *env, jclass cls, jstring key)
{
char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
char* v = getenv(k);
return (*env)->NewStringUTF(env, v);
}
и скомпилируем библиотеку
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC Posix.c -shared -o libPosix.so -Wl,-soname -Wl,--no-whole-archive
strip libPosix.so
Для того чтобы Java могла загрузить native-библиотеку, она должна быть найдена системным ld по всем правилам Linux. Дополнительно у Java есть набор property, которые содержат пути, где поиск библиотек происходит. Для работы внутри оракл проще всего положить нашу библиотеку в $ORACLE_HOME/lib.
И уже после того, как мы создали библиотеку, нужно скомпилировать класс внутри базы данных и опубликовать его как plsql пакет. Для создания ява классов внутри базы данных есть 2 варианта:
- загрузить бинарный class-файл через утилиту loadjava
- скомпилировать код класса из исходников с помощью sqlplus
Мы воспользуемся вторым способом, хотя они в принципе равноправны. Для первого случая нужно было сразу написать весь код класса на 1 этапе, когда мы получали класс-заглушку для h-файла.
Для создания ява класса в субд используется специальный синтаксис:
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "Posix" AS
...
...
/
Когда класс создан, его нужно опубликовать как plsql методы, и тут опять специальный синтаксис:
procedure set_env(var_name varchar2, var_value varchar2)
is
language java name 'Posix.set_env(java.lang.String, java.lang.String)';
При попытке вызова потенциально небезопасных методов внутри Java возбуждается эксепшн, который говорит, что не выдан java grant для пользователя. Загрузка native методов — это еще какая небезопасная операция, потому что мы инжектируем посторонний код прямо внутрь процесса базы данных(тот самый эксплойт, который был анонсирован в заголовке).
Но поскольку база тестовая, даем грант без особых опасений подключившись от sys:
begin
dbms_java.grant_permission( 'SYSTEM', 'SYS:java.lang.RuntimePermission', 'loadLibrary.Posix', '');
commit;
end;
/
Имя пользователя system — это тот, куда я компилировал код java и plsql wrapper package.
Важно заметить, что при загрузке библиотеки через вызов System.loadLibrary мы опускаем префикс lib и расширение so(то как это описано в документации) и не передаем никакой путь где искать. Есть аналогичный метод System.load, который может загрузить библиотеку только с использованием абсолютного пути.
И тут нас ожидает 2 неприятный сюрприз — я угодил в очередную кроличью нору Оракла. При выдаче гранта возникает ошибка с довольно туманным сообщением:
ORA-29532: Java call terminated by uncaught Java exception: java.lang.SecurityException: policy table update
Проблема гуглится в интернете и ведет на My Oracle Support (aka Metalink). Т.к. по правилам Оракл публиковать в открытых источниках статьи с металинка запрещено, то упомяну только номер документа — 259471.1 (те у кого есть доступ смогут прочитать сами).
Суть проблемы — в том что Оракл не даст нам просто так разрешить загрузку подозрительного стороннего кода себе в процесс. Что логично.
Но раз база тестовая и мы в своем коде уверены, разрешаем загрузку без особых опасений.
Фух, злоключения на этом все закончены.
It's alive, alive
С замиранием сердца я решил попробовать вдохнуть жизнь в своего Франкенштейна.
Запускаем базу данных с предзагруженым libfaketime и 0 смещением
Подключаемся в базу и делаем вызов кода который просто выведет время до и после изменения переменной окружения:
begin
dbms_output.enable(100000);
dbms_java.set_output(100000);
dbms_output.put_line('old time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
system.posix.set_env('FAKETIME','+1d');
dbms_output.put_line('new time: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));
end;
/
Работает, черт подери! Честно сказать, я ждал каких-то еще сюрпризов, например ORA-600 ошибок. Однако в алертлоге было все число и код продолжал работать.
Важно отметить, что если подключение в базу делается как dedicated, то после завершения соединения процесс уничтожится и никаких следов не останется. Но если мы используем shared соединения, то в этом случае из серверного пула выделяется уже готовый процесс, мы в нем меняем время через переменные окружения и при отключении оно внутри процесса так и останется измененным. И когда потом другая сессия базы данных попадает в тот же серверный процесс, она будет получать неправильное время к своему немалому удивлению. Поэтому при завершении сессии тестов лучше всегда возвращать время обратно к нулевому смещению.
Заключение
Надеюсь, что рассказ был интересным(и может даже кому-то полезным).
Исходные коды все доступны на Github.
Документация по libfaketime — тоже.
А как вы делаете тестирование? И как создаете dev- и test-базы в компании?
xtender
А почему не просто установить параметр fixed_date?
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/refrn/FIXED_DATE.html#GUID-2AE0D45E-C4EB-4A12-87F0-69F7CFF1CB30
kish4ever Автор
Cпасибо за интересную идею. Ее как оказалось мы не смотрели при отборе вариантов. Но тут есть момент: время то полностью заморозится в таком случае. А нам нужно чтобы оно продолжало тикать и после модификации.