1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonAdvent Calendar 2021

Day 22

連携先のシステムがgzip対応してなくてもやりくりする方法

Last updated at Posted at 2021-12-21

はじめに

システム間の結合後をおさえるために、処理に必要なデータをファイルでやりとりするケースがあります。
それに加えて、やりとりするデータが大量の場合、ディスク使用量やIOをおさえるために、そのファイルをgzipなどで圧縮したいこともあります。

しかし、連携先のシステムがgzip圧縮したファイルでのやりとりを対応していない場合も、ままあります。
そういった場合に、自分のシステムのところだけではgzip圧縮させつつ、連携ファイルは圧縮なしのものでやりとりするための小技を集めたのが、この記事です。

基本方針

  • 連携先からファイルをもらう場合は、自分のサーバに、gzip圧縮したコピーを作成
  • 連携先にファイルを渡す場合は、自分のサーバにgzip圧縮したものを作成して、それを解凍しながらコピー

これによって、自分のサーバ上は圧縮したファイルのみを扱い、連携先には圧縮していない状態でやりとりできます。

Pythonはgzipファイルの操作はgzip.open で、比較的簡単にできます。

NFSで連携する場合

ファイルをもらう場合

/nfs/input/data.csv が、NFSに配置されるとします。
/input/data.csv.gz として、圧縮コピーします。


input_file = Path('nfs', 'input', 'data.csv')
compressed_file = Path('input', 'data.csv.gz')

with open(input_file, 'rb') as src, gzip.open(compressed_file, 'w') as dst:
     shutil.copyfileobj(src, dst)

単純なコピーなのでバイナリモードとshutil.copyfileobjを使います。

やってることは cat /nfs/input/data.csv | gzip > /input/data.csv.gz と同等です。

ファイルを渡す場合

バッチで、連携するデータをgzip圧縮したファイルとして、自分のサーバ上の /output/result.csv.gz に作成するとします。
これを、NFSの /nfs/output/result.csv となるように、解凍コピーします。

入力のときのコードの役割を逆にするだけです。


compressed_file = Path('output', 'result.csv.gz')
output_file = Path('nfs', 'output', 'result.csv')

with gzip.open(compressed_file, 'r') as src, open(output_file, 'wb') as dst:
     shutil.copyfileobj(src, dst)


やってることは zcat /output/result.csv.gz > /nfs/output/result.csv と同等です。

SFTPで連携する場合

セキュリティの関係で、SFTPでやりとりするケースもあります。
Pythonの場合、ParamikoというSFTPクライアントライブラリがあります。
このライブラリでは、リモート上のファイルに対して、file-like object相当のものを返してくれるメソッドがあります。

これを使えば、NFSのときとほとんど同じコードで、同じことができます。

ファイルをもらう場合


sftp_client = ...
remote_file = sftp_client.file('data.csv')
compressed_file = Path('input', 'data.csv.gz')

with open(remote_file, 'rb') as src, gzip.open(compressed_file, 'w') as dst:
     shutil.copyfileobj(src, dst)

ファイルを渡す場合


compressed_file = Path('output', 'result.csv.gz')

sftp_client = ...
output_file = sftp_client.file('result.csv')

with gzip.open(compressed_file, 'r') as src, open(output_file, 'wb') as dst:
     shutil.copyfileobj(src, dst)

Appendix

gzipファイルを解凍せずに、DBに取り込む

大量データの場合、RDBMSが提供するファイル取り込みの機能(MySQLだとLoad data構文)を使いたくなります。
このファイル取り込みの機能が、gzip圧縮されたものをサポートしていないケースがあります。

こういった場合、

  1. ファイルを解凍
  2. 解凍したファイルに対してファイル取り込みを実施

とやると、折角、圧縮してディスク使用量をおさえたのが、なかったことになります。

これを回避するために、

  1. 別プロセスで、named pipeに解凍した内容を書き込み
  2. named pipeに対してファイル取り込みを実施

とすれば、いったん、解凍したファイルをつくらずにすみます。
シェルだと、下記のようになります。

mkfifo /tmp/named_pipe
zcat data.csv.gz > /tmp/named_pipe &
mysql -e "LOAD DATA INFILE '/tmp/named_pipe' INTO TABLE sample_table"

Pythonでも、以下の2つと、これまでの内容を組み合わせることで同様のことができます。

運用時につかえるコマンド

zcatなど、gzipファイル用のコマンドのあとに、awkなどのやりたいコマンドにパイプしてつなげば、たいがいのことはできます。

まとめ

知ってしまえば、gzip圧縮したまま、様々な操作をする方法があり、そのやり方も、通常のファイルとさほど変わらない(gzipを扱うためのコマンドやクラスを一段かませるだけ)ことが多いです。

データが大きいほど、圧縮したときの差がでてくるので、大量データを扱うプロジェクトでは、地味に効いてくるテクニックです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?