17 дек. 2017 г.

Каждой программе по своей "проблеме 2000 года"

Помните, мир стоял на ушах, ожидая как самолеты попадают и атомные станции повзрываются? К счастью, ничего такого не произошло благодаря тому, что все критическое ПО было вовремя проверено и исправлено.

В Гедымине есть своя "проблема  2000 года", а точнее проблема 32-х битного идентификатора бизнес-объекта. Чтобы быть еще более точным, не самого идентификатора а генератора GD_G_UNIQUE, с помощью которого идентификаторы добываются.

Данный генератор стартует со значения 147 000 000 и увеличивается по мере запроса новых идентификаторов. Причем, для сокращения количества запросов к серверу, увеличиение идет с шагом в 100, а неиспользованный на момент завершения программы интервал сохраняется в системном реестре.

Так как у нас ИД объекта -- это знаковое 32-х битное целое, то всего доступно чуть более 2-х миллиардов идентификаторов (мы не учитываем первые 147 миллионов, которые выделены под системные объекты платформы).

Два миллиарда число большое. Скажем, если непрерывно получать по одному идентификатору в секунду, то такого диапазона хватит на 63 года. Однако сам генератор ничем не защищен от некорректного использования, уже не говоря про то, что его можно просто "подвинуть" вперед вручную на произвольную величину. Нам встречался код, где генератор использовался для упорядочивания записей в выборке. Естественно, каждое перестроение запроса приводило к пустому расходу сотен, если не тысяч значений генератора.

Таким образом у нас появились первые клиенты, у которых значение генератора подошло вплотную к физическому лимиту.

Что делать?

Теоретически есть два варианта решения проблемы. Первый -- это сдвинуть, утрамбовать все идентификаторы "вниз" на выявленные пустые пробелы. Затем изменить значение генератора в соответствии с максимальным ИД в базе.

Технически, для этого придется выполнить следующую последовательность шагов:
  1. Сохранить все существующие ИД в некоторой структуре.
  2. Построить таблицу соответствия старый ИД -- новый ИД.
  3. Отключить все внешние и первичные ключи.
  4. Обновить ВСЕ записи в базе данных, заменяя старые идентификаторы на новые.
  5. После предыдущего шага желательно выполнить бэкап-восстановление БД для чистки мусора.
  6. Восстановить все первичные и внешние ключи.
На базах размером свыше 100 Гб мы не представляем как можно выполнить указанную последовательность в доступное нам технологическое окно (обычно 8-10 часов).  И, если где-то в коде, используется привязка к ИД записи, вместо РУИД, то такой код перестанет работать. К тому же, надо будет как-то вычистить из реестров всех компьютеров сохраненные интервалы или одномоментно заменить все экзешники, скорректировав алгоритм кэширования.

Второй вариант:
  1. Создать таблицу для доступных интервалов идентификаторов GD_AVAILABLE_ID.
  2. При обращении к функции gdcBaseManager.GetNextID проверять не приблизились ли мы к опасной черте. 
  3. Если нет, то работать по-старому -- через генератор. 
  4. Если уже пора, то заполняем таблицу и по-мере необходимости берем очередной интервал из нее.
На практике проверено, что заполнение такой таблицы занимает пару часов даже на самой большой, доступной нам базе данных.

Если спохватиться во-время, то остатка генератора хватит на работу устаревших экзешников (в сети большого предприятия трудно выявить и заменить все программы сразу) и кода, который получает занчение идентификатора менуя функцию GetNextID.