Как стать автором
Обновить

Интеграция сервиса «Проверки по списку недействительных российских паспортов» или как сжать csv-файл в 38 раз

Время на прочтение5 мин
Количество просмотров11K
Всего голосов 7: ↑6 и ↓1+10
Комментарии30

Комментарии 30

Что-то линк на GH не работает.

Хотелось бы узнать, как реализовано требование "возможности обновления данных без прерывания работы сервиса".

Так как данные хранятся в памяти, обновление не требует прерывания работы сервиса.

НЛО прилетело и опубликовало эту надпись здесь

Познавательно. Дополнил алгоритм оптимизацией из этой статьи:

Длина массива будет зависеть от максимального номера в серии. Другими словами, если в серии 3382 встречается только один паспорт с номером 000032, то для всей серии потребуется 4 байта.

В итоге сжатие улучшилось на 10%.

Письмо на Балабановскую спичечную фабрику:

«Я 11 лет считаю спички у вас в коробках - их то 59, то 60, а иногда и 58. Вы там сумасшедшие что ли все???»

А в чём выгоды подобного решения? Какой смысл экономить на спичках? Просто в базу csv-шку залить и читать оттуда запретили? Этот наносервис один? Инстанс базы стоит миллион?

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

Так и есть, мы в БД у себя положили и проблем с этим местом вообще никаких.

Да, тут просится БД с загрузкой в нее данных при их поступлении.

Для поиска лучше иметь "нормированное" поле sernum - серия+номер без пробелов.

У нас много таких таблиц. Причем, в общем случае - ДУЛ (документ, удостоверяющий личность). Там не только паспорта могут быть. И содержится там тип ДУЛ, серия, номер, нормализованый номер.

А таблиц много - ДУЛы клиентов, ДУЛы подозреваемых в экстримизме, пособничестве терроризму, распостранении оружия массового уничтожения (эти списки тоже извне приходят и регулярно обрабатываются, раскладываются по базе с последующей проверкой всех клиентов на совпадения по разным параметрам).

[зануда mode]

И если докапываться до терминологии, то у паспорта РФ нет серии. Есть "номер бланка паспорта" в формате NN NN NNNNNN.

А "серия паспорта" - это наследие паспортов СССР. Там да - серия в формате две римских цифры - пробел - две буквы кирилицы и номер в формате NNNNNN

[/зануда mode]

НЛО прилетело и опубликовало эту надпись здесь

Да, был неправ. Спасибо.

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

Если нужен зеро-даунтайм, то просто импортите в таблицу паспорта2, а потом в одну операцию переименовываете старую паспорта1 в мусор, а паспорта2 в паспорта1 и дропаете мусор. Без всяких проверок, сравнений и мерджей сложных. Переименование -- мгновенная операция.

А если в момент переименования по удаляемой таблице SQL запрос выполняется?

Он до конца дойдёт, но по данным переименовываемой таблицы.

А не залочит он старый файл, не дав его удалить при переименовании?

Вот возьмет и переключится моментально на новый не меняя плана запроса?

Как-то сомнительно это мне... Сколько пробовал - если на индексе висит открытый запрос, пересоздать его не удается - object lock error

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

Сдается мне что не выдет.

Выйдет

Что Вы понимаете под "синхронизацией данных"?

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

Под синхронизацией в бд я понимаю сравнение двух наборов данных, затем вставку недостающих записей и удаление лишних.

Потоковая синхронизация была бы нужна, когда одновременно меняются данные в объекте-хранилище и читаются из него. В моем случае хранилище заменяется целиком на новое.

А просто подготовить таблицу в бд и одной транзакцией поменять имена у обоих?

Лучше минимизировать количество аллокаций, вынести индекс запятой в константы и избавиться от LINQ. Метод IsOnlyNumbers превратиться в следующее:


private bool IsOnlyNumbers(string value)
{
    if (value.Length == PART1_LENGTH + PART2_LENGTH && value[COMMA_INDEX] == ',')
    {
        for (int index = 0; index < value.Length; index++)
        {
            if (index != COMMA_INDEX && !char.IsDigit(value[index]))
            {
                return false;
            }
        }
        return true;
    }
    return false;
}

Вишенка на торте — использовать спаны:


private (int, int) SplitNumbersValue(string value)
{
    var onlyNumbers = value.Substring(0, COMMA_INDEX) + value.Substring(COMMA_INDEX + 1);
    var onlyNumbersSpan = onlyNumbers.AsSpan();
    return (int.Parse(onlyNumbersSpan.Slice(0, PART1_LENGTH)), int.Parse(onlyNumbersSpan.Slice(PART1_LENGTH)));
}

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


Остальное нужно смотреть по полному коду, но ссылка на GitHub невалидная.

Спасибо, полезные замечания! Сжатие ускорилось на 19 секунд (25%).

Уточните требования к задаче:

Что надо делать со строковыми сериями паспортов? Игнор?

Самое интересное - нефункциональные требования:

  1. Ожидаемый RPS. Какое кол-во запросов должен тянуть сервис? Пробовали делать нагрузочное тестиование? Сколько тянет ваше решение на 1 поде?

  2. Скорость обновления? (Как вы его вообще будете обновлять?)

  3. Нужны ли инкрементальные обновления?

  4. Какой даунтайм допускается?

  5. Максимальное время запуска новой версии сервиса?

  6. Ограничения по CPU, Mem, HDD?

  7. Требования по горизонтальному масштабированию? Например, возможность горизонтального масштабирования до Х подов?

  8. Вообще, требования к масшабированию?

Это называется: "...и тут появляется архитектор".

Поздно, батенька, такие умные вопросы задавать - все уже закодировано :-).

Программеру же было интересно с битиками поиграться. А вы к не нему с неприятными вопросами...

Паспорта со строковыми сериями также должны проверяться на наличие в списке.

  1. Нагрузочное тестирование не делал. Можно расширить сервис, добавив проверку списка паспортов. Должно работать быстро, т.к. поиск идет в памяти и сложность близка к O(1).

  2. Сейчас (после некоторых оптимизаций) обновление занимает менее 6 минут и запускается по расписанию раз в сутки. Обновляется обьект в памяти.

Особых требований нет, нужно сделать по возможности оптимально по скорости и памяти.

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

@skivsoft после фильтрации и сжатия значений, в случае успешного результата поиска, вы, вероятно, идёте в базу с более полными данными и проверяете причину включения в список, историю (некоторые записи то появляются то исчезают, выявлялись ли ложные срабатывания, пересечения с другими источниками), уровень доверия записи. Это поинтереснее сжатия. Расскажете?

Не совсем понял для чего разделять на 2 этапа. Сейчас в csv файле присутствуют следующие наборы данных:

  • в формате ^\d{4},\d{6}$ (таких записей большинство)

  • в формате ^[A-Za-zА-Яа-я\d\s-]+,\d+$

  • ошибочные данные, например З вместо 3 в номере паспорта

Проверять на соответствие формату не требуется, достаточно проверить присутствует ли комбинация Серия + Номер в csv-файле.

У меня нет истории записей и других источников, только этот csv-файл из открытого доступа. В данной реализации нет ложных срабатываний, сжатые данные эквивалентны исходному csv-файлу с точностью до порядка строк. Т.е. выполняется условие:

SORT(original.csv) = SORT(Unpack(Pack(original.csv)))

Ложные срабатывания - ошибочное включение в базу, а не ошибки при сжатии. Такие значения то появляются, то исчезают. Если не хранить историю, в т.ч. доп.проверок и отметок о ложных срабатываниях, качество применения данных (для скоринга?) снижается.

Это чье-то тестовое, но не помню чье. Тестовое было на позицию архитектора кажись или PHP разраба.

В итоге, в порядке извращения я сделал это все на связке nginx, который выполняет код на lua, который то ли грепает по этому файлу то ли ещё чего, не помню.

А обновление без даунтайма кажись через wget/rsync + изменение симлинка.

В общем, вообще без программирования.

Не проще было просто в parquet перевести и дергать каким нибудь drill у которого еще и кеширование есть?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории