Время прочтения: 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 процентов.

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