Время прочтения: 3 мин.
Не так давно передо мной стояла задача обработки данных из двух автоматизированных систем, в виде 11-ти файлов средним размером около 3 ГБ (в моём случае это было ~3000000 строк) и одного файла ~36 ГБ (~242000000 строк). Суть обработки заключалась в внутреннем соединении файлов (inner join). Вариант с использованием SQL-сервера сразу отпал, так как загрузка таких объёмов занимает значительное время и высока вероятность ошибок, возникающих в процессе импорта. Использование датафреймов (DataFrame) библиотеки Pandas физически невозможно в обычном виде: объём оперативной памяти 16 ГБ, объём данных загружаемых в память 39 ГБ. Тут всё очевидно.
Изначально мною было принято решение в цикле по 11-ти файлам читать их построчно и читать по каждой строке в цикле большой файл:
with open(file) as f:
for line in f:
row = line.split(';')
with open(bigfile) as bf:
for bline in bf:
brow = bline.split(';')
if row[3] == brow[15]:
<Тут некий код записи строки в файл, DataFrame - в контексте данной статьи это не важно>
В целом, вариант рабочий, но есть некоторые нюансы. Главная проблема такого подхода заключается во времени работы, ведь для реализации алгоритма должно пройти 11*3 000 000 * 242 000 000 итераций цикла, при этом в каждой итерации происходит выполнение функции разбиения текста (split). Так же, стоит отметить, что в каждой из 3 000 000 итераций происходит открытия файла в 242 000 000 строк. Обратный вариант однократного открытия файлов и чтения внутри него файлов по 3 000 000 строк также является неприемлемым для поставленной задачи.
Решить вопрос с комфортным временем выполнения и сравнительно не сложным алгоритмом мне удалось при помощи библиотеки Pandas. Сначала я этот вариант отмёл, но потом вспомнил, что функция read_csv имеет пару интересных и полезных параметров. Параметры эти nrows и skiprows, т.е. количество загружаемых и пропускаемых строк соответственно. Использование этих параметров позволило читать объёмный файл поблочно и «сталкивать» его с цельным файлом поменьше. Вот код такого решения:
for file in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]:
crm = pd.read_csv(r'files\file'+str(file)+'.csv', sep = '|', encoding= 'utf-8-sig', header = None)
try:
row_count = 0
i = 0
j = 122
skip = 1
for i in range(j):
kmp = pd.read_csv(r'files\bigfile.csv', sep = ';', nrows = 2000000, header = None, skiprows = skip, encoding= 'cp1251')
row_count +=len(kmp)
skip +=2000000
kmp = kmp.merge(crm, left_on = [2,7], right_on = [45,36])
kmp.to_csv(r'files\res'+str(file)+'_N.csv', sep ='|', append = True)
print(row_count)
except ValueError:
print('Готово')
continue
Таким образом мне удалось в приемлемые сроки получить необходимую выборку, не нагружая оперативную память. В процессе работы скрипта использование памяти колебалось от 35 до 54 процентов.
В заключение следует сказать, что сей метод далеко не панацея и подходит он далеко не во всех случаях, как, впрочем, и все алгоритмы на свете. Ведь любой инструмент нужно использовать с умом, чтобы он оправдал оджидания.