Начну с истории задачи, откуда она возникла: Сейчас я являюсь участником gapminder developers team, которая помогает компании gapminder foundation в разработке разнообразного стека модулей и ПО. gapminder foundation является некоммерческим предприятием, зарегистрированным в Стокгольме, Швеция, которое способствует устойчивому глобальному развитию и достижению Целей развития тысячелетия Организации Объединенных Наций за счет более широкого использования и понимания статистики и другой информации о социальном, экономическом и экологическом развитии на местном, национальном и глобальном уровнях. В следствии этого, мы, как разработчики, в основном занимаемся обработкой, хранением, визуализацией разнородных статистических данных а также обучаем этому всех желающих (по мере наших скромных сил) Одной из задач в нашем случае была обработка большого количества csv файлов (> 1Gb, или даже > 1Tb): т.е. это не просто перебрать строки csv файла и засунуть их в БД, а разобрать каждую на запчасти, посмотреть что эта строка означает в рамках того источника данных и определить ее место и назначение в БД. Кроме этой достаточно понятной функции нам необходимо было учитывать версионность этих файлов (в 1000000 строке обновились, добавились или удалились данные, и надо определить что это означает для всего источника, как это влияет на другие связанные сущности). Разрабатывать мы начали на nodejs, т.к. этот язык был известен и понятен для всей gapminder developers team. Также важно было, чтобы мы тратили как можно меньше дорогих ресурсов (таких как оперативная память, количество и мощности процессоров), пользуясь по максимуму самыми дешевыми (дисковое пространство, время обработки)
в гит коммитах находятся большие csv файлы (> 1Gb, или даже > 1Tb), которые время от времени иногда значительно иногда незначительно меняются. Надо научиться находить diff между 2мя коммитами и только его складывать в БД.
- nodejs (т.к. это общая технология для всех разрабочиков из нескольких команд, которую все знали)
- трата как можно меньше дорогих ресурсов: оперативная память, количество и мощность процессоров
- использовать по максимуму самых дешевых ресурсов: дисковое пространство, время обработки
ну вроде как все понятно. в самом общем случае: есть 2 больших файла (для начала > 1Gb) в разных коммитах, хотим получить дифф между ними, чтобы потом его обработать (сложить в бд). Получаемый дифф должен содержать некоторое состояние на каждое найденное изменение - MAD (Modified, Added, Deleted).
- должно ли для диффа должно иметь значение
- если поменяются 2 строки местами
- если поменяются 2 колонки местами
- если переименются колонки
- какие изменения в файлах для нас не важны
- что будут означать состояния M | A | D для каждого изменения (т.е. как их потом обрабатывать)
- стримы
Ячейки в каждом файле это не просто ячейки, а связанные сущности, и эти связи также надо было обновлять, а значит надо было иметь какое-то промежуточное хранилище. Поэтому дифф который мы создавали - хранился в файле (максимально экономим на оперативной памяти). Благо в жестком диске ограничений у нас не было и мы решили воспользоваться им. У нас был еще один козырь в рукаве, которого у вас может не оказаться: у нас был специфичный формат csv файлов, на который мы могли опереться.
уникальное значение особой колонки (или набора колонок) которое получаем из старой файла записывались в WeakSet. Если они совпадали
- Сначала проверяем структуру файлов. Это наиболее глобальные изменения, которые затрагивают все строки (и хотелось не плодить сущности без необходимости):
- добавилась колонка(и)
- удалилась колонка(и)
- изменилась колонка(и) (update)
- Затем проверяем содержимое (без учета изменений в структуре файлов)
- добавилась строка(и)
- удалилась строка(и)
- изменилась строка(и) (change)
На каждое изменение генерируется свой особенный дифф, чтобы потом можно было их отдельными потоками обрабатывать (отфильтровывая все неинтересное)
как ржали над тем, что сами каждый раз забывали что значит update, а что change
Нашли модуль daff, который потоком выдавал все изменения которые были произведены в файлах (основываясь на команде git diff). Оставалось только отфильтровать неинтересные нам, добавить расширенную информацию для постобработки (была строчка такая, стала такая, вид изменения такой-то, в таком-то файле и т.д.) и сохранять дифф в файл (в заранее оговоренном формате). Подробно разберем как работает daff. А также как работает дифф гита.
(сколько съедало озу, проца при повышении размера файлов и количества изменений в них)
- использование daff для просмотра диффа для csv'шек в git
- использование плагина для просмотра диффа для csv'шек в github под хром
как все упало на обновлении гита с 2.7.4 версии до 2.11.3 и в чем оказалась ошибка (в чейнджлогах git ничего не было, нашли по коммиту изменения, которые оказались для нас критическими)
выплевывали результаты в файл в формате ndjson (ссылка на спеку), а также описание формата для постобработки.
непросто тестировать: одно и то же изменение можно определить как удаление и добавление или как модификацию! И все зависит от версии гит! Например переименованный файл в некоторых версиях гита не считается переименованным, а удаленным и созданным заново.
- зная как строится ПО можно и нужно применять уже опробованные подходы (протестированные многими пользователями)
- всегда стоит искать уже готовую реализацию, но обязательно проверять как именно она работает
- всегда стоит фиксировать версию тех тулзов, которыми пользуетесь (ведь семвер - это договоренность, и далеко не всегда разработчики софта ее соблюдают)