Usando o GNU Parallel With Split

9

Estou carregando um arquivo gigantesco em um banco de dados postgresql. Para fazer isso, primeiro uso splitno arquivo para obter arquivos menores (30 GB cada) e, em seguida, carrego cada arquivo menor no banco de dados usando GNU Parallele psql copy.

O problema é que leva cerca de 7 horas para dividir o arquivo e, em seguida, ele começa a carregar um arquivo por núcleo. O que eu preciso é uma maneira de informar splitpara imprimir o nome do arquivo na saída std toda vez que ele terminar de escrever um arquivo, para que eu possa canalizá-lo Parallele ele começará a carregar os arquivos no momento em que splitterminar de escrevê-lo. Algo assim:

split -l 50000000 2011.psv carga/2011_ | parallel ./carga_postgres.sh {}

Eu li as splitpáginas de manual e não consigo encontrar nada. Existe uma maneira de fazer isso com splitou qualquer outra ferramenta?

Topo
fonte

Respostas:

13

Use --pipe:

cat 2011.psv | parallel --pipe -l 50000000 ./carga_postgres.sh

Requer ./carga_postgres.sh para ler do stdin e não de um arquivo, e é lento para a versão GNU Parallel <20130222.

Se você não precisa exatamente de 50000000 linhas, o --block é mais rápido:

cat 2011.psv | parallel --pipe --block 500M ./carga_postgres.sh

Isso passará pedaços de cerca de 500 MB divididos em \ n.

Não sei o que ./carga_postgres.sh contém, mas meu palpite é que contém psql com nome de usuário e senha. Nesse caso, você pode querer usar o GNU SQL (que faz parte do GNU Parallel):

cat 2011.psv | parallel --pipe --block 500M sql pg://user:pass@host/db

O principal benefício é que você não precisa salvar arquivos temporários, mas pode manter tudo na memória / pipes.

Se ./carga_postgres.sh não puder ler do stdin, mas precisar ler de um arquivo, você poderá salvá-lo em um arquivo:

cat 2011.psv | parallel --pipe --block 500M "cat > {#}; ./carga_postgres.sh {#}"

Trabalhos grandes geralmente falham no meio. O GNU Parallel pode ajudá-lo reexecutando os trabalhos com falha:

cat 2011.psv | parallel --pipe --block 500M --joblog my_log --resume-failed "cat > {#}; ./carga_postgres.sh {#}"

Se isso falhar, você poderá executar novamente o procedimento acima. Irá pular os blocos que já foram processados ​​com sucesso.

Ole Tange
fonte
1
Se você possui uma versão mais recente do GNU Parallel> 20140422, use a resposta de @ RobertB com --pipepart. Se isso não funcionar diretamente, veja se --fifo ou --cat podem ajudá-lo.
precisa
2

Por que não usar --pipe AND --pipepart com o GNU Parallel? Isso elimina o gato extra e inicia leituras diretas do arquivo no disco:

parallel --pipe --pipepart -a 2011.psv --block 500M ./carga_postgres.sh
Robert B.
fonte
1

Eu achei as respostas postadas aqui muito complexas, então perguntei no Stack Overflow e recebi esta resposta:

Se você usar GNU split, poderá fazer isso com a --filteropção

'--filter = command'
Com esta opção, em vez de simplesmente gravar em cada arquivo de saída, grave através de um canal no comando shell especificado para cada arquivo de saída. O comando deve usar a variável de ambiente $ FILE, que é configurada com um nome de arquivo de saída diferente para cada chamada do comando.

Você pode criar um script de shell, que cria um arquivo e inicia carga_postgres.sh no final em segundo plano

#! /bin/sh

cat >$FILE
./carga_postgres.sh $FILE &

e use esse script como filtro

split -l 50000000 --filter=./filter.sh 2011.psv
Topo
fonte
0

Uma alternativa para splitimprimir os nomes dos arquivos é detectar quando os arquivos estão prontos. No Linux, você pode usar o recurso inotify e, especificamente, o inotifywaitutilitário.

inotifywait -m -q -e close_write --format %f carga | parallel ./carga_postgres.sh &
split -l 50000000 2011.psv carga/2011_

Você precisará matar inotifywaitmanualmente. Matá-lo automaticamente é um pouco difícil, porque existe uma condição potencial de corrida: se você matá-lo assim que splitterminar, pode ter recebido eventos que ainda não foram relatados. Para garantir que todos os eventos sejam relatados, conte os arquivos correspondentes.

{
  sh -c 'echo $PPID' >inotifywait.pid
  exec inotifywait -m -q -e close_write --format %f carga
} | tee last.file \
  | parallel ./carga_postgres.sh &
split -l 50000000 2011.psv carga/2011_
(
  set carga/2011_??; eval "last_file=\${$#}"
  while ! grep -qxF "$last_file" last.file; do sleep 1; done
)
kill $(cat inotifywait.pid)
Gilles 'SO- parar de ser mau'
fonte