我正在将一个相当大的文件加载到 postgresql 数据库中。为此,我首先split
在文件中使用来获取较小的文件(每个 30Gb),然后使用GNU Parallel
和将每个较小的文件加载到数据库中psql copy
。
问题是大约需要 7 个小时来分割文件,然后开始为每个核心加载一个文件。我需要的是一种方法来告诉split
每次完成写入文件时将文件名打印到 std 输出,以便我可以将其通过管道传输,Parallel
并在split
完成写入时开始加载文件。像这样的东西:
split -l 50000000 2011.psv carga/2011_ | parallel ./carga_postgres.sh {}
我已阅读split
手册页,但找不到任何内容。有没有办法用split
或任何其他工具来做到这一点?
答案1
使用--管道:
cat 2011.psv | parallel --pipe -l 50000000 ./carga_postgres.sh
它需要 ./carga_postgres.sh 从标准输入读取而不是从文件读取,并且对于 GNU Parallel 版本 < 20130222 来说速度很慢。
如果您不需要正好 50000000 行,则 --block 更快:
cat 2011.psv | parallel --pipe --block 500M ./carga_postgres.sh
这将在 \n 上传递大约 500MB 分割的块。
我不知道 ./carga_postgres.sh 包含什么,但我猜测它包含带有用户名密码的 psql。在这种情况下,您可能需要使用 GNU SQL(它是 GNU Parallel 的一部分):
cat 2011.psv | parallel --pipe --block 500M sql pg://user:pass@host/db
主要好处是您不需要保存临时文件,但可以将所有文件保留在内存/管道中。
如果 ./carga_postgres.sh 无法从标准输入读取,但必须从文件读取,则可以将其保存到文件中:
cat 2011.psv | parallel --pipe --block 500M "cat > {#}; ./carga_postgres.sh {#}"
大型工作常常半途而废。 GNU Parallel 可以通过重新运行失败的作业来帮助您:
cat 2011.psv | parallel --pipe --block 500M --joblog my_log --resume-failed "cat > {#}; ./carga_postgres.sh {#}"
如果失败,那么您可以重新运行上述命令。它将跳过已经成功处理的块。
答案2
为什么不将 --pipe 和 --pipepart 与 GNU Parallel 一起使用?这消除了额外的 cat 并开始直接读取磁盘上的文件:
parallel --pipe --pipepart -a 2011.psv --block 500M ./carga_postgres.sh
答案3
我发现这里发布的答案非常复杂,所以我在 Stack Overflow 上询问,我得到了这回答:
如果你使用GNU split
,您可以使用--filter
选项来执行此操作
'--filter=command'
使用此选项,不是简单地写入每个输出文件,而是通过管道写入每个输出文件的指定 shell 命令。命令应使用 $FILE 环境变量,该变量为每次调用该命令设置为不同的输出文件名。
您可以创建一个shell脚本,该脚本创建一个文件并在后台启动carga_postgres.sh
#! /bin/sh
cat >$FILE
./carga_postgres.sh $FILE &
并使用该脚本作为过滤器
split -l 50000000 --filter=./filter.sh 2011.psv
答案4
打印文件名的另一种方法split
是检测文件何时准备好。在 Linux 上,您可以使用inotify设施,特别是inotifywait
公用事业。
inotifywait -m -q -e close_write --format %f carga | parallel ./carga_postgres.sh &
split -l 50000000 2011.psv carga/2011_
你需要inotifywait
手动杀死。自动杀死它有点困难,因为存在潜在的竞争条件:如果您一完成就杀死它split
,它可能已经收到了尚未报告的事件。为了确保报告所有事件,请计算匹配的文件。
{
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)