kikeda1104's blog

備忘録・技術に関することを書いています。(webエンジニア)

RedShiftのエンコード

前回の記事では、テーブル定義まで進めて検証しましたが、今回はエンコードについて書いていきます。

前提

  • RedShift

エンコード

メリットは下記の通りです。

圧縮は、データの格納時にそのサイズを小さくする列レベルの操作です。圧縮によってストレージスペースが節約され、ストレージから読み込まれるデータのサイズが小さくなり、ディスク I/O の量が減少するので、クエリパフォーマンスが向上します。

エンコードの定義方法は、カラムのデータ型、データ種類、データ数、連続性、データをみて選択可能な圧縮エンコードからテーブル定義(create table, alter table)と合わせて手動で定義するか、COPYコマンドを利用して自動圧縮エンコードを選択することができます。自動圧縮エンコードが推奨されていますが、サンプルデータがスライスで100000以上あることが条件になっていますので、それ以外については手動で選択することになります。(この条件値は変更可能ですが、100000が推奨値になっています)

エンコードの種類
  • デフォルト ソートキーとして定義されている、もしくは、BOOLEAN, REAL, DOUBLE PRECISION型の場合、デフォルトでROWエンコードになる。 他の型はデフォルトはLZOエンコードになる。

  • rawエンコード

    Raw エンコードは、ソートキー、および BOOLEAN、REAL、または DOUBLE PRECISION データ型として定義された列として指定された列のエンコードのデフォルトです。raw エンコードでは、データは非圧縮の raw 形式で格納されます。

ソートキー、BOOLEANでraw型がデフォルトになっています。

ただし、create tableで、エンコードを定義せずに実行すると、全てのカラムがrawエンコードが設定されています。

  • ランレングスエンコード

    たとえば、大きなディメンションテーブルの列に、予測どおりに小さなドメイン (10 個未満の可能値を持つ COLOR 列など) がある場合、データがソートされなくても、これらの値はテーブル全体にわたって長いシーケンスになる可能性が高くなります。

ステータスをStringで表記しているカラムがあるので、ランレングスエンコードを適用します。

  • text255 および text32k エンコード

    text255 および text32k エンコードは、同じ単語が頻繁に出現する VARCHAR 列を圧縮する場合に有用です。ディスク上の列値のブロックごとに、一意の単語の個別のディクショナリが作成されます (Amazon Redshift ディスクブロックは 1 MB を占有します)。ディクショナリには、列内の最初の 245 個の一意の単語が含まれます。これらの単語は、ディスク上で、245 個の値の 1 つを表す 1 バイトインデックス値に置き換えられ、ディクショナリに表されていないすべての単語は非圧縮で格納されます。このプロセスは、1 MB のディスクブロックごとに繰り返されます。インデックス作成された単語が列内に頻繁に出現する場合、列の圧縮率は非常に高くなります。

繰り返し出てくる単語が多ければ利用する。

  • LZOエンコード

    LZO エンコードは、非常に高い圧縮率と良好なパフォーマンスを実現します。LZO エンコードは、非常に長い文字列を格納する CHAR および VARCHAR 列、特に製品説明、ユーザーコメント、JSON 文字列などの自由形式テキストに適しています。LZO は、ソートキー、および BOOLEAN、REAL、または DOUBLE PRECISION データ型として定義された列として指定された列以外のエンコードのデフォルトです。

データ型によってはCOPYコマンドを実行した際に、十分なサンプルデータがある場合にデフォルトでエンコードとして指定される。

  • Mostly エンコード

    Mostly エンコードは、列のデータ型が、格納された大部分の値で必要なサイズより大きい場合に有用です。このタイプの列に Mostly エンコードを指定して、列内の大部分の値を、より小さい標準ストレージサイズに圧縮することができます。圧縮できない残りの値は、raw 形式で格納されます。たとえば、INT2 列などの 16 ビット列を 8 ビットストレージに圧縮できます。

  • デルタエンコード

    デルタエンコードは、日時列にとって非常に有用です。 デルタエンコードは、列内の連続する値間の差を記録することにより、データを圧縮します。この差は、ディスク上の列値の各ブロックに対する個別のディクショナリに記録されます (Amazon Redshift ディスクブロックは 1 MB を占有します)。たとえば、連続する 10 個の整数 1~10 が列に含まれる場合、最初の整数は 4 バイト整数 (+ 1 バイトのフラグ) として格納され、次の 9 個の整数は、前の値よりも 1 大きいことを示す値 1 の単一バイトとしてそれぞれ格納されます。

  • Zstandard エンコード

    Zstandard (ZSTD) エンコーディングは、多様なデータセット間で非常にパフォーマンスのいい高圧縮比率を提供します。ZSTD は、製品説明、ユーザーのコメント、ログ、JSON 文字列など、長さがさまざまな文字列を保存する CHAR および VARCHAR 列に対して、特に効果を発揮します。デルタ エンコードや Mostly エンコードのような一部のアルゴリズムでは非圧縮時よりも多くのストレージスペースを使用する場合があるのと異なり、ZSTD ではディスク使用量が増えることはほとんどありません。ZSTD では、Amazon Redshift のすべてのデータ型がサポートされています。

手動定義

// テーブル定義
create table books
(
  id BIGINT NOT NULL PRIMARY KEY encode zstd,
  name VARCHAR(255)  encode zstd
);

テーブルを作成する際に定義する。(alter tableも可能)

推奨エンコード

既存テーブルに推奨されるエンコードを表示

// 十分なサンプルデータがあるケース
analyze compression table_name;

// 結果(サンプル)

    Table    |     Column      | Encoding | Est_reduction_pct
-------------+-----------------+----------+-------------------
 table_name | id              | zstd     | 7.75
 table_name | quantity | zstd     | 52.35


// idはBIGINTではあるけど、delta32k,MOSTLY32より推奨?されていることに疑問はある。

// 十分なサンプルデータがないケース
analyze compression table_name;

// 結果(サンプル)

 Table |       Column       | Encoding | Est_reduction_pct
-------+--------------------+----------+-------------------
 table_name | id                 | raw      | 0.00
 table_name | name               | raw      | 0.00

// サンプルデータ足りないためにrawエンコードを推奨に出している。

Est_reduction_pctは、zstdエンコードの圧縮可能率になります。

エンコードの自動定義

COPYコマンドがデフォルトでは、空のテーブルに対して実行する場合、、rawエンコードか指定なしの場合に自動圧縮します。また、テーブルのエンコード定義を無視して、自動圧縮を有効したい場合は、COMPUPDATEオプションをONにします。OFFで自動圧縮を無効にします。

すでにデータが入っているテーブルには、自動圧縮は適用されない。

課題

  • クエリの実行時間の計測 公式が掲載している内容に基づき計測をする。実行するクエリ、回線速度の変化があり、計測が難しいですが、この辺りの誤差は踏まえて計測値を信じて、ソートキー、エンコード、分散スタイルを選択する。

  • 圧縮エンコードの選択 rawエンコードを利用した方が、そのほかの圧縮エンコードを使うより最適な可能性もある。

参考

text255 および text32k エンコード - Amazon Redshift

ステップ 5: 圧縮エンコードを確認する - Amazon Redshift

例: CUSTOMER テーブルの圧縮エンコードの選択 - Amazon Redshift

Unicornの参照先が変わらないケース(Capistrano)

前提条件

問題

デプロイした後にWeb + Appサーバにアクセスすると、release versionが古いバージョンにアクセスしている(しかもviewsのみ)そのrelease versionは削除されているので、templateがないと言われてAppサーバがエラーを吐いていた。

#{app_path}/currentが、シンボリックリンクで、更新されていないことでApplication Errorが起きた。unicornプロセスも古いコードを参照したままになっていた。

暫定対応

unicornUSR2シグナルによる再起動ではなく、QUITシグナルで殺してから、unicornプロセスを起動し直す。

kill -s QUIT `pid`
bundle exec unicorn -c /var/www/app_name/config/unicorn.rb -E development -D

これで解決した。

対応

以前からデプロイを繰り返していたにも関わらず今回、初めて起きた事象だったので、前提がおそらく違うのだが、対応はほぼ同じになる。

# config/deploy.rb set :bundle_binstubs, -> { shared_path.join('bin') } # config/unicorn.rb app_path = '/var/www/app' Unicorn::HttpServer::START_CTX[0] = File.join(app_path, 'shared/bin/unicorn')

unicorn + capistrano 構成で、古いリリースの実行パスを参照し続けてしまう問題 - scramble cadenza

今後

capstranoを継続利用せず、Docker + ECRに寄せていければ嬉しいので提案は検討。ただそれ自体がプロダクトに価値があるのか微妙なところだ。

参考

mgi.hatenablog.com

GitHub - capistrano/bundler: Bundler support for Capistrano 3.x

GitHub - defunkt/unicorn: Unofficial Unicorn Mirror.

unicorn/http_server.rb at master · defunkt/unicorn · GitHub

独り言 - 内容と関係ないです。

備忘録と言う名目もあるので、簡単なコードは引用しますが、それ以外は自分で形を変えてをコードを取ってくるつもりです。 (参考記事を見ればその辺は把握できる人たちが読者だと想定して)

sshの接続切れ対応(Broken pipe)

以前から利用しているサーバに対してクライアントには設定(.ssh/config)していたが、サーバに始めて接続する際に、オプションを設定していなかった。 サーバ接続中に起きたので、再設定して、sshした。

接続状態を維持したいので、ServerAliveIntervalとServerAliveCountMaxを設定する。(TCPKeepAliveはデフォルト値がyes)

.ssh/config
host lvh.me
  user ec2-user
  port 22
  IdentityFile ~/.ssh/lvhme.pem
  ServerAliveInterval 15
  ServerAliveCountMax 100

以上。

参考

ssh_config(5) - OpenBSD manual pages

man ssh_config

TCPKeepAlive
Specifies whether the system should send TCP keepalive messages to the other side.  If they are sent, death of the connection or crash of one of the machines will be properly noticed.  However, this means that connections will die if the route is down temporarily, and some people find it annoying.
The default is yes (to send TCP keepalive messages), and the client will notice if the network goes down or the remote host dies.  This is important in scripts, and many users want it too.
To disable TCP keepalive messages, the value should be set to no.

ServerAliveCountMax
Sets the number of server alive messages (see below) which may be sent without ssh(1) receiving any messages back from the server.  If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session.  It is important to note that the use of server alive messages is very different from TCPKeepAlive (below).  The server alive messages are sent through the encrypted channel and therefore will not be spoofable.  The TCP keepalive option enabled by TCPKeepAlive is spoofable.  The server alive mechanism is valuable when the client or server depend on knowing when a connection has become inactive.

The default value is 3.  If, for example, ServerAliveInterval (see below) is set to 15 and ServerAliveCountMax is left at the default, if the server becomes unresponsive,
ssh will disconnect after approximately 45 seconds.

ServerAliveInterval
Sets a timeout interval in seconds after which if no data has been received from the server, ssh(1) will send a message through the encrypted channel to request a
response from the server.  The default is 0, indicating that these messages will not be sent to the server.

BSD                              June 23, 2018                             BSD

sidekiq + redisの構築(Rails 4系) 3

今回は、workerのテストを書いていきます。

前提環境

Redis Worker Classの場合

方針を決めます。retryの上限数を超えた場合のテストも書きたいので、(Dead Job Queueに移す前に通る

# app/workers/high_worker.rb
class HighWorker
    include Sidekiq::Worker
    sidekiq_options queue: 'high'

    sidekiq_retries_exhausted do |msg, ex|
      puts "hoge" # loggerへの記載 or 通知
    end

    def perform(args)
       raise
    end
end

max_retriesは、sidekiq.ymlでも定義することができ、各worker classでも定義できます。

# sidekiq.yml

max_retries: 1 

でリトライの上限数を変更できます。

Rspec

Dead Job Queueに移動する際のテストを書く ただし、コールバックが呼ばれるかのテストではなく、そこに書いているロジックをテストしたい。これもクラスとしてくくり出せるならそちらでテストを書くことでも回避できる。

上記テストのためにgem rspec-siedkiqを追加する。 (sidekiqにもテスト用moduleがありますが、上記のテストを行いたいので、こちらを選びました)

# Gemfile
group :test do
  gem 'rspec-sidekiq'
end
# app/workers/high_worker_spec.rb

require 'rails_helper'

Rspec.describe HighWorker do
   describe '#perform' do
      it 'should be enqueue' do
        expect do
          HighWorker.perform
        end.to change(HighWorker.jobs, :size).by(1)
      end

      it 'shold call sidekiq_retries_exhausted' do
         HighWorker.within_sidekiq_retries_exhausted_block do
         expect(HighWorker).to receive(:puts).with('hoge')
         end 
      end
   end
end

おまけ

警告文を削除したい場合

> [rspec-sidekiq] WARNING! Sidekiq will *NOT* process jobs in this environment. See https://github.com/philostler/rspec-sidekiq/wiki/FAQ-&-Troubleshooting

# rails_helper.rbに追記

RSpec::Sidekiq.configure do |config|
  # Warn when jobs are not enqueued to Redis but to a job array
  config.warn_when_jobs_not_processed_by_sidekiq = false
end

以上になります。

参考

Error Handling · mperham/sidekiq Wiki · GitHub

GitHub - philostler/rspec-sidekiq: RSpec for Sidekiq

Testing · mperham/sidekiq Wiki · GitHub

テーブル定義/データインポート(Amazon Redshift)

前回mysqlからtsvファイルとしてダウンロードしたファイルをredshiftにデータをロードする。検証目的なので、batch処理は後日。

前提環境

  • Redshift(AWS)
  • MySql(5.7系)
  • macOS 10.13.5
  • PostgreSQL 9.6.2
  • 前回の記事でtxtファイルをs3アップロードしている

Redshiftにクラスターを追加

ここは省いて、追加済みとして進めます。

redshiftにテーブルを作る。

# tables.sql
create table tables
(
  id BIGINT NOT NULL PRIMARY KEY,
  name VARCHAR(255),
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);
  • ソートキーは無視します。
  • mysql側に定義したテーブルのカラム順番とredshiftのカラムの順番を一致させている。
  • 値を流し込む場合にredshiftの仕様により流し込みが失敗・エラーになるケースがあります。
    • String length exceeds DDL length Mysqlで扱うVARCHARの仕様のズレによりRedshiftではsizeを変更。 渡された数値を文字数として扱うかバイト数で扱うかの違い

postgresql clientからredshiftに接続して、テーブルを作ります。

コマンドを簡易化するためにshファイルを作っておく。

# redshift.sh
psql -Uusername poscalc --host=endpoint -p port
# redshiftのusernameに対応したpasswordを入力する
bash redshift.sh < tables.sql

 

データ投入

# import_to_redshift.sql
copy tables
from 's3://buckets/txts/tables.txt'
iam_role 'arn:aws:iam::000000000:role/RedshiftRoleForClient'
region 'ap-northeast-1'
blanksasnull
emptyasnull
csv;
bash redshift.sh < import_to_redshift.sql

データの確認

bash redshift.sh
select * from tables;

...=> 結果確認

以上になります。作るケースもありますが、redshiftと繋げるBIツールもありますのでユーザ(SQL必須)にはそちらを使って確認してもらうこともできます(tableau Desktop ... etc)

チューニングに関しては、まだ書く意欲があれば書きます。

次の課題

本稼働に向けて、batch処理化する必要がありますので、AWS DATAPIPEを利用することも検討しますし、記事内のスクリプトをcronに設定して、lambda(AWS)で流し込むことも可能です。

費用面と設計面でどちらを選択するか検討する。

回線速度の問題で、s3のアップロードで時間がかかるので、zip, gzipにしてからアップロードして、s3 + lambdaで解凍するとこの問題を迂回する。

参考

文字型 - Amazon Redshift

MySQL :: MySQL 5.7 Reference Manual :: 11.4.1 The CHAR and VARCHAR Types

tsvファイル出力(MySQL)とaws s3アップロード

RedShiftへのデータロードで、テーブルからcsvでダウンロードしてs3にアップロードします。txtファイルの出力とs3のアップロードについて書いていきます。

txtファイル

SQLファイルを作る

# dump.sql
select * from c into outfile '/tmp/table_name.txt' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
...

tsvファイルをdump

mysql cliからsqlファイルを実行する。

mysql -u username -p database_name < dump.sql 

s3へのアップロード

aws認証情報を登録

awsのIAMからaccess_key, secret_access_keyを取得、なければ作成する。defaultは登録されているので、プロファイル名は別で定義する。

$ aws configure --profile profile_name
AWS Access Key ID [None]: access_key
AWS Secret Access Key [None]: secret_access_key
Default region name [None]: ap-northeast-1
Default output format [None]: json

shファイルを作る

syncコマンドを利用してアップロードします。shファイルを作ります。

# upload_to_s3.sh
aws s3 sync /tmp/txts s3://bucket_name/txts --profile profile_name --exclude '*' --include '*.txt'
....

shファイルを実行

bash upload_to_s3.sh

# 結果
move: /tmp/table_name.txt to s3://bucket_name/txts/table_name.txt # 成功
move failed: /tmp/table_name.txt to s3://bucket_name/txts/table_name.txt Could not connect to the endpoint URL: "endpoing" #失敗
....

以上になります。

参考

AWS CLI の設定 - AWS Command Line Interface

AWS Command Line Interface での高レベルの S3 コマンドの使用 - AWS Command Line Interface

sync — AWS CLI 1.15.50 Command Reference

AWS CLI S3 Configuration — AWS CLI 1.15.50 Command Reference

sidekiq + redisの構築(Rails 4系) 2

前回の続きで、sidekiqのデプロイについて書いていきます。

前提環境

capistranoを利用していたので、capistrano/sidekiqをGemに追加します。 unicorn, pumaなどの設定は無視していきます。

実装

# Gemfile
   group :development do
     gem 'capistrano-sidekiq'
   end
# config/Capfile

...
  require 'capistrano/sidekiq'
  require 'capistrano/sidekiq/monit'
...
# config/deploy.rb

...
set :pty,  true
set :sidekiq_config, -> { File.join(shared_path, 'config', 'sidekiq.yml') }
set :sidekiq_monit_conf_dir, '/etc/monit.d'
  ...

(sudo権限があるユーザを利用することしない場合は、pty, false(default)にする) default hookが追加されますので、deployに合わしてsidekiqプロセスが起動・停止します。

sidekiq_default_hooks Sidekiq will start or stop automatically during deployments. Just set sidekiq_default_hooks to false if you don't want this to happen.

sidekiq_default_hooksをfalseにすれば、hooksを無効に出来ます。

手動起動

cap env sidekiq:start

以上。

参考

GitHub - seuros/capistrano-sidekiq: Sidekiq integration for Capistrano

Home · seuros/capistrano-sidekiq Wiki · GitHubHome · seuros/capistrano-sidekiq Wiki · GitHub

sidekiq + redisの構築(Rails 4系)

非同期処理を取り入れる提案をして、採用されたので構築していました。アウトプットとして何回かに分けて記事を書いていきます。

前提環境

前提環境の背景を話すと、Rails 5系にアップデートできていないプロジェクトです。テストは随時入れていますので、工数が取れればアップデートは出来ますが今のところ予定はないです。

サーバ構成はこんな感じ AWS EC2( Application Server Rails) <=> AWS EC2(Batch Server Rails, sidekiq(worker), Batch(cron)) <=> Aws ElastiCache(Redis, クラスターモード無し)

Rails 4.2系でもActive Jobがあり、retry機能が想定しているエラーを迂回できそうになかったので、workerクラスを実装して進めています。ActiveJobを利用するとRailsがadapterをラップしていることによるメリットもあり、ユースケースを満たせれば,テストコードも楽に書けるようになり採用は出来たと思います。

まずredisのインストールは、この記事ではローカル上にインストールします。(Dockerはまた別の機会に書きます) Hometbrewを利用してインストールします。

redis

  brew install redis
  redis-server
   `/usr/local/etc/redis.conf`が保存されている。

続いてAppに実装

# Gemfile
  gem 'sidekiq'
  bundle install
 # app/workers/high_worker.rb
  class HighWorker
    include Sidekiq::Worker
    sidekiq_options queue: :high, retry: false
    
    def perform(csv_file_id)
       AbcCsvImporterService.call(csv_file_id)
       # 完了通知 or logging
    end
  end

# AbcCsvImportクラスは実装無し。クラス内の例外についても対応する。thread safeのコードにする。(gemが提供するライブラリを使う場合も注意)
# retry: falseなので、Dead Job Queueにはenqueueされません。
# config/application.rb
...
config.active_job.queue_adapter = :sidekiq
....
# config/sidekiq.rb
---
:verbose: false
:timeout: 15
:concurrency: 2
staging:
  :concurrency: 5
production:
  :concurrency: 5
:queues:
  - high
# database.yml
development:
  adapter: mysql2
  encoding: utf8
  database: my_db
  pool: 7
  username: my_user
  password: my_password
  host: localhost
....
# config/initializes/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: 'redis://localhost:6379', network_timeout: 5 }
end

Sidekiq.configure_client do |config|
  config.redis = { url: `redis://localhost:6379` network_timeout: 5 }
end

ローカル上で動作することに限定するなら、設定ファイルをなくして、デフォルトにしてもいいです。上記のurlは、sidekiqのデフォルトになります。本番、ステージングなどある場合は環境別に変えましょう。network_timeoutオプションは、EC2を利用する都合timeout時間を伸ばしています。デフォルトは1です。

database poolと同値かそれ以下にconcurrencyを設定。

# csv_import_controller
class CsvImportController < ApplicationController
  before_action :set_csv_file, only: %w[new create]
  before_action :set_csv_files, only: %w[index]

  def index; end
  def new; end

  def create
     if @csv_file.save
         redirect_to new_csv_import_path, notice: '保存に成功しました'
     else
      render :new, notice: '保存に失敗しました'
     end
  end

  private
 
  def csv_file_params
     return params.require(:csv_file).permit(CsvFile.column_names) if params[:csv_file]
     {}
  end
 
  def set_csv_file
     @csv_file = Csvfile.new(csv_file_params)
  end

  def csv_files
    # index用
  end
end
# app/model/csv_file.rb

class CsvFile < ActiveRecord
   after_commit :import_data, on: :create

   def import_data
      CsvFileWorker.perform_async(id)
   end
end

viewsは省きます。CSVファイルの入力フォームのイメージです。

bundle exec sidekiq -C config/sidekiq.yml

以上です。次回、AWSの構成、テストコード、redisなどの記事を書ければ書きます。色々ノウハウ溜まっているのでそのほかにElasticsearchなど。そのうち。

参考

Home · mperham/sidekiq Wiki · GitHub

Error Handling · mperham/sidekiq Wiki · GitHub

https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting#threading

https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting#cannot-find-modelname-with-id12345

PostgreSQLサーバの起動コマンド

毎回ググっているので、自分の備忘録として残しています。

環境

## PostgreSQLサーバの起動(homebrewでインストール)
postgres -D /usr/local/var/postgres

参考

18.3. データベースサーバの起動

drop_tableの代わりにActiveRecordMigration#revert

環境

前提

初期からRailsでアプリを作成していて、tableを作成したmigrationもそのアプリが保有していることが前提になります。

modelを途中で消す場合、./bin/rails d model model_nameで消すと作成した際、migration fileも削除される。 途中で、カラムを追加・変更・削除している場合は、./bin/rails db:migrateが失敗することになり、modelを削除する際には、気をつけないと、migrateを一から実行することができなくなり、復旧することになります。

revert

rails app単体の構成であれば、drop_tableを使ってテーブルを削除するよりも、revertを使うのが良いと思います。 では、手順ですが、一度作成したmodel、testファイル、該当するコードを削除した後、tableを作成したmigrationは残しておき、新規drop用のmigration fileを準備します。

サンプル

model bread作成

f:id:kikeda1104:20170630224831p:plain

model rice作成

f:id:kikeda1104:20170630232206p:plain

migrate実行とgit commit

./bin/rails db:migrate
git add . && git commit -m "add bread and rice models"

model bread削除

./bin/rails d model bread
git checkout db/migrate
./bin/rails g migration DropBreadsTable

作成したmigrationをエディタで開く

require_relative '20170629123105_create_breads' # 追記(migrationファイル名は、適切なファイル名に直してください)

class DropBreadsTable < ActiveRecord::Migration[5.1]
  def change
    revert CreateBreads # 追記 create_breadsをキャメルケースに直してrevertの引数に渡す。
  end
end

以上です。これで、migrate, rollbackすることができます。

別サンプル

途中で、カラムを追加していた場合のケースも追加しておきます。

breadsにcolumnを追加

./bin/rails g migration AddIsSweetnessToBreads

migration file編集

class AddIsSweetnessToBreads < ActiveRecord::Migration[5.1]
  def change
    add_column :breads, :is_sweetness, :boolean, default: false
  end
end

テーブルを削除するmigration fileを作成

require_relative '20170630135043_add_is_sweetness_to_breads'
require_relative '20170630134600_create_breads'

class DropBreadsTable < ActiveRecord::Migration[5.1]
  def change
    revert AddIsSweetnessToBreads
    revert CreateBreads
  end
end

以上です。

./bin/rails db:migrate
./bin/rails db:rollback STEP=3
./bin/rails db:migrate

複数テーブルの操作をmigrationに書き込んでいる場合

このケースだと対応はできないです。 migration fileは、1テーブルの操作に制限して、プロジェクトメンバーに周知して守らせる必要がありそうです。 (上記をオススメしますが、migration fileの書き方で、dropしないテーブルに関する操作をrevert後にコピーしてくることで復旧できそうなので、 今度改めて、追記します。)

参考

http://api.rubyonrails.org/classes/ActiveRecord/Migration.html#method-i-revert

https://railsguides.jp/active_record_migrations.html#%E4%BB%A5%E5%89%8D%E3%81%AE%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E9%80%86%E8%BB%A2%E3%81%99%E3%82%8B