O documento apresenta vários one-liners em bash para exportar dados de um banco MongoDB com diferentes níveis de compressão e métricas, como taxa de transferência e taxa de compressão.
9. Vamos ver...
$ time --help
--help: command not found
$ type time
time is a shell keyword
$ help time
time: time [-p] pipeline
Report time consumed by pipeline's execution.
$ /usr/bin/time --help
Usage: /usr/bin/time [-apvV] [-f format] [-o file] [--append] [--verbose]
[--portability] [--format=format] [--output=file] [--version]
[--quiet] [--help] command [arg...]
tem dois times no sistema, cuidado com aliases e built-ins
10. Ei, espera aí, que tal usar xz?!
$ mongoexport -d sambatech -c portal | head -c 10000 | tee >( (
/usr/bin/time -f 'gz: %E real, %U user, %S sys' gzip -9 | wc -c |
sed 's/^/gz: /' ) 1>&2) | /usr/bin/time -f 'xz: %E real, %U user, %
S sys' xz -9 | wc -c | sed 's/^/xz: /'
connected to: 127.0.0.1
gz: 0:00.24 real, 0.00 user, 0.00 sys
gz: 2078
xz: 0:00.31 real, 0.02 user, 0.04 sys
xz: 2012
vemelho: limitei os bytes de entrada em um tamanho arbitrário, mas ele
foi insuficiente para chegar a alguma conclusão.
11. Melhor limitar por tempo do que por
bytes. Mas como parar?...
$ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed
's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U
user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) |
/usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c |
sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT %%
[1] 12289
connected to: 127.0.0.1
$ raw: 14623779
gz: 0:08.99 real, 0.75 user, 0.00 sys
gz: 2706591
Command terminated by signal 2
xz: 0:09.02 real, 8.78 user, 0.17 sys
vermelho: os 3 wc(raw, gz, xz) e os seus respectivos resultados (note
que falta um resultado)
azul: a causa; tentei matar o pipe inteiro, mas o resultado ficou cortado.
12. Ahh, aí está o número perdido!
$ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed
's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U
user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) |
/usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c |
sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.
*mongoexport(([0-9]*)).*/1/p')
[1] 12724
connected to: 127.0.0.1
$ raw: 14497402
gz: 0:09.10 real, 0.73 user, 0.01 sys
gz: 2695783
xz: 0:09.18 real, 8.73 user, 0.16 sys
xz: 2021928
vermelho: o wc e sue resultado ,(que estava perdido)
azul: o que fiz pra resolver, matei o processo inicial do pipe e não o
pipe todo
roxo: pid do ultimo processo que foi pra background
13. Mas deixa eu formatar isso melhor...
$ { ( { mongoexport -d sambatech -c portal 2>/dev/null | tee >
(wc -c | sed 's/^/s_raw=/' 1>&2) | tee >( ( /usr/bin/time -f
't_gz=%U+%S' gzip -9 | wc -c | sed 's/^/s_gz=/') 1>&2) |
/usr/bin/time -f 't_xz=%U+%S' xz -9 | wc -c | sed 's/^/s_xz=/' ;
echo 'scale=2; print "nxz is ", t_xz/t_gz; r_sz=s_gz/s_xz;
r_gz=s_gz/s_raw; r_xz=s_xz/s_raw; scale=0; print " times
slower and ", (r_sz-1)/.01,"% smallerncompression ratios:
gz=",(1-r_gz)/.01,"%, xz=",(1-r_xz)/.01,"%n"' ; } 2>&1 | bc ) & } ;
sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.*mongoexport(([0-9]
*)).*/1/p')
xz is 12.02 times slower and 33% smaller
compression ratios: gz=82%, xz=87%
vermelho: contas em bc pra calcular as taxas
azul: variaveis usadas no bc
fundo verde: bloco de shell script cujo output eh codigo para o bc
14. Ai... :/ será que tem algum outro nível
de compressão que valha a pena?
$ { ( { { echo -n 'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for
z in 0 1 ; do for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >
(/usr/bin/time -f 't[$z$l]=%U+%S' $(test $z == 1 && echo gzip || echo xz)
-$l | wc -c | sed 's/^/s[$z$l]=/' 1>&2)" ; done ; done ; echo -n ' | cat >
/dev/null' ; } | bash 2>&1 ; echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0;
x<=1;x++) { for(l=x;l<=9;l++) { i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print
1/s^p/t," (1/s^",p,"/t) "; if(x==1) print "gzip" else print "xz"; print" -",l," is ",
t," time and ",s," size of gzip -6n" } } }' ; } | bash <( echo -n 'bc | tee ' ; for
p in $(seq 1 6 13) ; do echo -n " >(grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f
2- 1>&2)" ; done ; echo -n ' > /dev/null' ) 2>&1 ) & } ; sleep 9 ; kill -INT
$(pstree -p $! | sed -n 's/.*mongoexport(([0-9]*)).*/1/p'); fg
(1/s^1/t) gzip -2 is .62 time and 1.12 size of gzip -6
(1/s^7/t) gzip -6 is 1.00 time and 1.00 size of gzip -6
(1/s^13/t) xz -6 is 14.00 time and .76 size of gzip -6
enfase na metaprogramacao
vermelho: interpretadores, o bash do meio teve que receber o script
como arquivo e nao como stdin pq o bc dentro dele precisava da stdin
pra ele.
azul: geracao dinamica dos scripts
fundo vermelho: primeiro bash, que vai gerar um bash script que,
finalmente, vai escrever as variaveis para o bc
fundo verde: codigo estatico para o bc
fundo azul: script shell que vai receber com stdin um codigo bc e vai
parsear a saida do bc
verde: o cat eh necessario pq os redirects se comportam diferentes do
pipe, sem ele as variaveis viriam depois do codigo bc que usa elas
branco com fundo vermelho: tem um race-condition na geracao
paralela das variaveis do bc, tive que rodar varias vezes pra conseguir
16. pstree dos comandos no slide anterior
bash─┬─bash───bash─┬─cat
│ ├─mongoexport
│ └─tee─┬─10*[bash─┬─sed]
│ │ ├─time───xz]
│ │ └─wc]
│ └─9*[bash─┬─sed]
│ ├─time───gzip]
│ └─wc]
└─bash─┬─bc
└─tee───3*[bash─┬─cut]
├─grep]
├─sort]
└─tail]
vermelho: os pontos chaves de processamento
azul: o unico input que flui por toda essa arvore
verde: tees que forkam os processos e pipes
17. código bash, resultado do primeiro trecho
mongoexport -d sambatech -c portal 2>/dev/null | tee
>(/usr/bin/time -f 't[00]=%U+%S' xz -0 | wc -c | sed 's/^/s[00]=/' 1>&2)
>(/usr/bin/time -f 't[01]=%U+%S' xz -1 | wc -c | sed 's/^/s[01]=/' 1>&2)
>(/usr/bin/time -f 't[02]=%U+%S' xz -2 | wc -c | sed 's/^/s[02]=/' 1>&2)
>(/usr/bin/time -f 't[03]=%U+%S' xz -3 | wc -c | sed 's/^/s[03]=/' 1>&2)
>(/usr/bin/time -f 't[04]=%U+%S' xz -4 | wc -c | sed 's/^/s[04]=/' 1>&2)
>(/usr/bin/time -f 't[05]=%U+%S' xz -5 | wc -c | sed 's/^/s[05]=/' 1>&2)
>(/usr/bin/time -f 't[06]=%U+%S' xz -6 | wc -c | sed 's/^/s[06]=/' 1>&2)
>(/usr/bin/time -f 't[07]=%U+%S' xz -7 | wc -c | sed 's/^/s[07]=/' 1>&2)
>(/usr/bin/time -f 't[08]=%U+%S' xz -8 | wc -c | sed 's/^/s[08]=/' 1>&2)
>(/usr/bin/time -f 't[09]=%U+%S' xz -9 | wc -c | sed 's/^/s[09]=/' 1>&2)
>(/usr/bin/time -f 't[11]=%U+%S' gzip -1 | wc -c | sed 's/^/s[11]=/' 1>&2)
>(/usr/bin/time -f 't[12]=%U+%S' gzip -2 | wc -c | sed 's/^/s[12]=/' 1>&2)
>(/usr/bin/time -f 't[13]=%U+%S' gzip -3 | wc -c | sed 's/^/s[13]=/' 1>&2)
>(/usr/bin/time -f 't[14]=%U+%S' gzip -4 | wc -c | sed 's/^/s[14]=/' 1>&2)
>(/usr/bin/time -f 't[15]=%U+%S' gzip -5 | wc -c | sed 's/^/s[15]=/' 1>&2)
>(/usr/bin/time -f 't[16]=%U+%S' gzip -6 | wc -c | sed 's/^/s[16]=/' 1>&2)
>(/usr/bin/time -f 't[17]=%U+%S' gzip -7 | wc -c | sed 's/^/s[17]=/' 1>&2)
>(/usr/bin/time -f 't[18]=%U+%S' gzip -8 | wc -c | sed 's/^/s[18]=/' 1>&2)
>(/usr/bin/time -f 't[19]=%U+%S' gzip -9 | wc -c | sed 's/^/s[19]=/' 1>&2) | cat > /dev/null
fundo vermelho: codigo de bash que vai gerar codigo de bc
19. código bash, resultado do terceiro trecho
bc | tee
>(grep 's^1/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
>(grep 's^7/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
>(grep 's^13/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
> /dev/null
fundo azul: ultimo script bash que recebe o stdin com o bc e faz o pos-
processamento do seu output
20. Como tirar o race-condition?
$ while true ; do { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { {
echo -n "$d: " ; ls /dev/fd/ ; } 1>&2 ; " ; done ; echo -n 'seq 2000
2>/dev/null | tee' ; for z in 0 1 ; do for l in $(seq 0 9); do test $z
= 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't[$z$l]=%U+%S'
2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc -c |
sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n '
>/dev/null' ; for d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 |
grep '' " ; done ; } | tee /dev/fd/2 | bash ; echo 123 ; } | sed 's/^/b:
/' | grep =.*= ; } 2>/dev/null ; done
vermelho: grep "" line buffered dos varios file descriptors; o ultimo grep
testa por linhas encavaladas
azul: debug supimido pelo 2>/dev/null no final
mas isso eh uma outra história...
21. Mais consistência no teste dos
parâmetros de compressão
$ { { { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { " ; done ; echo -n
'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for z in 0 1 ; do
for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't
[$z$l]=%U+%S' 2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc
-c | sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n ' > /dev/null' ; for
d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 | sed '' " ; done ; } | bash ;
echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0;x<=1;x++) { for(l=x;l<=9;l++)
{ i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print 1/s^p/t," (1/s^",p,"/t) "; if(x==1) print
"gzip" else print "xz"; print" -",l," is ",t," time and ",s," size than gzip -6n" }
} }' ; } | bash <( echo -n 'bc | { tee ' ; for p in $(seq 1 6 13) ; do echo -n " >
(grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)" ; done ; echo -n '
>/dev/null ; } 2>&1 | grep "" ' ) ; } & } ; A=$! ; { { sleep 9 ; kill -INT $(pstree
-laAp $A | sed -n 's/^[^,]*<mongoexport,([0-9][0-9]*)>.*/1/p') ; } & } ;
B=$! ; fg %- || { echo "finishing..." ; kill -INT $( { pstree -laAp $A ; pstree -
laAp $B ; } | sed -n 's/^[^,]*,([0-9][0-9]*)>.*/1/p') ; } ; } #xz-vs-gz
vermelho: separacao dos streams em multiplos file descriptors
azul: limpando rodadas incompletas
cinza: tag
22. Bom, mas voltando ao dump do banco,
com xv fica assim:
$ ( export H=localhost D=sambatech C=portal; export F="
dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)";
mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c
-N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" |
mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 |
cut -c 2- )" | xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 )
raw: 54.7MB 0:00:38 [ 1.4MB/s] [ <=> ]
xz: 7.85MB 0:00:38 [ 176kB/s] [ <=> ]
rows: 32.7k 0:00:38 [ 818/s ] [=> ] 44% ETA 0:00:47
vermelho: o xz na linha do dump de bando
23. Como está idle e o xv é o gargalo, o
negócio é paralelizar de algum jeito
top - 12:15:16 up 53 days, 13:13, 27 users, load average: 0.69, 0.24, 0.29
Tasks: 338 total, 3 running, 334 sleeping, 0 stopped, 1 zombie
Cpu0 : 99.7%us, 0.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu1 : 2.3%us, 1.3%sy, 0.0%ni, 96.4%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu2 : 18.3%us, 1.7%sy, 0.0%ni, 79.4%id, 0.7%wa, 0.0%hi, 0.0%si,
Cpu3 : 2.0%us, 1.3%sy, 0.0%ni, 96.7%id, 0.0%wa, 0.0%hi, 0.0%si,
Mem: 4107620k total, 1334364k used, 2773256k free, 24868k buffers
Swap: 4192960k total, 2373236k used, 1819724k free, 259280k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20507 zed 20 0 99420 93m 736 R 100 2.3 1:06.74 xz
20504 zed 20 0 17688 4304 2880 S 17 0.1 0:11.88 mongoexport
20506 zed 20 0 4056 780 576 S 2 0.0 0:01.45 pv
20505 zed 20 0 4056 732 576 S 0 0.0 0:00.25 pv
vermelho: xv como gargalo, no lugar do mongoexport; processos single
thread
24. pixz ou parallel?
$ git clone https://github.com/vasi/pixz.git
$ cd pixz/
$ make
pixz.h:1: fatal error: lzma.h: No such file or directory
$ sudo apt-get install lzma-dev
Setting up lzma-dev (4.43-14ubuntu2) ...
$ make
pixz.h:1: fatal error: lzma.h: No such file or directory
$ grep lzma README
* liblzma 4.999.9-beta-212 or later (from the xz
distribution)
vermelho: muitos poblemas para instalar um xz paralelo
25. parallel? não?..
$ parallel
The program 'parallel' is currently not installed. You
can install it by typing: sudo apt-get install moreutils
$ apt-get install moreutils
...
$ parallel --help
parallel: invalid option -- '-'
parallel [OPTIONS] command -- arguments
for each argument, run command with argument, in
parallel
parallel [OPTIONS] -- commands
run specified commands in parallel
vermelho: o parallel do ubuntu parece muito velho
26. Bem vindo ao mundo real...
$ wget http://ftp.gnu.org/gnu/parallel/parallel-20110822.tar.bz2
$ tar xvf parallel-20110822.tar.bz2
$ cd parallel-20110822
$ ./configure && make -j 4
$ ./src/parallel --help
Usage:
parallel [options] [command [arguments]] < list_of_arguments
parallel [options] [command [arguments]] (::: arguments|:::: argfile(s))...
cat ... | parallel --pipe [options] [command [arguments]]
-j n Run n jobs in parallel
-k Keep same order
vermelho: parallel sendo buildado em paralelo :)
27. Quantos jobs pra compressão?...
$ uptime ; for j in $(seq $(( $(grep -c ^bogomips /proc/cpuinfo) * 4
)) ) ; do echo "$j: $( ( time ( mongoexport -d sambatech -c portal
2>/dev/null | head -20000 | parallel -j $j --pipe nice xz | xz -t ) )
2>&1 | tr "n" "t" )" ; done ; uptime
14:46:08 up 53 days, ... load average: 0.07, 1.29, 2.23
1: real 0m18.759s user 0m26.602s sys 0m2.448s
2: real 0m11.673s user 0m24.746s sys 0m1.916s
3: real 0m9.033s user 0m23.553s sys 0m1.460s
4: real 0m8.561s user 0m23.093s sys 0m1.468s
...
8: real 0m7.645s user 0m23.173s sys 0m1.356s
...
16: real 0m7.087s user 0m22.153s sys 0m1.260s
14:48:27 up 53 days, ... load average: 6.49, 2.88, 2.66
vermelho: parallel configurado para fazer split de um pipe para varios
xz
azul: numero de jogs testados: de 1 ateh 4 vezes o numero de cores
verde: o nice eh importante senao o mongoexport nao tem cpu o
suficiente para alimentar todos os xz
laranja: note que usei o time built-in, porque estou fazendo time de um
parenteses (sub-shell)
28. Achievement Unlocked
$ ( export H=localhost D=sambatech C=portal; export F="
dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)";
mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c
-N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" |
mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 |
cut -c 2- )" | parallel --pipe nice xz | pv -c -N xz | tee $F.xz |
md5sum > $F.md5 ) #dump-mongo-collection
raw: 48.9MB 0:00:10 [5.64MB/s] [ <=> ]
xz: 6.38MB 0:00:10 [ 678kB/s] [ <=> ]
rows: 29.6k 0:00:10 [3.27k/s] [==> ] 40% ETA 0:00:14
vermelho: o parallel na linha de dump do banco; o default do parallel eh
um para cada core
cinza: tag, pra lembrar do oneliner
30. E se eu quiser mandar pra net?
$ scp dump.xz example.com:
dump.xz 100% 97MB 32.3MB/s 00:03
31. Putz, mas e a integridade?
$ ssh example.com "xz -vd > dump" < dump-*.xz
(stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10
vermelho: descomprime do lado remoto para verificar integridade
azul: arquivo armazenado estah descomprimido
fundo amarelo: roda remoto
32. Mas não queria descomprimir lá...
$ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F"
vermelho: tee para testar apenas
azul: armazena o arquivo comprimido
fundo amarelo: roda remoto
33. Ueh?.. kd o output?
$ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F"
$
vermelho: xz -tv normalmente imprime na stderr
fundo amarelo: roda remoto
34. Aff, a stderr nao passou pelo ssh,
claro... mas tudo bem.
$ time ( F="dump.xz"; ssh example.com "{ tee >(xz -vt) > '$F' ; }
2>&1" < "$F" )
(stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10
real 0m10.580s
user 0m2.963s
sys 0m0.243s
vermelho: redirecionando a stderr para stdout antes de sair do ssh
azul: xz e seu output
verde: stdout dentro das chaves precisa ser preservada para o arquivo
laranja: tempo total do processo
fundo amarelo: roda remoto
note que ssh -t nao funciona: Pseudo-terminal will not be allocated
because stdin is not a terminal.
35. Soh que descomprimir o xz pra validar
eh overkill... que tal usar o md5sum?
$ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) >
(echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp -
d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee
>(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n
"s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee
'"$F"' | md5sum -c $FIFO/b | cut -c 4-'
vermelho: multiplexa arquivo, seu md5 e tamanho
azul: demultiplexa do outro lado
verde: fifo a carrega o tamanho do arquivo e o fifo b o md5
roxo: trap para apagar os fifos quando sair, mesmo se cancelado
fundo amarelo: roda remoto
36. Droga, deadlock! E agora?...
$ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) >
(echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp -
d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee
>(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n
"s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee
'"$F"' | md5sum -c $FIFO/b | cut -c 4-'
^C
vermelho: multiplexa arquivo, seu md5 e tamanho
azul: demultiplexa do outro lado
verde: fifo a carrega o tamanho do arquivo e o fifo b o md5
roxo: trap para apagar os fifos quando sair, mesmo se cancelado
fundo amarelo: roda remoto
o deadlock causado por arquivos maiores que o buffer do pipe porque
os metadados estao no final (nao tem como saber o md5 no inicio),
mas para comecar a gravar o arquivo no disco, e permitir que o pipe
avance, preciso do size e do md5 nos fifos
37. kkkk, tem um jeito bem mais simples!
$ time ( F=dump.xz; pv "$F" | { tee >(md5sum 1>&2) | ssh
example.com 'tee '"$F"' | md5sum' ; } 2>&1 | sort -u | wc -l |
grep -qx 1 && echo OK || echo ERR )
96.9MB 0:00:03 [28.7MB/s] [===========>] 100%
OK
real 0m3.212s
user 0m2.373s
sys 0m0.307s
$ ### time ssh + xz:
$ #real 0m10.580s
$ #user 0m2.963s
$ #sys 0m0.243s
vermelho: o md5 eh rodado local (para stderr) e remoto (stdout)
azul: no final verificamos se tem apenas uma variedade de arquivo
laranja: o md5sum eh bem mais rapido que o xz -t
fundo amarelo: roda remoto
38. Jah tah bem mais leve;
que tal tirar o ssh tb?
$ { F="dump.xz" ; H="example.com" ; P=9999 ; pv "$F" | tee >
(md5sum 1>&2) >(wc -c 1>&2) >( sed -n '' | ssh "$H" "{ netcat
$(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee
'$F' >(wc -c 1>&2) | md5sum ; } 2>&1" 1>&2 ) >(netcat -l $P)
>/dev/null ; } 2>&1 | tee >( sort -u | wc -l | grep -qx 2 && echo
OK || echo ERR )
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
19335168
85d99c783b541885b05ef263c1f35cdc -
ERR
azul: todo o fluxo foi controlado por um unico pipe, mas o ssh que nao
deveria receber os dados teve seu input editado
vermelho: deu erro na transmissao porque o pipe produtor fechou
antes do netcat receptor terminar; esse erro eh intermitente
verde: note que a seguranca eh fraca, se alguem estiver tentando
pegar o arquivo dessa porta vai conseguir, isso nao eh pra redes
desprotegidas
fundo amarelo: roda remoto
39. Morreu antes de terminar o envio,
o receptor vai ter que autorizar o fim
$ { F="dump.xz" ; H="example.com" ; P=$(printf "39%03d"
$RANDOM | cut -c 1-5) ; { ssh "$H" "{ netcat $(set | grep [S]
SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee '$F' >(wc -c
1>&2) | md5sum ; } 2>&1" 1>&2 ; } | { < "$F" tee >(md5sum
1>&2) >(wc -c 1>&2) >(netcat -l $P) >/dev/null ; } ; } 2>&1 | tee
>( sort -u | wc -l | grep -qx 2 && echo OK || echo ERR )
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
OK
vermelho: o ssh estah dominando o pipe
azul: o pipe soh morre quando o ssh morrer
roxo: o produtor vem depois
verde: um pouco mais de segurança por obscuridade
fundo amarelo: roda remoto
isso funcionou no ubuntu 10.10, mas nao funcionou no archlinux 2011-
09
40. Cuidados com o netcat
$ { F="dump.xz" ; H=example.com ; P=$(printf "39%03d" $RANDOM |
cut -c 1-5) ; S=$(stat -c %s "$F") ; { { pv "$F" | { tee >(md5sum 1>&2) |
{ netcat -l -p $P 2>/dev/null || netcat -l $P ; } ; } 2>&1 ; } & } ; A=$! ; ssh
$H "{ H=$(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") ; {
netcat -w 5 -c $H $P 2>/dev/null || netcat -w 5 $H $P ; } | tee '$F' >
(md5sum 1>&2) | pv -n -s $S 2>&1 >/dev/null | while read X ; do test "
$X" -ge 100 && kill -INT $(pstree -laAp $$ | sed -n 's/^[^,]*<netcat,
([0-9][0-9]*)>.*/1/p') &>/dev/null ; done ; } 2>&1" ; kill -INT $(pstree -
laAp $A | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; } |
uniq | wc -l | grep -qx 1 && echo OK || echo ERR #netcat-send
537MB 0:00:04 [ 110MB/s] [=================>] 100%
OK
vermelho: por causa das inconsistencias de terminacao do netcat, tive
que monitorar o progresso da transmissao e matar explicitamente o
netcat
azul: alternativas de parametros do netcat em sistemas diferentes
verde: melhoria de tempo
a inversao do consumidor com o produtor acrescentou um race-
condition, o ssh podia tentar conectar no netcat local antes dele existir
(teoricamente)
e pior, em alguns sistemas o netcat precisa de um "-p" alem do "-l" e,
pior, algumas versões não fecham, sem o "-c", quando o pipe fecha, o
que faz o script anterior travar. nesse ultimo caso o ideal é usar a
mesma técnica do mongoexport; por em background e matar com
pstree
requisitos:
1) netcat escutando tem que ser criado primeiro;
2) logo o netcat do ssh tem que conectar depois,
41. 3) ateh porque ele que tem a autoridade de terminar pq o receptor
terminar depois.
4) foi preciso fazer fallback entre os dois estilos de netcat;
5) soh o netcat que conecta fecha consistentemente com o pipe, mas,
como o ssh precisa conectar ./ fechar e ele nao tem o arquivo, foi
preciso monitorar a transmissao com pv e matar explicitamente o
netcat
melhor ter um netcat confiavel, mas se nao foi possivel o slide
apresenta um contorno para o problema
42. Mas serah que houve vantagem?
$ time !?#netcat-send
537MB 0:00:04 [ 110MB/s] [============>]
100%
real 0m5.298s
user 0m0.063s
sys 0m0.003s
OK
$ time !?scp
dump.xz 100% 537MB 35.8MB/s 00:15
real 0m14.658s
user 0m11.123s
sys 0m1.130s
vermelho: tempos do netcat e do ssh
44. E se forem vários arquivos?
$ find dumps/ -type f -printf "%10s %pn"
101632892 dumps/b/dump.xz.2
65536 dumps/b/a/dump.xz.1
0 dumps/a/3688
0 dumps/a/8958
(... total de 10000 arquivos no dumps/a/)
0 dumps/a/4308
0 dumps/a/3338
$ scp -r dumps/* example.com:dump-backup/
(...)
3338 100% 0 0.0KB/s 00:00
dump.xz.2 100% 97MB 48.5MB/s 00:02
dump.xz.1 100% 64KB 64.0KB/s 00:00
vermelho: scp recursivo
45. Mas quero selecionar melhor
$ tar c -C dumps/ --exclude=./a . | ssh example.com "tar xv -C
dump-backup/"
./
./b/
./b/dump.xz.2
./b/a/
./b/a/dump.xz.1
vermelho: tarpipe
azul: um lista menor foi selecionada
46. Progresso...
$ TAR="tar c -C dumps/ --exclude=./a ." ; $TAR | pv -s $($TAR |
wc -c) | ssh example.com "tar x -C dump-backup/"
97MB 0:00:02 [32.8MB/s] [================>] 100%
vermelho: pv mostrando o progresso a partir de uma rodada previa do
mesmo tar
47. Não rola fazer o tar duas vezes...
$ S="dumps" ; D="dump-backup" ; X="--exclude=a" ;
H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --
apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C
'$D' "
96.9MB 0:00:02 [33.5MB/s] [==============>] 100%
$ S="dumps" ; D="dump-backup" ; X="" ; H=example.com ; tar
c -C "$S" $X . | pv -s $(cd "$S" && du --apparent-size --block-
size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' "
102MB 0:00:03 [33.7MB/s] [================] 104%
vermelho: du do tamanho dos arquivos, isso eh impreciso quando tem
arquivos pequenos no meio
azul: filtros para um arquivo grande e um arquivo grande com muitos
pequenos, respectivamente
48. Mas assim tem muita imprecisão... :/
$ S="dumps" ; D="dump-backup" ; X="--exclude=./a --exclude=.
/b/d*" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" &&
du --apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar
x -C '$D' "
70kB 0:00:00 [ 544kB/s] [====> ] 29%
$ S="dumps" ; D="dump-backup" ; X="--exclude=./b/d*" ;
H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --
apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C
'$D' "
4.95MB 0:00:00 [7.77MB/s] [================] 1267%
vermelho: du do tamanho dos arquivos, isso eh impreciso
especialmente se tiverem varios arquivos pequenos, por causa do
overhead do tar, mas quebra um galho.
azul: filtros para um arquivo muito pequeno e muitos arquivos
pequenos, respectivamente
49. Vou tentar incorporar o overhead do tar
na conta do tamanho total
$ for X in "('./a/*' './b/a/*')" "()" "('./a/*' './b/d*')" "('./b/d*')" ; do eval
"X=$X" ; E=""; N=""; for x in "${X[@]}" ; do test -n "$N" && N="$N -and"
; N="$N -not -path '$x'" ; E="$E --exclude='$x'" ; done ; echo "${X[@]}
=> $E => $N"; S="dumps" ; D="dump-backup" ; H=example.com ; eval
"tar c -C '$S' $E ." | pv -s $(cd "$S" && eval "find . $N -printf '%y %s %
pn'" | awk 'BEGIN { sum=0 } { s=length($0)-length($2)-1+($1 ~ /f/ ? $2
: 0) ; c=512; r=s%c; s=s+(r?c-r:0) ; sum+=s } END { chunk=10240;
remain=sum%chunk; print sum+(remain?chunk-remain:0) }') | ssh $H
"tar x -C '$D' " ; done #tar-pipe-ssh
96.9MB 0:00:02 [34.7MB/s] [====================>] 100%
102MB 0:00:02 [41.5MB/s] [====================>] 100%
70kB 0:00:00 [ 835kB/s] [====================>] 100%
4.95MB 0:00:00 [21.2MB/s] [====================>] 100%
vermelho: find + awk calculando, respectivamente os arquivos da
mesma maneira que o tar vai fazer
azul: todas as configurações de exceção mostradas antes
passo pelos arquivos duas vezes, mas isso tem uma certa vantagem,
que a primeira eh soh pela listagem de diretorios (curta e que deve
ficar cacheada depois dessa passada) e a segunda pelo conteudo dos
arquivos.. que ficam em lugares bem diferentes do disco, entao
possivelmente fazendo isso estou evitando muito io-wait em seek
51. Não tentem isso em casa
Nesse fantástico mundo dos one-liners se pode usar o history
como repositório de comandos; basta comentá-los com
tags memoráveis e deixar o history bem, bem grande:
$ export HISTSIZE=999999999 HISTFILESIZE=999999999
$ echo !! >> $(ls -S ~/.{,bash}{rc,{,_}profile} 2>/dev/null | head
-1)
$ ls -ltr #lista-recentes
...
$ !?#lista-re
ls -ltr #lista-recentes
...
52. Referência
GNU bash, version 4.1.5(1)-release (i686-pc-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc. (*)
GNU bash, version 4.2.10(2)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc. (+)
License GPLv3+: GNU GPL version 3 or later <http://gnu.
org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
(*) Ubuntu 10.10
(+) Arch Linux 2011-09