Sử dụng GNU Parallel With Split


9

Tôi đang tải một tệp khá lớn vào cơ sở dữ liệu postgresql. Để làm điều này trước tiên tôi sử dụng splittrong tệp để nhận các tệp nhỏ hơn (mỗi tệp 30Gb) và sau đó tôi tải từng tệp nhỏ hơn vào cơ sở dữ liệu bằng cách sử dụng GNU Parallelpsql copy.

Vấn đề là phải mất khoảng 7 giờ để phân chia tệp, và sau đó nó bắt đầu tải một tệp cho mỗi lõi. Những gì tôi cần là một cách để nói splitđể in tên tệp thành đầu ra std mỗi khi nó hoàn thành việc viết một tệp để tôi có thể dẫn nó đến Parallelvà nó bắt đầu tải các tệp tại thời splitđiểm viết xong. Một cái gì đó như thế này:

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

Tôi đã đọc các splittrang người đàn ông và tôi không thể tìm thấy bất cứ điều gì. Có cách nào để làm điều này với splithoặc bất kỳ công cụ nào khác không?

Câu trả lời:


13

Sử dụng --pipe:

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

Nó yêu cầu ./carga_postgres.sh để đọc từ stdin chứ không phải từ tệp và chậm đối với phiên bản GNU Parallel <20130222.

Nếu bạn không cần chính xác 50000000 dòng thì --block nhanh hơn:

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

Điều này sẽ vượt qua các phân chia khoảng 500 MB trên \ n.

Tôi không biết ./carga_postgres.sh chứa gì, nhưng tôi đoán là nó chứa psql với mật khẩu tên người dùng. Trong trường hợp đó, bạn có thể muốn sử dụng GNU SQL (là một phần của GNU Parallel):

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

Lợi ích chính là bạn không cần lưu các tệp tạm thời, nhưng có thể giữ tất cả trong bộ nhớ / ống dẫn.

Nếu ./carga_postgres.sh không thể đọc từ stdin, nhưng phải đọc từ tệp, bạn có thể lưu tệp vào tệp:

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

Những công việc lớn thường thất bại nửa chừng. GNU Parallel có thể giúp bạn bằng cách chạy lại các công việc thất bại:

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

Nếu điều này không thành công thì bạn có thể chạy lại ở trên. Nó sẽ bỏ qua các khối đã được xử lý thành công.


1
Nếu bạn có phiên bản GNU Parallel> 20140422 mới hơn, hãy sử dụng câu trả lời của @ RobertB với --pipepart. Nếu điều đó không hoạt động trực tiếp, hãy xem --fifo hoặc --cat có thể giúp bạn không.
Ole Tange

2

Tại sao không sử dụng --pipe VÀ --pipepart với GNU Parallel? Điều này giúp loại bỏ con mèo thừa và bắt đầu đọc trực tiếp từ tệp trên đĩa:

parallel --pipe --pipepart -a 2011.psv --block 500M ./carga_postgres.sh

1

Tôi thấy các câu trả lời được đăng ở đây là cách phức tạp nên tôi đã hỏi trên Stack Overflow và tôi đã nhận được câu trả lời này :

Nếu bạn sử dụng GNU split, bạn có thể làm điều này với --filtertùy chọn

'--filter = lệnh'
Với tùy chọn này, thay vì chỉ ghi vào từng tệp đầu ra, hãy ghi thông qua một đường ống đến lệnh shell được chỉ định cho mỗi tệp đầu ra. lệnh nên sử dụng biến môi trường $ FILE, được đặt thành một tên tệp đầu ra khác nhau cho mỗi lần gọi lệnh.

Bạn có thể tạo tập lệnh shell, tạo tập tin và bắt đầu carga_postgres.sh ở cuối nền

#! /bin/sh

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

và sử dụng tập lệnh đó làm bộ lọc

split -l 50000000 --filter=./filter.sh 2011.psv

0

Một cách khác để thực hiện splitin tên tệp là phát hiện khi các tệp đã sẵn sàng. Trên Linux, bạn có thể sử dụng tiện ích inotify và cụ thể là inotifywaittiện ích.

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

Bạn sẽ cần phải giết inotifywaitbằng tay. Giết nó tự động hơi khó vì có một điều kiện chủng tộc tiềm năng: nếu bạn giết nó ngay khi splitkết thúc, nó có thể đã nhận được các sự kiện mà nó chưa được báo cáo. Để đảm bảo rằng tất cả các sự kiện được báo cáo, hãy đếm các tệp phù hợp.

{
  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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.