プロセスが約350MBのRAMに制限されているシステムの一部を開発しています。cx_Oracleを使用して、処理のために外部システムからファイルをダウンロードします。
外部システムはファイルをBLOBとして保存し、次のようにファイルを取得できます。
# ... set up Oracle connection, then
cursor.execute(u"""SELECT filename, data, filesize
FROM FILEDATA
WHERE ID = :id""", id=the_one_you_wanted)
filename, lob, filesize = cursor.fetchone()
with open(filename, "w") as the_file:
the_file.write(lob.read())
lob.read()
MemoryError
300〜350 MBを超えるファイルをヒットすると、明らかに失敗するため、一度にすべてを読み取るのではなく、次のようなものを試しました。
read_size = 0
chunk_size = lob.getchunksize() * 100
while read_size < filesize:
data = lob.read(chunk_size, read_size + 1)
read_size += len(data)
the_file.write(data)
残念ながら、私たちはまだMemoryError
数回の反復の後で得ます。時間lob.read()
がかかり、最終的にメモリ不足状態になると、毎回lob.read()
データベースから(chunk_size + read_size)バイトをプルしているように見えます。つまり、バッファがかなり小さい場合でも、読み取りにはO(n)時間とO(n)メモリが必要です。
これを回避するために、次のような方法を試しました。
read_size = 0
while read_size < filesize:
q = u'''SELECT dbms_lob.substr(data, 2000, %s)
FROM FILEDATA WHERE ID = :id''' % (read_bytes + 1)
cursor.execute(q, id=filedataid[0])
row = cursor.fetchone()
read_bytes += len(row[0])
the_file.write(row[0])
これは一度に2000バイト(argh)をプルし、永久にかかります(1.5GBファイルの場合は2時間程度)。なぜ2000バイトなのか?Oracleのドキュメントによると、dbms_lob.substr()
戻り値はRAWに格納されます。RAWは2000バイトに制限されています。
dbms_lob.substr()
結果をより大きなデータオブジェクトに保存し、一度に数メガバイトを読み取る方法はありますか?cx_Oracleでこれを行うにはどうすればよいですか?