FunctionsでGoogle Cloud Storageにデータをアップロードするならupload()ではなくsave()がお勧め


概要

Google Cloud Storageの入門して間もない初心者が対象なので、タイトルで意味の分かる方はスルーでOKです。

各ベンダーの提供するFunctionsはサーバーレスであることから、ファイルへの入出力を推奨していない。
永続的なものはStorageに保存し、一時的なものはメモリー(基本は変数)を使用することになる。
しかしGoogle Cloud Storageのドキュメントを見ると、ファイルを前提としたupload()が使用されています。
Functionsから使用するサンプルとしては適していないため、Functions用に残しておきます。

私はNode.jsの信者なので、その他の言語はドキュメントで該当するメソッドをお調べください

読者対象

  • Google Cloud Functionsの初心者
  • Google Cloud Storageの初心者

解説

まずは公式ドキュメントのおさらい

遷移先でコードが表示されない場合は、「コードサンプル」=>「NODE.JS」の順でクリックしてください。

アップロード
https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-nodejs

コードを抜粋します。
upload()は第一引数にアップロード対象のファイル名を指定しています。


await storage.bucket(bucketName).upload(filename, {
  // Support for HTTP requests made with `Accept-Encoding: gzip`
  gzip: true,
  // By setting the option `destination`, you can change the name of the
  // object you are uploading to a bucket.
  metadata: {
    // Enable long-lived HTTP caching headers
    // Use only if the contents of the file will never change
    // (If the contents will change, use cacheControl: 'no-cache')
    cacheControl: 'public, max-age=31536000',
  },
});

Functionsはベンダーによって仕様が異なりますが、サーバーにファイルが作れないか、ファイル保存APIを通してメモリーに保存することになります。
(Functionsはサーバーを管理しないため、次に呼び出された際、別のサーバーから実行され、ファイルがないことに起因しているためだと思います)
そのため、アップロードしようにもファイルが作れなかったり、後始末が面倒だったりします。
以下のURLのソースはダウンロードした画像を変換してアップロードしていますが、面倒なのが何となくわかると思います。
https://firebase.google.com/docs/storage/extend-with-functions?hl=ja#download_transform_and_upload_a_file
一時ファイルが面倒なのと、ストリームを使用することで回避できるアドバイスはこちら。
https://cloud.google.com/functions/docs/bestpractices/tips#always_delete_temporary_files

そういうわけでファイルを通さないようにやり取りできないか調べました。

Cloud Storageのドキュメントをあさってみる

唯一見つかったのはこれです。
https://cloud.google.com/storage/docs/boto-plugin#streaming-transfers

サードパーティのライブラリを使用したストリーミング転送とファイルを使用した例が交互に載っています。
コードから見てわかるようにストリーミング転送の方ではファイル名が出てきません。
Functionsではこれがやりたいわけですが、できたら公式ライブラリでこれがすんなりできるとうれしい。

APIリファレンスを眺めてみた

Node.jsのライブラリではFile操作はFileオブジェクトが担当するため、APIリファレンスを見ます。
https://googleapis.dev/nodejs/storage/latest/File.html

キーワードはストリーミングなのでStreamでヒットするとうれしいのですが…createWriteStream()があります!
見ると出力先にファイルを使っているしごちゃごちゃしてとっつきにくい。
ファイル使わなくても出力先をメモリーストリームに変更すればこれでもいいんですが、簡略化されたメソッドがないか探します。

救世主Save()登場

更にブラウザのページ内をstreamで検索候補をあさっているとsave()メソッドがヒットします。
https://googleapis.dev/nodejs/storage/latest/File.html#save

This is a convenience method which wraps File#createWriteStream.

Google翻訳先生お願いします。

これはFile#createWriteStreamをラップする便利なメソッドです。

サンプルコードはこうなっています。

const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const myBucket = storage.bucket('my-bucket');
const file = myBucket.file('my-file');
const contents = 'This is the contents of the file.';
//-
// If the callback is omitted, we'll return a Promise.
//-
file.save(contents).then(function() {});

contentsの文字列をStorageに保存しています。ファイル名は登場しません。
createWriteStream()より非常にすっきりしています。(実際にはasync function内で使用してawait file.save(contents);で済みます)
これでデータをファイルにいったん保存することなく、かつ簡潔にアップロードすることができるようになりました

ちなみにダウンロードの場合はdownload()がファイル名を必須としないため、以下のコードで変数にデータを格納できます。

const contents = await file.download();

まとめ

Save()メソッドによって、Functionsでも簡単にアップロードが可能になりました。
入門向けガイドだけ見ていると、ファイルへいったん保存してそれをアップロードしていたでしょう。
Functionsではファイルへの保存はメモリーを使用することになるため、後始末も必要になり面倒になるところでした。

APIリファレンスをちょっと探ってみるとすっきり解決することもある例として紹介してみました。
直接的な成果もないことのほうが多いかもしれませんが、意外な掘り出し物が見つかることも少なくありません。

Have a great day!