Поиск изменений между 2 ОГРОМНЫМИ (текстовыми) файлами - PullRequest
6 голосов
/ 22 марта 2011

У меня есть доступ к файлам зоны .com.Файл зоны - это текстовый файл со списком доменных имен и их серверов имен.Он имеет следующий формат:

mydomain NS ns.mynameserver.com.
mydomain NS ns2.mynameserver.com.
anotherdomain NS nameservers.com.
notinalphadomain NS ns.example.com.
notinalphadomain NS ns1.example.com.
notinalphadomain NS ns2.example.com.

Как видите, для каждого домена может быть несколько строк (при наличии нескольких серверов имен), и файл имеет вид NOT * 1006.* в альфа-порядке .Эти файлы имеют размер 7 ГБ .

Я пытаюсь взять предыдущий файл и новый файл и сравнить их, чтобы найти:

  1. ЧтоДобавлены домены
  2. Какие домены были удалены
  3. На каких доменах изменились серверы имен

Поскольку 7 ГБ слишком много для загрузки всего файла в память, очевидно,Мне нужно читать в потоке.Метод, который я в настоящее время придумал как лучший способ сделать это, состоит в том, чтобы сделать несколько проходов по обоим файлам.Один проход для каждой буквы алфавита, загружая все домены в первом проходе, которые начинаются, например, с «а».Получив все «a» домены из старого и нового файла, я могу сделать довольно простое сравнение в памяти, чтобы найти изменения.

Проблема в том, что даже чтение char по char и оптимизациясколько я мог думать, каждый проход по файлу занимает около 200-300 секунд , с сбором всех доменов для письма текущего прохода.Итак, я полагаю, что в своем текущем состоянии я смотрю около часа, чтобы обработать файлы, даже не сохраняя изменения в базе данных (что займет еще некоторое время).Это на двухъядерном сервере xeon, поэтому для меня это не слишком большая мощность.Это время не может быть решающим, но я надеюсь, что у кого-то есть блестящие идеи о том, как ускорить процесс ... По общему признанию, я еще не пробовал асинхронный ввод-вывод, это мой следующий шаг.

Заранее спасибоза любые идеи!

Ответы [ 4 ]

5 голосов
/ 22 марта 2011

Подготовка ваших данных может помочь как с точки зрения наилучшего вида кода: неписаного типа, так и с точки зрения скорости выполнения.

cat yesterday-com-zone | tr A-Z a-z | sort > prepared-yesterday
cat today-com-zone | tr A-Z a-z | sort > prepared-today

Теперь ваша программа использует очень простой алгоритм различий, и вы можете даже использовать diff:

diff prepared-today prepared-yesterday

Edit:

И альтернативное решение, которое устраняет некоторую дополнительную обработку при возможной стоимости diff времени выполнения. Это также предполагает использование GnuWin32 CoreUtils:

sort -f <today-com-zone >prepared-today
sort -f <yesterday-com-zone >prepared-yesterday
diff -i prepared-today prepared-yesterday

Результатом этого будет список добавлений, удалений и изменений. Не обязательно 1 запись об изменении для каждой зоны (рассмотрите, что происходит, когда два домена в алфавитном порядке убраны). Возможно, вам придется поиграть с опциями diff, чтобы заставить его не проверять столько строк контекста, чтобы избежать значительных ложных изменений.

Вам может понадобиться написать программу, чтобы взять два отсортированных входных файла и просто запустить их в пошаговом режиме для каждой зоны. Когда в файле СЕГОДНЯ найдена новая зона, это новая зона. Когда в файле YESTERDAY обнаружена «новая» зона (но отсутствует сегодня), это удаление. Когда в обоих файлах обнаружена «одинаковая» зона, сравните записи NS. Это либо без изменений, либо смена серверов имен.

4 голосов
/ 05 апреля 2011

На вопрос уже дан ответ, но я дам более подробный ответ с фактами, которые будут понятны всем. Я постараюсь рассказать о существующих решениях и даже о том, как их распространять, с объяснениями того, почему все вышло так, как они.

У вас есть 7 ГБ текстовый файл. Ваш диск позволяет нам передавать данные со скоростью 20 МБ / с, давайте будем пессимистичны. Это может передать все это за 350 секунд. Это меньше 6 минут.

Если предположить, что средняя строка составляет 70 символов, у нас есть 100 миллионов строк. Если наш диск вращается со скоростью 6000 об / мин, среднее вращение занимает 0,01 секунды, поэтому захват случайного фрагмента данных с диска может занять от 0 до 0,01 секунды, а в среднем - 0,005 секунды. Это называется нашим временем поиска. Если вы точно знаете, где находится каждая запись, и будете искать каждую строку, это займет у вас 0,005 с * 100 000 000 = 500 000 с, что близко к 6 дням.

Уроки

  1. При работе с данными на диске вы действительно хотите избежать поиска. Вы хотите передавать данные.
  2. Если возможно, вы не хотите, чтобы ваши данные были на диске.

Теперь стандартный способ решения этой проблемы - сортировка данных. Стандартная сортировка слиянием работает, беря блок, сортируя его, беря другой блок, сортируя его, а затем объединяя их вместе, чтобы получить больший блок. Операция слияния передает данные и записывает поток, который является именно тем шаблоном доступа, который нравится дискам. Теперь в теории с 100 миллионами строк вам потребуется 27 проходов с сортировкой слиянием. Но на самом деле большинство из этих проходов легко вписываются в память. Кроме того, умная реализация - какой, кажется, nsort - может сжать промежуточные файлы данных, чтобы сохранить больше проходов в памяти. Этот набор данных должен быть хорошо структурированным и сжимаемым, в котором все промежуточные файлы данных должны умещаться в оперативной памяти. Поэтому вы полностью избегаете дисков, за исключением чтения и записи данных.

Это решение, которое вы выбрали.


ОК, это говорит нам, как решить эту проблему. Что еще можно сказать?

Совсем немного. Давайте проанализируем, что случилось с предложениями базы данных. Стандартная база данных имеет таблицу и несколько индексов. Индекс - это просто структурированный набор данных, который сообщает вам, где находятся ваши данные в вашей таблице. Таким образом, вы идете по индексу (возможно, выполняете множественный поиск, хотя на практике все, кроме последнего, как правило, находятся в ОЗУ), который затем сообщает вам, где находятся ваши данные в таблице, и вам нужно будет снова искать их, чтобы получить данные. Таким образом, захват части данных из большой таблицы потенциально означает поиск 2 дисков. Кроме того, запись части данных в таблицу означает запись данных в таблицу и обновление индекса. Что означает писать в нескольких местах. Это означает, что больше дисков ищет.

Как я объяснил в начале, поиск диска плох. Вы не хотите делать это. Это катастрофа.

Но, спросите вы, разве люди из баз данных не знают этого? Ну, конечно, они делают. Они разрабатывают базы данных, чтобы делать то, что просят пользователи, и они не контролируют пользователей. Но они также заставляют их поступать правильно, когда они могут понять, что это такое. Если вы работаете с приличной базой данных (например, Oracle или PostgreSQL, но не MySQL), база данных будет иметь неплохую идею, когда будет хуже использовать индекс, чем слиянием, и выберет поступать правильно. Но он может сделать это только в том случае, если у него есть весь контекст, поэтому так важно перенести работу в базу данных, а не кодировать простой цикл.

Кроме того, база данных хороша тем, что не нужно писать везде, пока это не нужно. В частности, база данных записывает что-то, называемое журналом WAL (запись в журнал доступа - да, я знаю, что второй журнал избыточен) и обновляет данные в памяти. Когда он добирается до него, он записывает изменения в память на диск. Это объединяет записи и заставляет их искать меньше. Однако есть предел тому, сколько можно выпить. Таким образом, поддержание индексов является по своей сути дорогостоящей операцией. Вот почему стандартный совет для больших объемов данных в базах данных - отбросить все индексы, загрузить таблицу, а затем воссоздать индексы.

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


И последний бит. Что если у нас есть кластер машин? Стандартное решение для этого случая (популяризируемое Google, которое использует это внутренне) называется MapReduce. Он основан на наблюдении, что сортировка слиянием, которая хороша для диска, также действительно хороша для распределения работы по нескольким машинам. Таким образом, мы очень, очень хотим подтолкнуть работу к роду.

Уловка, которая используется для этого, состоит в том, чтобы выполнить работу в 3 основных этапа:

  1. Возьмите большой объем данных и создайте поток фактов ключ / значение.
  2. Сортировка фактов, разбиение их на ключ / значения и отправка для дальнейшей обработки.
  3. Имейте редуктор, который принимает набор ключей / значений и что-то с ними делает.

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

С точки зрения пользователя, хорошая вещь в этой парадигме состоит в том, что все, что вам нужно сделать, это написать простой преобразователь (берет часть данных, например, строку, и генерирует 0 или более пар ключ / значение). ) и редуктор (принимает набор ключей / значений, что-то с ним делает) и кровавые подробности могут быть перенесены в вашу среду MapReduce. Вам не нужно осознавать тот факт, что он использует своего рода под капотом. И он может даже позаботиться о таких вещах, как то, что делать, если одна из ваших рабочих машин умирает в середине вашей работы. Если вам интересно поиграть с этим, http://hadoop.apache.org/mapreduce/ - это широко доступный фреймворк, который будет работать со многими другими языками. (Да, он написан на Java, но не важно, на каком языке написаны маппер и редуктор.)

В вашем случае ваш картограф может начать с фрагмента данных в форме (filename, block_start), открыть этот файл, начать с этого блока и выдать для каждой строки пару ключ / значение в форме domain: (filename, registrar). Затем редуктор получит для одного домена 1 или 2 файла, из которых он получен, с полной информацией. Это тогда только испускает факты интереса. Добавляет, что это в новом, но не в старом. Снижения в том, что это в старом, а не в новом. Изменения в регистраторе состоят в том, что он есть в обоих, но регистратор изменился.

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

3 голосов
/ 22 марта 2011

Это очень похоже на вопрос об интервью в Google, который звучит примерно так: «скажем, у вас есть список из одного миллиона 32-разрядных целых чисел, которые вы хотите напечатать в порядке возрастания, а на машине, на которой вы работаете, всего 2 МБ»оперативной памяти, как бы вы подошли к проблеме?

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

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

Повторите вышеуказанные шаги для второго файла, а затем откройте два отсортированных набора данных и прочитайте их по одной строке за раз.Если строки совпадают, переходите к следующей строке.В противном случае запишите разницу в наборе результатов (или в выходном файле), а затем перейдите к следующему ряду для каждого файла с лексикографически «меньшим» значением и повторите.

Не уверен, насколько быстро это будет, но этопочти наверняка быстрее, чем делать 26 проходов через каждый файл (у вас есть 1 проход для построения чанков, 1 проход для объединения чанков и 1 проход для сравнения отсортированных наборов данных).

Это, или использовать базу данных.

2 голосов
/ 22 марта 2011

Вы должны прочитать каждый файл один раз и сохранить их в базе данных.Затем вы можете выполнить любой необходимый анализ, используя запросы к базе данных.Базы данных предназначены для быстрой обработки и обработки больших объемов данных, подобных этой.

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

...