Unicornの参照先が変わらないケース(Capistrano)
前提条件
問題
デプロイした後にWeb + Appサーバにアクセスすると、release versionが古いバージョンにアクセスしている(しかもviewsのみ)そのrelease versionは削除されているので、templateがないと言われてAppサーバがエラーを吐いていた。
#{app_path}/current
が、シンボリックリンクで、更新されていないことでApplication Errorが起きた。unicornプロセスも古いコードを参照したままになっていた。
暫定対応
unicornをUSR2
シグナルによる再起動ではなく、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に寄せていければ嬉しいので提案は検討。ただそれ自体がプロダクトに価値があるのか微妙なところだ。
参考
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
テーブル定義/データインポート(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で解凍するとこの問題を迂回する。
参考
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 -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のデプロイについて書いていきます。
前提環境
- rails 4.x
- ruby 2.2.10
- capistrano 3.5
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
PostgreSQLサーバの起動コマンド
毎回ググっているので、自分の備忘録として残しています。
環境
- macOS Sierra
- PostgreSQL 9.6.2(homebrewでインストール)
## PostgreSQLサーバの起動(homebrewでインストール) postgres -D /usr/local/var/postgres
参考
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作成
model rice作成
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
Capybara aタグ(target="_blank")をクリックについて
掲題の件、featureテストを書く量が日に日に増えてきており、何を書くかと書き足りないケースなどを自覚できるになり知見も日々、蓄積できていて楽しく、 クライアントと紹介してくれた友人に感謝っすね。
aタグのtarget属性(_blank)
新規ウィンドウ、タブを開いてページを表示した後、表示したページで、タイトルを確認する方法を書いていきます。
環境
サンプルコード
require 'rails_helper' feature 'TOP画面からの操作', type: :feature do scenario 'ユーザがリンクをクリックして新規タブが開いている' do within_window(window_opened_by { first("a.new-window-link").click }) do expect(page).to have_content '新しいページタイトル' end end end # なお、within_window(string)は、非推奨になっているため注意。
感想
テストを書いているプロジェクトって、本当少ないですよね・・。
参考
Method: Capybara::Session#within_window — Documentation for capybara (2.14.3)