LoginSignup
2
5

More than 5 years have passed since last update.

GoogleCloudStorage(GCS)へ安全にアップロードする

Last updated at Posted at 2017-09-01

環境

  • ruby歴 初心者
  • rails5
  • ruby 2.4.1p111

これはなに?

GCSへのアップロードフローをBlueGreenDeploymentに見立てて組んでみました。
本来は、切り替え役のRouterなる存在が必要ですが、今回はRouterなしでフローを組まざるを得ず、このような内容となっていますが了承ください。

概要

まず、前提としてアプリはローンチ済みで
アプリは GCS のバケットに置かれているリソースを見ているという状態です。

ざっくりのフローは下記の通りです。

■STEP.1

└── storage
    └── main # ここにアプリが参照しているリソースが配置されている

■STEP.2
アップロードするファイル群を別パスにアップロード

└── storage
    ├── main  
    └── prepare # ここにまずはアップロード

■STEP.3
STEP.2 が完了したら、つぎにmainの中身を別のパスにコピー

└── storage
    ├── main  
    ├── prepare
    └── tmp # mainのコピーをここに置く

■STEP.4
STEP.3 が完了したら、つぎにmainを削除し、prepare の中身をmainへコピー

└── storage
    ├── main # 削除して、prepare をここにコピーする
    ├── prepare
    └── tmp 

■STEP.5
STEP.4 が完了したら、preparetmp を削除

└── storage
    └── main

途中で失敗したら?

それぞれ STEP で何かしらの理由で失敗しても
ゴミがのこるだけで、mainへの影響はありません。
「STEP4. のmainを削除したあとに失敗したら結局だめじゃん」という意見もあるかと思いますが
そこは「限りなく安全に」というポリシーなので、 手動でtmpに残っているファイルを移動して元に戻すということになります。

※もし、この状況でよりよい方法があれば教えていただきたいです!

実装

publisher.rb

    # @storage_keys: すでに存在するファイル群のパスが入った配列
    # @upload_keys: これからアップロードするファイル群のパスが入った配列

    MAIN_PREFIX = "main".freeze    
    PREPARE_PREFIX = "prepare".freeze
    TMP_PREFIX = "tmp".freeze

    def publish
      begin
        upload_to_prepare
      rescue => e
        clean(:prepare)

        Rails.logger.error(e)
        Rails.logger.error(e.backtrace)
        raise UploadError
      end

      begin
        copy(from: :main, to: :tmp)
      rescue => e
        clean(:prepare)
        clean(:tmp)

        Rails.logger.error(e)
        Rails.logger.error(e.backtrace)
        raise CopyCleanError
      end

      begin
        clean(:main)
        copy(from: :prepare, to: :main)
        clean(:prepare)
        clean(:tmp)
      rescue => e
        clean(:main)
        clean(:prepare)
        copy(from: :tmp, to: :main)
        clean(:tmp)

        Rails.logger.error(e)
        Rails.logger.error(e.backtrace)
        raise CopyCleanError
      end
    end

    def copy(from:, to:)
      case from
      when :main
        from_key_prefix = MAIN_PREFIX
        keys = @storage_keys
      when :tmp
        from_key_prefix = TMP_PREFIX
        keys = @storage_keys
      when :prepare
        from_key_prefix = PREPARE_PREFIX
        keys = @upload_keys
      else
        raise "target not found"
      end

      case to
      when :main
        to_key_prefix = MAIN_PREFIX
      when :tmp
        to_key_prefix = TMP_PREFIX
      when :prepare
        to_key_prefix = PREPARE_PREFIX
      else
        raise "target not found"
      end

      keys.each do |key|
        from_key = from_key_prefix.blank? ? key : "#{from_key_prefix}/#{key}"
        file = StorageBucket.files.get(from_key)
        if file.present?
          to_key = to_key_prefix.blank? ? key : "#{to_key_prefix}/#{key}"
          copyed_file = file.copy(StorageBucket.key, to_key)

           # 注意! 
           # fog が用意している copy メソッドはデフォルトで公開設定にしています。
           # もし元のファイルが非公開ファイルの場合は、下記の処理を挟まないと
           # 公開ファイルになってしましますので気をつけて
          if file.public_url.present?
            copyed_file.public = true
            copyed_file.save
          end
        end
      end
    end

    def clean(target)
      case target
      when :main
        prefix = MAIN_PREFIX
        keys = @storage_keys
      when :tmp
        prefix = TMP_PREFIX
        keys = @storage_keys
      when :prepare
        prefix = PREPARE_PREFIX
        keys = @upload_keys
      else
        raise "target not found"
      end

      keys.each do |key|
        key = prefix.blank? ? key : "#{prefix}/#{key}"
        file = StorageBucket.files.get(key)
        if file.present?
          file.destroy
        end
      end
    end

説明

リリース済みのアプリが見ているリソースの更新をかけようとして
GCSへアップロードする際に、そのまま直接更新をかけてしまっては
途中で処理がエラーした時はどうするんだ?という、サーバーのデプロイと同じような問題があがり
今回の実装に至りました。

わりとゴリゴリな実装なのと
「copy が失敗し、元に戻す処理が失敗したら結局だめじゃん」という意見もあるかと思いますが
そこは「限りなく安全に」というポリシーなので起こったらしゃーないということでm(_ _)m

※もし、この状況でよりよい方法があれば教えていただきたいです!

学んだこと

・ストレージにおける BlueGreenDeployment の適用

参考

How to list files in a Google Storage bucket with fog-google
How do I rename a file with Fog?

2
5
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
2
5