Почему печать данных в файл значительно медленнее с awk, чем с оболочкой?

Я использую сценарий оболочки в течение ~ 3 лет, который запускает tail -F -n0 /path/to/LOGFILE, анализирует и форматирует вывод и выгружает его в файл с разделителями каналов. Однако мы перешли от нескольких тысяч строк журнала в день к нескольким миллионам строк журнала в день, и скрипт начал потреблять огромное количество памяти и ЦП.

Недавно я заменил всю логику разбора данных на awk, который при тестировании кажется на МНОГО порядков быстрее при анализе данных. Чтобы протестировать новый код awk, я попытался прокачать журналы за целый день через логику (~ 6 миллионов строк), используя как код awk, так и код оболочки, и неудивительно, что код awk вытащил ~ 92 000 соответствующих строк за ~ 10 секунд, в то время как Шелл-коду потребовалось более 15 минут, чтобы сделать то же самое. Однако, если я использую точно такой же код, но вместо cat /path/to/file|awk '...' я делаю tail -F -n0 /path/to/file|awk '..., возникает ОГРОМНАЯ задержка, когда текст записывается в файл, до ~ 2-3 минут, а не ~ 0,5-1,0 секунды, когда он проходит через шелл-код.

Шелл-код (да, я знаю, насколько уродлив шелл-код):

outDir="/opt/redacted/logs/allerrors/"
tail -F -n0 /opt/redacted/logs/manager.log|while read -a logLine;do
if [[ "${logLine[2]}" == "E" ]];then
  if [[ "${logLine[7]:0:1}" == "@" ]];then
    echo "${logLine[0]}|${logLine[1]}|${logLine[6]}|${logLine[7]}|${logLine[@]:8:${#logLine[@]}}" >> ${outDir}allerrors.${logLine[0]//\//.}.log
  else
    echo "${logLine[0]}|${logLine[1]}|${logLine[6]}|NULL|${logLine[@]:7:${#logLine[@]}}" >> ${outDir}allerrors.${logLine[0]//\//.}.log
  fi
fi

Выполнено

авк-код:

  outDir="/opt/redacted/logs/allerrors/"
  tail -F -n0 /opt/redacted/logs/manager.log|awk -v dir=$outDir '{OFS="|"}
{
  if ($3 == "E")
  {
    file="allerrors."$1".log"
    gsub("/",".",file)
    if ($8 ~ /@/)
      print $1,$2,$7,$8,substr($0, index($0,$9)) >> dir file
    else {if ($8 !~ /@/)
      print $1,$2,$7,"NULL",substr($0, index($0,$8)) >> dir file
    }
  }
}'

Чтобы было ясно, оба набора кода работают и создают идентичный вывод, если я использую cat вместо хвоста файла, но с кодом awk я не вижу результатов в своем выходном файле до тех пор, пока ~ 2-3 минуты после того, как он появляется в журнале в то время как версия оболочки занимает всего несколько секунд.


person drldcsta    schedule 26.11.2013    source источник
comment
Вероятно, это потому, что awk буферизует ввод, а read нет. Почему вы используете tail -F вместо того, чтобы просто предоставить файл в качестве входных данных для awk (т.е. ни кота, ни хвоста, а просто читать): awk ... /opt/redacted/logs/manager.log   -  person rici    schedule 27.11.2013
comment
Я думал, что это может быть что-то с буферизацией строк, но я недостаточно хорошо понимаю это концептуально, чтобы обойти это. . У меня были проблемы с передачей хвоста в grep, но у grep есть флаг --line-buffer (или что-то в этом роде), который исправил это. Есть ли что-то подобное для awk? Что касается хвоста, это файл журнала, который переворачивается, когда достигает определенного размера. Но я хочу, чтобы синтаксический анализ выполнялся в реальном времени. Я не знаю, как заставить запрос читать в файле в режиме реального времени и выполнять его, когда он катится, возможно ли это?   -  person drldcsta    schedule 27.11.2013
comment
mywiki.wooledge.org/BashFAQ/009   -  person tripleee    schedule 27.11.2013


Ответы (2)


awk буферизует по умолчанию, а sh нет. Это увеличивает пропускную способность awk, но также и его задержку.

Просто добавьте fflush(); в свой awk-код, чтобы принудительно очистить буферы:

  outDir="/opt/redacted/logs/allerrors/"
  tail -F -n0 /opt/redacted/logs/manager.log|awk -v dir=$outDir '{OFS="|"}
{
  if ($3 == "E")
  {
    file="allerrors."$1".log"
    gsub("/",".",file)
    if ($8 ~ /@/)
      print $1,$2,$7,$8,substr($0, index($0,$9)) >> dir file
    else {if ($8 !~ /@/)
      print $1,$2,$7,"NULL",substr($0, index($0,$8)) >> dir file
    }
    fflush();
  }
}'
person that other guy    schedule 26.11.2013
comment
Да, это сработало, хотя мне пришлось использовать flush(stdout), чтобы заставить его работать. - person drldcsta; 27.11.2013

tail -f -n0 filename 

блокируется до тех пор, пока не появится новый ввод-вывод для файла, поэтому, как уже было сказано, проблема ввода, связанная с буферизацией для stdout в процессе awk. Однако, если в файле журнала нет изменений в течение 2 минут, буферизация ввода-вывода awk не полностью учитывает временную задержку.

Попробуйте это, чтобы показать эффект буферизации строк:

while true                    
do                        
echo -n 'this is a string'
sleep 5                   
echo ' add a newline'     
done | awk '{print $0}'   
person jim mcnamara    schedule 26.11.2013