node.jsでgcsからファイルを読み込む
gcsからファイルを読み込む方法を探すと、よくdownload()を使用する例が紹介されています。
conststorage=newStorage();constbucket=storage.bucket('test-20180903');constfile=bucket.file('sample');file.download().then(function(data){res.status(200).json({'result':data.toString('utf-8')});});
download()だと、サイズの大きいファイルを読み込むとメモリ不足に
cloud functionsだと2Gまでしかメモリ拡張できないので、gcs側にファイル配置する際に、ファイルサイズを小さく分割しながら.·゜゜·(/。\)·゜゜·.
@google-cloud/storageのソースを見ている
download()以外に、createReadStream()なるものが!
file.ts
conststorage=newStorage();constbucket=storage.bucket('my-bucket');constfs=require('fs');constremoteFile=bucket.file('image.png');constlocalFilename='/Users/stephen/Photos/image.png';remoteFile.createReadStream().on('error',function(err){}).on('response',function(response){// Server connected and responded with the specified status and headers.}).on('end',function(){// The file is fully downloaded.}).pipe(fs.createWriteStream(localFilename));
createReadStream()でらファイルを読み込む
gcsからファイルを読み込む方法を探すと、よくdownload()を使用する例が紹介されています。
conststorage=newStorage();constbucket=storage.bucket('test-20180903');constfile=bucket.file('sample');file.download().then(function(data){res.status(200).json({'result':data.toString('utf-8')});});
download()だと、サイズの大きいファイルを読み込むとメモリ不足に
cloud functionsだと2Gまでしかメモリ拡張できないので、gcs側にファイル配置する際に、ファイルサイズを小さく分割しながら.·゜゜·(/。\)·゜゜·.
@google-cloud/storageのソースを見ている
download()以外に、createReadStream()なるものが!
file.ts
conststorage=newStorage();constbucket=storage.bucket('my-bucket');constfs=require('fs');constremoteFile=bucket.file('image.png');constlocalFilename='/Users/stephen/Photos/image.png';remoteFile.createReadStream().on('error',function(err){}).on('response',function(response){// Server connected and responded with the specified status and headers.}).on('end',function(){// The file is fully downloaded.}).pipe(fs.createWriteStream(localFilename));
createReadStream()で、ストリーム処理に
↑サンプルをもとに、mongodbへの登録処理を実装してみると、なぞのエラーが・・・
responseイベントは、ノンブロッキング(非同期)処理されるので、mongodbへのアクセスが多すぎたみたい
gcsからサイズの大きいjsonlファイルをmongodbに登録する
createReadStream()で作成したストリームを、ブロッキング(同期)処理したら大丈夫でした。
exports.execute=async(event,context)=>{constclient=awaitmongo.connect(process.env.MONGODB_URL,{useNewUrlParser:true,useUnifiedTopology:true})letrs=nulltry{constdb=client.db(process.env.MONGODB_DATABASE)rs=awaitstorage.bucket(bucket).file(pubsubMessage.name).createReadStream();forawait(constlineofreadLines(rs)){constjson=JSON.parse(line)json.lastModified=newDate()// 更新日時があたらしかった場合更新する constresult=awaitdb.collection(collection).replaceOne({_id:json._id,updateDttm:{$lte:json.updateDttm}},json,{upsert:false})if(result.matchedCount==0){// 未登録の場合があるので登録してみる。 try{awaitdb.collection(collection).insertOne(json)}catch(err){if(err.message.indexOf("E11000")<0){throwerr}}}}}catch(err){throwerr}finally{if(client){client.close()}if(rs){rs.destroy()}}functionreadLines(rs){constoutput=newstream.PassThrough({objectMode:true});constrl=readline.createInterface(rs,{});rl.on("line",line=>{output.write(line);});rl.on("close",()=>{output.push(null);});returnoutput;}}
for await...of 文を使うことで、ブロッキング(同期処理)にできました!
node v12からは、標準のreadline.createInterface()が、async iterable を返すようになるようなので、自前のreadLines()もいらなくなるようです。スッキリ書けますね。