6 июня 2025
Задача
У клиента возникли проблемы с безопасным хранением кодов в базе данных — при проверке выяснилось, что коды хранятся в открытом виде и при утечке могут быть использованы злоумышленниками.
Нам нужно было решить три задачи:
- Сделать хранение кодов в базе более безопасным.
- Улучшить архитектуру и использовать более надёжные библиотеки.
- Избежать бесконечной работы программы из-за рекурсии.
Решение
Обеспечили безопасное хранение кодов
Чтобы коды не хранились в базе в открытом виде, мы стали использовать хеш кода. Для большей защиты хеш формируется из кода и секретного ключа. Этот ключ хранится в настройках программы. После тестирования все временные данные удаляются.
Что мы сделали:
- Добавили новые поля в таблицы для хранения хешей.
- Написали отдельный сервис, который создаёт хеш и записывает его во временную таблицу при генерации.
- Перешли на более надёжный способ генерации кодов — RandomNumberGenerator, что улучшило уникальность кодов.
- В настройках добавили ограничения на количество повторов, чтобы не возникала бесконечная генерация.
- Перед началом генерации кодов программа теперь рассчитывает, сколько уникальных кодов можно получить при заданных параметрах. Если нужное количество невозможно, генерация не начинается.
- Изменили запросы для удаления дубликатов: теперь они работают по хешу, а не по коду.
- Подготовили инструкцию для клиента по переходу на работу с хешами вместо обычных кодов.
Добавили хеши для уже существующих кодов
В работе с такими модулями важно не упустить детали. Например, возможность изменить название способа оплаты или добавить фирменный логотип в чекауте — мелочи, но они могут заметно улучшить пользовательский опыт. Хотя в этот раз мы на них останавливаться не будем.
В базе уже было ~ 65 миллионов кодов. Чтобы быстро посчитать для них хеши, мы использовали SQL-запросы прямо в MySQL:
UPDATE fmccodes SET CodeHash = SHA2(CONCAT(Code, 'key'), 256) WHERE Prefix IN ('XX', 'YY', 'ZVC');
Хеши были рассчитаны для всех кодов с разными префиксами, что заняло много времени. После завершения процедуры мы добавили индекс на колонку с хешами, чтобы ускорить работу запросов.
Оптимизировали процесс генерации и хранения кодов
Как работала генерация кодов до оптимизации? На основе настроек создавался массив параметров, где указывалось, сколько кодов нужно сгенерировать за один раз и сколько потоков использовать. Коды генерировались по заданным правилам: длина, допустимые символы, спецсимволы и т. д.
Полученные коды записывались во временную таблицу — по 1000 штук за итерацию. После каждой записи запускалось удаление дубликатов. Это происходило рекурсивно, пока не набиралось нужное количество уникальных кодов. Затем дубликаты удалялись, уже с учётом основной таблицы — особенно если использовался уже существующий префикс.
Когда оставались только уникальные коды, они записывались в файл. После этого запускался перенос кодов из временной таблицы в основную с помощью хранимой процедуры напрямую на сервере.
Проблема была в том, что генерация 10 млн кодов с новым префиксом занимала около 8 часов. С уже существующими префиксами процесс мог вообще не завершиться.
Мы проанализировали процесс генерации и нашли самые «тормозящие» операции:
- Коды долго записывались в основную таблицу из-за большого объёма данных и индексов;
- Также долго удалялись дубликаты в основной и временной таблицах;
- При удалении старых кодов место на диске не освобождалось.
Как мы ускорили процесс:
- Ввели отдельные таблицы для каждого префикса — как бы разбили основную таблицу на части.
- Убрали лишние индексы, которые тормозили запись.
- Сделали поле CodeHash основным ключом во временной таблице. Это значит, что дубликаты в неё просто не добавлялись.
- Использовали вставку INSERT IGNORE — если код уже есть, он не добавляется, а программа просто генерирует ещё.
- Отказались от удаления дублей — теперь мы просто дозаписываем недостающие коды.
После оптимизации генерация 10 миллионов кодов заняла всего 18 минут на обычном ПК вместо 8 часов — то есть время сократилось в 20 раз.
Результат
Мы предоставили клиенту исходный код консольной программы на языке C#, которая генерирует уникальные коды и записывает их в базу данных. Перед запуском сотрудник настраивает параметры генерации в XML-файле, а после завершения работы коды появляются в базе в хешированом виде, а в файле — в открытом.
Все коды разбиты на группы по трёхбуквенным префиксам, для каждого префикса в базе есть отдельная колонка. Внутри одного префикса коды не должны повторяться.
Пример работы программы:
Что дальше
Чтобы сделать процесс генерации кодов ещё удобнее и безопаснее, мы наметили следующие шаги:
- Создать веб-интерфейс, через который можно будет запускать генерацию кодов, следить за процессом и скачивать готовые файлы с кодами.
- Развернуть приложение на сервере или в Docker-контейнере.
- Описать и задокументировать архитектуру базы данных.
- Внедрить очереди задач для более стабильной и масштабируемой генерации.
- Изменить консольную программу, чтобы она принимала параметры прямо при запуске, а не из XML-файла.