Исследование проблемы зависания PL/SQL с SYSTIMESTAMP в Docker-контейнере
1. Описание проблемы
При выполнении PL/SQL-кода из Docker-контейнера наблюдается зависание на неопределённый срок:
v_start TIMESTAMP := SYSTIMESTAMP;
LOOP
EXIT WHEN SYSTIMESTAMP >= v_start + NUMTODSINTERVAL(1, 'SECOND');
NULL;
END LOOP;
Среда выполнения:
- Хост (работает): CentOS/RHEL, glibc 2.17/2.28, Python 3.9, oracledb 3.4.2, Oracle Instant Client 12.2
- Контейнер (зависает): apache/airflow:2.10.5 на Debian bookworm, glibc 2.36, Python 3.12, oracledb 3.4.2, Oracle Instant Client 19.31
Сервер БД: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0. Один и тот же в обоих случаях.
Проявление: пакет TSC_MAJOR_V2.send_soap_request перестаёт отвечать при запуске из Docker. Процедура содержит цикл ожидания (busy-wait) с использованием SYSTIMESTAMP. Все процедуры, вызывающие send_soap_request, зависают — update_events, update_cities, airflow_run_registry.
2. Диагностика
2.1. Методика
Проведено пошаговое тестирование каждого компонента send_soap_request изолированно. Все тесты выполнялись в отдельных соединениях с таймаутом 8 секунд (подпроцессный запуск).
2.2. Результаты
| Компонент | Результат в контейнере |
|---|---|
SELECT 1 FROM dual |
✅ 0.0s |
DBMS_LOCK.SLEEP(2) |
✅ 2.0s |
UTL_HTTP запрос к API |
✅ 0.1s |
TIMESTAMP := SYSTIMESTAMP + LOOP |
❌ зависание |
TIMESTAMP WITH TIME ZONE := SYSTIMESTAMP + LOOP |
✅ 1.0s |
LOCALTIMESTAMP + LOOP |
✅ 1.0s |
DATE := SYSDATE + 1/86400 LOOP |
✅ 0.3s |
UTL_HTTP после TIMESTAMP loop (default TZ) |
❌ зависание |
UTL_HTTP после TIMESTAMP loop (TZ=Europe/Moscow) |
✅ 1.1s |
2.3. Проверенные версии
Нижеприведённые комбинации не влияют на наличие проблемы — все воспроизводят зависание в контейнере:
- oracledb: 2.4.1, 3.4.2, 4.0.1
- Oracle Instant Client: 12.2, 19.19, 21.16
- Python: 3.9, 3.12
- Базовый образ: Debian bookworm (glibc 2.36), oraclelinux:8-slim (glibc 2.28)
Базовые образы с glibc 2.28 и 2.36 воспроизводят зависание. На хосте с glibc 2.17/2.28 проблема отсутствует.
3. Причина
3.1. Технический механизм
SYSTIMESTAMPвозвращает типTIMESTAMP WITH TIME ZONE.- Переменная
v_startобъявлена какTIMESTAMP— тип без часового пояса. - Присваивание
v_start := SYSTIMESTAMPвызывает неявное преобразование с потерей часового пояса. - Сравнение
SYSTIMESTAMP >= v_start + NUMTODSINTERVAL(...)требует приведенияTIMESTAMPкTIMESTAMP WITH TIME ZONE(или наоборот). - Процедура приведения типов в Oracle использует системные вызовы timezone-преобразования.
- В glibc 2.36 эти вызовы приводят к блокировке процесса.
- Блокировка не снимается стандартными механизмами таймаутов.
3.2. Факторы
- glibc 2.36. Основной фактор. В более старых версиях (2.17, 2.28) блокировка не возникает.
- Неявное преобразование типов. Инициирует вызов системной функции timezone-преобразования.
- Docker-окружение. Контейнеризация может влиять на доступ к timezone-данным и системному времени, однако базовой причиной является поведение glibc.
- Busy-wait. Цикл ожидания с
SYSTIMESTAMPне является штатным механизмом задержки в PL/SQL.
3.3. Условия возникновения
Проблема воспроизводится при совпадении всех трёх факторов:
- Использование
TIMESTAMP := SYSTIMESTAMPс последующим сравнением - Версия glibc >= 2.28 (подтверждено на 2.28 и 2.36)
- Запуск в Docker-контейнере
4. Решение
4.1. Основной метод
Установка часового пояса сессии перед вызовом процедур, содержащих проблемный код:
cur.execute("ALTER SESSION SET TIME_ZONE = 'Europe/Moscow'")
Данный метод устраняет необходимость неявного преобразования типов, так как Oracle интерпретирует TIMESTAMP в заданном часовом поясе, не вызывая системную функцию timezone-преобразования.
4.2. Альтернативные способы
При невозможности изменения сессии можно использовать корректные типы данных:
-- Вариант 1. Явное указание типа с часовым поясом
v_start TIMESTAMP WITH TIME ZONE := SYSTIMESTAMP;
-- Вариант 2. Использование LOCALTIMESTAMP (чистый TIMESTAMP)
v_start TIMESTAMP := LOCALTIMESTAMP;
-- Вариант 3. Использование DATE
v_start DATE := SYSDATE;
4.3. Рекомендация
Штатным механизмом задержки в PL/SQL является DBMS_LOCK.SLEEP(n). Он не нагружает CPU, не зависит от системного времени и timezone, и не вызывает проблем с glibc. Busy-wait с SYSTIMESTAMP не рекомендуется к использованию.
5. Выводы
TIMESTAMP := SYSTIMESTAMPс последующим сравнением типовTIMESTAMPиTIMESTAMP WITH TIME ZONEвызывает неявное преобразование, которое на glibc 2.36 приводит к блокировке процесса.- Проблема не зависит от версий Oracle Instant Client, oracledb, Python или образа Docker — определяющим фактором является версия glibc.
- Решение —
ALTER SESSION SET TIME_ZONE = 'Europe/Moscow'перед вызовом процедур с проблемным кодом. - Для задержек в PL/SQL следует использовать
DBMS_LOCK.SLEEP(n)— это безопасный и предсказуемый механизм.