RDSの"リードレプリカのマスター昇格機能"を使ってDDLオペレーションを実行する方法

AWSのRDSに、
ついにリードレプリカをマスターに昇格する機能が追加されました。

【AWS発表】Amazon RDS for MySQL - リードレプリカのマスター昇格機能を追加!

さて何故(なにゆえ)にこのような機能があるのかというと、
以下3つのケースが同記事にて紹介されています。

  • DDLオペレーションの実行
  • テーブルのシャーディング
  • 障害回復機能を実装する

"障害回復機能を実装する"ケースは、マスターで障害が発生した際に、
リードレプリカをマスターに切り替えることで復旧させる方法です。

既存のデータ復旧機能である"Multi-AZ 配備"に比べて良い点は、
非同期なので書き込みにおけるパフォーマンス劣化が防げたり、
ホットスタンバイとして寝かしておくのではなく
リードレプリカとして読み込み用として利用しておけたりする事でしょうか。

もちろん、同記事にあるようにこれは"非同期"のため、
障害時にデータの整合性が失われる可能性があるので、注意が必要です。

"テーブルのシャーディング"は、
同時多発的にマスター用途のインスタンスを作成するってところですかね。

さて肝心の"DDLオペレーションの実行"です。

そもそも何が問題なの?というと、

サービスやアプリケーションで利用中のテーブルに対して
ADD COLUMNやADD INDEX等のテーブルスキーマの変更を行うと、
該当のテーブルはロックされるため後続のクエリ全てがwait状態になること、
また実行中はある程度のマシンリソースを使うことにより、
稼働に影響を及ぼす可能性があるためです。

特にレコード数の多い大きなテーブルでは顕著に現れます。
サイズ、マシンリソースによってはひとつの変更に
数十分、数時間かかる可能性もあるでしょう。

そこで昇格機能を使うことで、
事前に伝搬中のリードレプリカ上でスキーマ変更を行い、
更新を一時的に止め、リードレプリカをマスターに昇格させ、
アプリケーションの向き先を昇格したマスターへ切り替える、
そんなことが可能なのです。

これなら昇格にかかる時間(数分)と
アプリケーションの向き先変更時間だけで、
数時間もかかる作業を行うことができるのです。

まず下準備です。

RDSのコンソール画面のNavigationにあるDB Parameter Groupsから、
新しいパラメータセットを作ります。

f:id:kazukiyunoue:20130505141638p:plain

リードレプリカに対して変更処理を行うため、
デフォルトのパラメータで設定されている
書き込み制限が外れたセットが必要なためです。

f:id:kazukiyunoue:20130505143203p:plain

適当な名前を付けて作成しておきます。
Edit Parametersから必要な値の変更をします。項目はread_onlyです。

元々は自動でリードレプリカの場合は読み込み専用になるようになっています。

f:id:kazukiyunoue:20130505141701p:plain

これを0にしておきます。

f:id:kazukiyunoue:20130505141724p:plain

インスタンス一覧に戻って、
Instance Actionsからリードレプリカを作成します。

f:id:kazukiyunoue:20130505142751p:plain

作成できたら、リードレプリカを選択した上でActionsからModifyを選択し、
パラメータの設定セットを先ほど作った書き込みが可能な
DDLオペレーション用のものに変えます。

f:id:kazukiyunoue:20130505143002p:plain

再起動が完了したら、
早速リードレプリカに対してスキーマ変更を実施しましょう。
サービスで利用していないのでどんな負荷がかかろうが問題ないですね。

変更中は伝搬が遅延する可能性がありますが、
変更中も変更後も問題なく伝搬は続行しているはずです。

これで準備は整いました。

あとは更新を一時的に停止し、スキーマ変更済みのリードレプリカを
マスターに昇格しましょう。

f:id:kazukiyunoue:20130505143040p:plain

その後アプリケーションの向き先を新マスターに切り替えるだけです。
あっという間にスキーマ変更済みのDBへと切り替えることができました。

実は書き込み可能なリードレプリカにしているのでマスターに昇格しなくても、
向き先を変更してそのまま利用することが可能です。
しかしバックアップ等はマスターにしかない機能なので、
必ずこのタイミングで昇格した方が無難ですね。

今の一連の作業はもちろんAPI経由でも実施可能なので、
Rails等のマイグレーションのタスクに組み込んでしまえば、

rake db:migrate

こんなコマンド一発で
スキーマ変更からDSN切り替えまでやれちゃうかもですねぇ、、

末恐ろしきRDS、、!

AWSのRDSは外部のMySQLとレプリケーションできない -マスターユーザの権限を見て分かったこと-

※ 2013/09/08 added
ついにレプリケーションの機能が付いたようです!
hereAmazon Web Services ブログ: 【AWS発表】 オンプレミスのMySQLデータをAmazon RDSへ移行する(その逆も)


RDSのインスタンスを新規に作成する際に
合わせて作成するマスターユーザの権限を見てみた。

マスターユーザというのは、

f:id:kazukiyunoue:20130414164558p:plain

ココでMaster Usernameとしていれたもの。

権限が、以下のような感じ。

                  Host: %
                  User: root
              Password: *<HASHPASSWORD>
           Select_priv: Y
           Insert_priv: Y
           Update_priv: Y
           Delete_priv: Y
           Create_priv: Y
             Drop_priv: Y
           Reload_priv: Y
         Shutdown_priv: N
          Process_priv: Y
             File_priv: N
            Grant_priv: Y
       References_priv: Y
            Index_priv: Y
            Alter_priv: Y
          Show_db_priv: Y
            Super_priv: N
 Create_tmp_table_priv: Y
      Lock_tables_priv: Y
          Execute_priv: Y
       Repl_slave_priv: N
      Repl_client_priv: Y
      Create_view_priv: Y
        Show_view_priv: Y
   Create_routine_priv: Y
    Alter_routine_priv: Y
      Create_user_priv: Y
            Event_priv: Y
          Trigger_priv: Y
Create_tablespace_priv: N
              ssl_type: 
            ssl_cipher: 
           x509_issuer: 
          x509_subject: 
         max_questions: 0
           max_updates: 0
       max_connections: 0
  max_user_connections: 0
                plugin: 
 authentication_string: 

サーバそのものに影響を及ぼすようなFILE権限であったり、
SHUTDOWN権限がなく、
MySQL接続によるインスタンス停止ができなくなっていたりする。
※コンソール画面やAPIからはもちろん停止可能。

さらにSUPER権限がないため、

外部のMySQL(例えばec2上のMySQLVPC先の自前サーバ上のMySQLなど)
とのレプリケーション設定である

mysql> CHANGE MASTER TO …

上記のようなCHANGE MASTER文を発行出来ない。

つまり、外部MySQLとのスレーブとしてのRDS利用は不可能だ。

さらに、Repl_slave_privもないことから、
外部からのレプリケーション利用のためのユーザも作成できないので、

外部MySQLとのマスターとしてのRDS利用も無理。

RDSには読み出し専用のスレーブ”リードレプリカ”を作成する機能があるので、
そちらを利用しておくんなましってことだろう。

とはいえ、外部とのレプリケーションが実現出来たとしても、
サーバに入って、バイナリーログ等をいじいじいじくれないのなら、
おそらくレプリケーション運用は困難を極めること必死だ。

※ほんとはRDSへの移設にレプリケーションを利用したかったのだけどね。

ちなみにマスターユーザはCreate user権限もGrant権限もあるので
新規にユーザを作成することは可能だけど、

Grantは自身の持つ権限しか付与できないので、
上記の制約はどんな後発のユーザでも当てはまる。

**

あと全然関係ないけど、
rdsadmin@localhostというALL PRIVILEGESを有したユーザがいるのだが、
※おそらくセッション数等を取得してる監視などのためのユーザ。

なぜか、

mysql> drop user rdsadmin;
ERROR 1396 (HY000): Operation DROP USER failed for 'rdsadmin'@'%'

なぜかこの人を消し去れない。。
マスターユーザにはCreate user権限があるので可能な気がするんだけど。。

いや、もちろん消し去る必要はないんだけど。。

どうやってんのかなって。。
まあどうでもいいことなんだけど。。

OmniAuth(OAuth2.0)でproxyを使う方法

YammerのOAuthで認証するアプリが作りたいんだけど、

proxyが必要なネットワーク内でかつ、
環境変数の”http_proxy”が使えないときのメモ。

※そんなマニアックスな人が世の中にどれだけいるか甚だ疑問ですが、、

結果的に、

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :yammer , ENV["YAMMER_CONSUMER_KEY"], ENV["YAMMER_CONSUMER_SECRET"], 
           :client_options => {:connection_opts => { :proxy => "http://proxy.sample.com:80" }}
end

こんな感じでいけました。

deviseだと、

config/initializers/devise.rb

  config.omniauth :yammer, ENV["YAMMER_CONSUMER_KEY"], ENV["YAMMER_CONSUMER_SECRET"],
                  :client_options => {:connection_opts => { :proxy => "http://proxy.sample.com:80" }}

こんな感じです。

※OAuth2.0仕様です。

仕組みとしては、

各OAuthプロバイダのライブラリ(ここではomniauth-yammer)が、
omniauth-oauth2ライブラリのOAuth2クラスを継承していて、

そこからoauth2ライブラリの接続用Clientクラスを生成するのですが、
その際に引き渡すオプション名がclient_optionsとなっていて、

さらに、

そのClientクラスがhttpクライアントライブラリである
faradayのインスタンスを生成する際に引き渡すオプション名がconnection_optsという顛末でした。

とりあえず通って良かったね!

Macbook Air 2012(mid) にvim-rubyを入れた

Macbook Air 2012(mid) が手に入ったので、いつものごとくvim-rubyを入れました。

以前のポスト

例のごとく、vimrubyに対応してるか確認すると、

mba:~ kazuki.yunoue$ vi --version | grep ruby
-python3 +quickfix +reltime -rightleft +ruby/dyn +scrollbind +signs

あら、対応してる。最近から?

以前の通り、

git clone http://github.com/vim-ruby/vim-ruby.git
cd vim-ruby
git checkout vim7.3

rake package

直接ソースからgemのパッケージを作成してinstallしようとすると、

mba:vim-ruby kazuki.yunoue$ rake package
rake aborted!
ERROR: 'rake/gempackagetask' is obsolete and no longer supported. Use 'rubygems/package_task' instead.

rakeのバージョンとの親和性が良くないのか怒られる。

仕方がないので、ココのpathogen.vim方法と思わせてManuallyに、

https://github.com/vim-ruby/vim-ruby/wiki/VimRubySupportA

./bin/vim-ruby-install.rb

これで~/.vim/下に必要なファイルがコピーされるので、

vi ~/.vimrc

に、

set tabstop=2
set shiftwidth=2
set expandtab
set autoindent
set nocompatible
syntax on
filetype on
filetype indent on
filetype plugin on

とか書いてしまえば、完了だ!

ActiveRecordモデルの独自で作成したメソッドもjson(もしくはxml)で出力したい

シチュエーション的には、

class Book < ActiveRecord::Base
  attr_accessible :title, :author, :price

  def price_with_tax
    return self.price*1.05
  end
end

こんな感じでモデルに独自のメソッド(price_with_tax)がある場合、

  # GET /books/1
  # GET /books/1.json
  def show
    @book = Book.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @book}
    end
  end

上記のような自動で生成されるコントローラーでは、

http://localhost:3001/books/1.json
{"author":"aaa","created_at":"2013-01-04T07:49:50Z","id":1,"price":1000,"title":"aaa","updated_at":"2013-01-04T07:49:50Z"}

jsonに独自メソッドは出てきません。

そこでコントローラーを、

  # GET /books/1
  # GET /books/1.json
  def show
    @book = Book.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @book, :methods => [:price_with_tax]}
    end
  end

上記のようにjsonないしxmlに:methodsオプションで該当の独自メソッドを追加してあげると、

{"author":"aaa","created_at":"2013-01-04T07:49:50Z","id":1,"price":1000,"title":"aaa","updated_at":"2013-01-04T07:49:50Z","price_with_tax":1050.0}

出てきた!

これはActiveRecordのto_jsonないしto_xmlメソッドの、
オプションですので、

1.9.3-p194 :001 > book=Book.find(1)
  Book Load (5.7ms)  SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT 1  [["id", 1]]
 => #<Book id: 1, title: "aaa", author: "aaa", price: 1000, created_at: "2013-01-04 07:49:50", updated_at: "2013-01-04 07:49:50"> 
1.9.3-p194 :003 > book.to_json
 => "{\"author\":\"aaa\",\"created_at\":\"2013-01-04T07:49:50Z\",\"id\":1,\"price\":1000,\"title\":\"aaa\",\"updated_at\":\"2013-01-04T07:49:50Z\"}" 
1.9.3-p194 :002 > book.to_json :methods => [:price_with_tax]
 => "{\"author\":\"aaa\",\"created_at\":\"2013-01-04T07:49:50Z\",\"id\":1,\"price\":1000,\"title\":\"aaa\",\"updated_at\":\"2013-01-04T07:49:50Z\",\"price_with_tax\":1050.0}" 
1.9.3-p194 :004 > 

このようにも可能!です!見にくっ!

参考ページ

CoffeeScriptでsetTimeoutを書く

超ピンポイントトピックですが、迷ったので、、

setTimeout ->
  console.log("ok")
, 1000

改行+インデント揃えてからのカンマ+ミリ秒だそうですー。

ありがとうございます!

参考にしたページ