2014年2月20日木曜日

タイトル変更とドメイン設定しました

ブログタイトルを「ありんこ日記」から「ありぺい日記」に変更しました。また、aripei.comを取得したので、ブログをサブドメインのblog.aripei.comにして運用する事にしました。こうなると他のアカウントとかも@aripeiにしたくなるところだが、だいたいすでに取られてる…。

2014年2月17日月曜日

Top Coder SRM 609 Level3 おさらい

今週末もTop Coderに参戦したが、例のごとく最終問題のみ解けなかったので復習した。

今回はGumiとIaとMayuというボーカロイドが音楽アルバムを作りますという若干Geekyなお題でした。以下のような入力値が与えられた時に、すべての曲が必ず一名以上に歌われるようなアルバムのパターンは何種類あるかという問題です(一つの曲を複数人で歌うのはOK)。

入力値(すべて1から50までの整数)

  • S: 全曲数
  • gumi: Gumiが歌う曲数
  • ia: Iaが歌う曲数
  • mayu: Mayuが歌う曲数
出力値
  • Int: アルバムのパターン数
例えば(3,1,1,1)だと3C1 * 2C1 * 1C1で6パターンとなります。

各曲に対して、各一名ずつ歌う場合(3パターン)、各二名ずつ歌う場合(3パターン)、三人全員が歌う場合(1パターン)の合計7パターンあります。なので7のS乗回試行して条件を満たすものをカウントすればよいのですが、Sが50の場合には1.798465e+42回ほど試行する事になるので残念ながら僕が死ぬまでには計算が終わらなそうです。

この手の問題はどうやら以下の二種類の方法で解けるようです。
  • 場合分けをして数学的に解く
  • 動的計画法で解く

前者は場合分けを行ってCombinationを駆使して解く方法、後者は分割統治法とメモ化と呼ばれる手法を使って問題を細分化して同じパターンの部分をキャッシュしながら解く方法です。勿論、数学的に解けた方がエレガントではあるのですが、Top Coderでは動的計画法は必須知識らしく、他の多くの問題に応用可能なようなので今回はこちらで解いてみました。

まずは各項が以下の漸化式で求められるという事に気づく必要があります。こちらは前述の通り各曲に対して7パターン存在するということからも分かると思います。

  • f(s, g, i, m) = f(s-1, g-1, i, m) + f(s-1, g, i-1, m)  + f(s-1, g, i, m-1)  + f(s-1, g,-1 i-1, m)  + f(s-1, g-1, i, m-1) +  + f(s-1, g, i-1, m-1)  + f(s-1, g-1, i-1, m-1)
  • f(0,0,0,0) = 1
後は、各入力値は正の値であるとか、g+i+m > sであるとかの確認をして、この漸化式を満たして、s/g/i/mのパターンをキャッシュしてあげるようなプログラムを実装すればよいです。

できあがりは以下のようなコードになりました。でも、やっぱこのレベルの問題を30分で完璧に解くのはまだまだ無理だなぁ。あ、ちなみに1000000007で割ってるのは問題文に数値がでかすぎるのでこの値の剰余をとるようにって書いてあったから。


import java.util.*;

public class VocaloidsAndSongs {

    long cache[][][][];
    final long MOD = 1000000007L;

    public int count(int S, int gumi, int ia, int mayu) {

        cache = new long[S + 1][gumi + 1][ia + 1][mayu + 1];

        for (int s = 0; s <= S; s++) {
            for (int g = 0; g <= gumi; g++) {
                for (int i = 0; i <= ia; i++) {
                    for (int m = 0; m <= mayu; m++) {
                        cache[s][g][i][m] = -1L;
                    }
                }
            }
        }
        return (int) dp(S, gumi, ia, mayu);
    }

    private long dp(int s, int g, int i, int m) {

        if (s > (g + i + m)) {
            return 0;
        }

        if (s == 0) {
            if (g == 0 && i == 0 && m == 0) {
                return 1L;
            } else {
                return 0L;
            }
        }

        if (g < 0 || i < 0 || m < 0) {
            return 0L;
        }

        if (cache[s][g][i][m] == -1L) {
            cache[s][g][i][m] = dp(s - 1, g - 1, i, m) +
                    dp(s - 1, g, i - 1, m) +
                    dp(s - 1, g, i, m - 1) +
                    dp(s - 1, g - 1, i - 1, m) +
                    dp(s - 1, g - 1, i, m - 1) +
                    dp(s - 1, g, i - 1, m - 1) +
                    dp(s - 1, g - 1, i - 1, m - 1);

        }

        return cache[s][g][i][m] % MOD;
    }

}

2014年2月9日日曜日

Top Coder SRM 608 Level3 おさらい

昨日、Top Coder SRMに二回目の参加をしてみたのですが、Level 3が解けなくて悔しかったので復習してみた。

問題は要約するとN個のノードからなる有効グラフが渡された時にステップ数Lからなる経路の数が多項式P(L)で表されるか判別するプログラムを書くというのが問題です。本番では英語だし若干複雑なので理解するのに若干時間がかかりましたが…。 

入力値は個々のエッジを表すYとNの文字の羅列が以下のようにStringのArrayとして渡される。配列長と文字数はグラフの頂点の数と同じです。
String[] graph = new String[]{"NYN","NNY","YNN"};

また、出力は多項式解が存在する場合には"Bounded"、存在しない場合には"Unbounded"をリターンするだけです。

この問題は本番時にはコード以前にそもそもどういうアルゴリズムにすれば解けるのかが思いつきませんでした。終了後に以下のようにホワイトボードを使って精査したらやっとひらめいた。




NPがNon Polynomial(多項式解なし)でPがPolynomial(多項式解あり)を表しています。どうやら循環して自分に戻ってくるような経路を二つ以上もつノードがある場合に多項式解がなくなるようです。赤のラインで循環を示しているのですが、このラインが一つのノードから2つ以上出ている場合に多項式解がなくなります。例えば循環経路がM、自分に戻ってくるまでにNステップかかると経路数の計算は「Mの(L / N)乗」になるはずです。という訳でMが1の場合には多項式解、Mが2以上の時には多項式では解けないはず。

ルールさえ分かってしまえばコードはこんな感じで簡単に書けました。単純に一つ一つのノードをイテレートして循環が2つ以上ないか確認するだけです。

import java.util.*;

public class BigOEasy {
    static final String UNBOUNDED = "Unbounded";
    static final String BOUNDED = "Bounded";

    boolean[][] edges;
    public String isBounded(String[] graph) {

        // edges as boolean 2d array
        edges = new boolean[graph.length][graph.length];
        for (int i = 0; i < graph.length; i++) {
            for (int j = 0; j < graph[i].length(); j++) {
                edges[i][j] = (graph[i].charAt(j) == 'Y');
            }
        }

        // each vertex
        for (int currentVertex = 0; currentVertex < edges.length; currentVertex++) {
            int num_of_circle = 0;

            // each edge
            for (int nextVertex = 0; nextVertex < edges[currentVertex].length; nextVertex++) {
                // check if circular
                if (edges[currentVertex][nextVertex]) {
                    if(checkCircular(currentVertex, nextVertex, new boolean[edges.length])){
                        num_of_circle++;
                    }
                }

                // unbounded if more than one circulars
                if(num_of_circle > 1){
                    return UNBOUNDED;
                }
            }
        }
        return BOUNDED;
    }

    private boolean checkCircular(int startVertex, int currentVertex, boolean visited[]) {
        visited[currentVertex] = true;
        for(int nextVertex = 0; nextVertex < edges.length; nextVertex++){
            if(edges[currentVertex][nextVertex]){
                if(startVertex == nextVertex){
                    return true;
                }else if(visited[nextVertex]){
                    continue;
                }else{
                    if(checkCircular(startVertex, nextVertex, visited)){
                        return true;
                    }
                }
            }
        }
        return false;
    }
}


ただ、Level 1で10分、Level 2で20分程度は消費すると考えるとLevel 3に費やせる時間は30分程度しかありません。ちょっと本番でこのレベルの問題がでたらかなりラッキーじゃないと時間内に解ける気がしませんね。今のところは確実にLevel 1とLevel 2を片付けることを目標とするか。忘れかけたJavaをググりながら書いているので、この辺りのTipsが身に付けば早く解けるようになってもう少しスコアがあがるはず。

2014年1月27日月曜日

Pythonでデータ分析ごっこ事始めしてみる

弊社(Cambridge Energy Data Lab)ではデータサイエンス系の開発言語をPythonに統一しておりまして、社員達がNumPy、SciPy、pandasなどなどを目下勉強中です。

データサイエンティストも積極採用中なのですが、応募者全員に以下のデータ解析タスクをGitHubから提出するという課題を課しています。

Cambridge Energy Data Lab: EnergyDataSimulationChallenge

既に15フォークされていて、質の良いPull Requestも多く来ていて嬉しい限りです。Cambridge大学の学生もどんどん応募してきてくれています。皆さんもお暇な時に是非 :)

これが採用に非常に有効で、まずランダムにCVを送りまくっている応募者や、最低限のスキルの身に付いていない応募者のフィルタリングが非常に効率的に行えます。基本的にはこのプログラミングタスクの提出をしていない応募者のCVは一切確認しません。また、どの言語が得意なのか、どんなライブラリの扱いに慣れているか、どのように考えて分析を進めているのかなどなど、やはりコードを見ると非常に効率よく応募者のスキルを知る事ができます。

とか偉そうな事を言っておいて、実は僕はWeb Applicationの開発に集中しないといけないという言い訳があり、自分ではデータ解析部分に殆ど触れておらずデータサイエンティストに任せっきりにしていました。しかしながら採用活動をバンバンしている手前、それじゃいかんなと反省して、週末にちょっと時間をみつけて自分でもPythonでデータ解析をしてみました。

環境設定

まずはCanopyをインストールしました。こちらはPythonでのデータ解析に利用されるライブラリ群(NumPy、SciPy、Matplotlib、pandasなどなど)に加えてIPythonが搭載された統合環境です。これをインストールするだけで必要なツールが一気に揃うので、Macを使っている方には非常にお薦めです。

分析結果はIPython Notebookにまとめると便利です。Web上でInteractiveにコードの実行が出来、かつDescriptionなども加えられて自分の分析がNotebookとしてまとめられるので分析結果の公開が非常にやり易いです。

以下のように実行すると、Plotに必要なライブラリが読み込まれ、かつ結果のグラフ等の出力がHTMLに出力されて便利です。

ipython notebook --pylab inline

UIはこんな感じ。


チャレンジ

覚えないと行けない事は非常に多いのですが、とりあえずはpandasDataFrameとしてデータを取り込んで、Matplotlibで可視化するという事を目標にして、Challenge 2 - Visualization of Energy Consumptionsに取り組みました。主にデータの可視化、時系列変換、クラスタリングを行うタスクです。

初めて触るということもありやっぱデータの扱いに慣れておらず、半日近くかけてなんとか以下の処理だけ済ませました。

  1. pandasにデータ取り込み
  2. そのままPlot
  3. データのDiffをプロット
  4. 時系列変換してプロット
  5. データの分類(クラスタリング)をしてプロット

ちなみに、Gitに載せておくとIPython Notebook Viewerというサービスで公開できて便利です。僕の分析はこちらから見られます。とりあえず最低要件だけはなんとか済ませたというレベルなので、暇を見つけてちょっとかっこ良く分析するかな。特に単純に平均から標準偏差を足して引いてしてるだけの分類がダサいので。

2014年1月17日金曜日

HerokuからS3へアクセスするRakeを作ってみた

S3に置かれたデータファイルを取得し、それを読み込みデータを更新するというRakeタスクを作って、Herokuのスケジューラでバッチタスクとして登録してみました。このあたり(Using AWS S3 to Store Static Assets and File Uploads)を参照しながら進めました。

Rakeの設定

Rakeのタスクはrailsコマンドでlib/tasks以下に生成される。

$ rails generate task loader
      create  lib/tasks/loader.rake
とりあえずこんな感じで作って。

namespace :loader do
  desc "Load Data Files"
  task :test => :environment do
    puts "test"
  end
end
実行できる。

$ rake loader:test
test
RSpecもこんな感じで書けました。generatorはないっぽいけどspec/tasks以下とかに置けばよいのでは。

require 'spec_helper'
require 'rake'
MyApp::Application.load_tasks

describe 'Rake Test' do
  it 'run test' do
    Rake::Task['test'].invoke('arg')
  end
end

S3へのアクセス

RubyからS3の利用はAmazonのドキュメント(AWS SDK for Ruby)を見ながら設定したら思いのほか簡単にできました。 S3のアクセス情報はHerokuのConfigに突っ込んで環境変数として使います(Configuration and Config Vars)。ローカルから走らせる場合には環境変数に設定するかRakeコマンドに引数として渡す事もできます。

$ heroku config:set S3_KEY=xxx S3_SECRET=yyy S3_BUCKET=zzz
Adding config vars and restarting app... done, v21
  S3_BUCKET  => zzz
  S3_KEY     => xxx
  S3_SECRET  => yyy
まぁ、以下のようにrailsのconfigに置いちゃっても良いみたいだけど。ちなみにこれは接続に失敗した時に表示されるAWSライブラリのエラーメッセージです。

= Ruby on Rails
       
In a Ruby on Rails application you may also specify your credentials in
the following ways:
       
* Via a config initializer script using any of the methods mentioned above
 (e.g. RAILS_ROOT/config/initializers/aws-sdk.rb).
       
* Via a yaml configuration file located at RAILS_ROOT/config/aws.yml.
  This file should be formated like the default RAILS_ROOT/config/database.yml
  file.
Rubyからのアクセスには、まず以下のGemを追加します。

gem "aws-sdk", "~> 1.32.0"
そうすると上で設定した環境変数を引っ張って下記のように接続からBucketの取得までできます。

AWS.config(access_key_id: ENV['S3_KEY'],
           secret_access_key: ENV['S3_SECRET'],
           region: 'us-west-2')
BUCKET = AWS::S3.new.buckets[ENV['S3_BUCKET']]
すると以下のように、key名のprefixでS3上のオブジェクトを取得したりデータを読み込んだりできます。オブジェクトの詳細な扱いとかはAWS::S3のクラスドキュメントにまとまっています。

BUCKET.objects.with_prefix(directory).each do |obj|
  puts obj.key
  puts obj.read
end

スケジューラへの追加

スケジュール化されたタスクを走らせる方法としては定期的にRakeコマンドを実行するHeroku SchedulerClockworkなどのGemを利用する方法があるらしい。Heroku Schedulerはone-off dynos上で実行されるBest Effortサービスらしいが、とりあえずこっちの方が簡単そうなのでこちらで実装してみました。 Heroku Schedulerのアドオンを追加すると以下のような感じで簡単にJobの登録ができます。
実行後に以下のようにログを確認できます。

$ heroku logs -p scheduler
2014-01-16T18:07:59.715840+00:00 heroku[scheduler.7222]: Starting process with command `bundle exec rake test`
2014-01-16T18:08:00.376906+00:00 heroku[scheduler.7222]: State changed from starting to up
2014-01-16T18:08:04.282904+00:00 app[scheduler.7222]: Loading Files for 20140116
2014-01-16T18:08:04.719251+00:00 app[scheduler.7222]: 
2014-01-16T18:08:04.719251+00:00 app[scheduler.7222]: [AWS S3 200 0.431475 0 retries] list_objects(:bucket_name=>"zzz",:max_keys=>1000,:prefix=>"pre")  
2014-01-16T18:08:05.958921+00:00 heroku[scheduler.7222]: Process exited with status 0
2014-01-16T18:08:05.980991+00:00 heroku[scheduler.7222]: State changed from up to complete
ところで、Herokuのログってdefaultだと1500行までしか保持してくれないんですね。アドオン導入しないとな。やっぱPapertrailでしょうか。

2013年12月10日火曜日

Rails多言語対応

今日はRailsで日本語と英語の出し分けを実装してました。さすがRails、多言語対応も安定の簡単さ。グリーみたいにアラビア語対応とかしなくていいから更に楽です。ここ(Rails Internationalization (I18n) API)を見ながらちょいちょいと設定が済みました。 基本的にはI18nというオブジェクトですべてハンドルしております。 I18n.lでlocalize、I18n.tで翻訳をします。

2.0.0p247 :010 > I18n.l Time.now
 => "Tue, 03 Dec 2013 17:19:04 +0000" 
2.0.0p247 :022 > I18n.t "hello"
 => "こんにちわ世界" 
どのLocaleを利用するかはI18n.localeに格納されています。

2.0.0p247 :026 > I18n.locale
 => :jp 
2.0.0p247 :027 > I18n.default_locale
 => :en 
言語リソースはymlで用意する。config/locales/*.ymlというファイルを作るとRailsが自動的に読み込んでくれる。

en:
  hello: "Hello world"
jp:
  hello: "こんにちわ世界"
ちなみに階層化できるので、きれいにまとめましょう。

en:
  greeting:
    hi:
      world: "Hi World"
      ippei: "Hi Ippei"
.(ドット)でつなぎます。

2.0.0p247 :004 > I18n.t("greeting")
 => {:hi=>{:world=>"Hi World", :ippei=>"Hi Ippei"}} 
2.0.0p247 :005 > I18n.t("greeting.hi.world")
 => "Hi World" 
2.0.0p247 :006 > I18n.t("greeting.hi.ippei")
 => "Hi Ippei" 
若干ハマったのが、なぜかjp.ymlという言語リソースファイルを追加したのに読んでくれていない。Railsがconfig/locales/から自動的にロードしてくれるのだが、残念ながらファイルを追加した時には動的に呼んでないらしい。ファイルに新しいKeyをアペンドした時は読んでくれるんだけど。たぶんこのパラメータが起動時に作られるのだと思われる。

2.0.0p247 :001 > I18n.load_path
 => ["/Users/ippei/RubymineProjects/eneberg/vendor/bundle/ruby/2.0.0/gems/activesupport-4.0.1/lib/active_support/locale/en.yml", "/Users/ippei/Rubymine.....
また、application.rbからdefault_localeの変更やload_pathの追加をすることもできる。少なくともrails4.0.1では以下のようなComment OutされたエントリーがあるのでComment Inして任意の言語またはディレクトリを追加するだけ。

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de


問題はI18n.localeにどうやって任意の言語を入れてあげるか。つまりどうやって言語を判別するか。 大まかに以下の方法があると思います。組み合わせて使ったりもします。グリーはAccept-Languageだったはず。

  1. ドメインで識別 
    • jp.example.com
    • example.jp
  2. Query Stringで渡す
    • example.com?locale=jp
  3. Pathに追加する
    • example.com/jp
  4. Accept-Languageを見る
    • Accept-Language:en-US,en;q=0.8,ja;q=0.6
  5. GeoIP
  6. ユーザ毎に設定を持たせる

今回はお手軽にQuery StringとAccept-Languageで実装しました。


まずはQuery Stringから。ApplicationController.rbに下記を追加してQuery Stringで渡されたパラメーターをI18n.localeに設定するようにします。

before_action :set_locale
 
def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end
これだけだと画面遷移時にlocaleが引き継がれないので、以下のmethodをoverrideして常にlocaleパラメータを渡すようにします。

def default_url_options(options={})
  { locale: I18n.locale }
end
お次はAccept-Languageの実装です。残念ながらRails自体にはAccept-LanguageをParseする機能はないようなので、http_accept_languageというGemを使いました。 Gemfileに以下を追加します。
gem "http_accept_language", "~> 2.0.0"
そうするとこんな感じで準備したLocaleに対応する言語を返してくれます。
http_accept_language.compatible_language_from(I18n.available_locales)
Query Stringを優先させるので、終わってみるとApplication Controllerはこんな感じになりました。

  before_action :set_locale

  def default_url_options(options={})
    {:locale => I18n.locale}
  end

  def set_locale
    I18n.locale = extract_locale_from_params ||
        extract_locale_from_accept_language_header ||
        I18n.default_locale
  end

  private
  def extract_locale_from_params
    if params[:locale] and I18n.available_locales.index(params[:locale].to_sym)
      params[:locale]
    end
  end

  def extract_locale_from_accept_language_header
    http_accept_language.compatible_language_from(I18n.available_locales)
  end
後はja.ymlとen.ymlにひたすらリソースを追加して、Templateにせこせこと以下の文字を馬鹿みたいに埋め込み続けるだけの簡単なお仕事です。
<%= t('KEY') %>

2013年12月1日日曜日

DNSimpleでHerokuアプリのドメイン登録


Herokuとの連携が簡単そうだったのでDNSimpleというドメインサービスを利用してみたら死ぬほど簡単だったので紹介してみる。DocumentもSimpleにまとまっていて良い(DNSimple Support)。
HeokuのドキュメントもDNSimple推しでまとまってます(SSL Endpoint, Purchasing an SSL Certificate from DNSimple)。
まずはクレジットカードの登録をしてアカウントアクティベートしてみます。ドメインを追加します。フォームに打ち込むだけで取得できるかの確認や、Name Servreへの登録とかもやってくれて数分待っていれば名前が解決できるようになりました。

 
既にとられているドメインだと怒られる。


こんな感じでダッシュボードから登録の状況やALIAS、SOA、NS等の情報が確認できます。




Advance Editorという画面からRecordが登録できるので、サブドメインと一緒にHerokuのアプリへのALIASレコードを作ってやります。ALIASレコードはDNSimpleが追加してたCNAMEの拡張レコードのようなもの。Aレコードがなくてもdomain名からアドレスを解決してくれるものらしい。詳しくはこちら(What is an ALIAS record?, Introducing the ALIAS Record – Naked Domain Aliasing that Works!)。


後は、Herokuのアプリ設定で上で追加したDomainを追加してあげればOK。ここまでで、もう作ったばかりのサブドメインでアプリに接続できるようになりました。



お次はSSLの設定。これもしっかりまとまっています(Using an SSL Certificate with Heroku)。まずはダッシュボードからこんな感じでサブドメインを指定して証明書を購入します。これも一瞬で済みます。サブドメイン一つのみだと$20/year、ワイルドカードだと$100/yearっぽいです。



購入すると発行期間からemailでkeyが送られてくるので、こんな感じで証明書とPrivate Keyを設定。

$ heroku certs:add /tmp/server.crt /tmp/server.key /tmp/bundle.pem 
Resolving trust chain... done
Adding SSL Endpoint to .$oldapp.. done
$oldapp now served by $newapp
Certificate details:
Common Name(s): 
Expires At:.....

注意点としては$newapp.herokussl.comっていうアプリ名がSSL用のエンドポイントとして割り振られるので、ALIASの先をherokuapp.comから変更すること。じゃないと、証明書のドメインと一致しなくて激おこされます。こちらに書いてある通りです(Troubleshooting SSL error for *.heroku.com or *. herokuapp.com)。もともと非SSLのアプリへのALIASにしてあったので、キャッシュの関係で変更後もしばらくは怒られていましたが一晩おいといたらちゃんとアップデートされてhttpsでアクセスできるようになりました。

後は以下のようにConfigでProduction環境のみhttpsを強制するようにすれば終了。

--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -40,7 +40,7 @@Application.configure do
   # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
 
   # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
-  # config.force_ssl = true
+  config.force_ssl = true
という感じで初めての僕でも作業時間半日くらいでドメインを取得してサブドメインでSSL対応のWebアプリを公開するとこまでできてしまいました。もう一度やったら1-2時間で済みそう。ちなみにBatch Server用のAWSのドメイン登録もしたんだけど、こちらはPublic IPが割り振られているのでAレコードを追加するのみでした。

2013年11月27日水曜日

EC2とS3の設定してみた

Herokuで動作しているRuby on Railsアプリケーションで外部からのファイルを受け取るためにEC2とS3のセットアップをしてみました。HerokuはDyno単位で動作するという特性上、ファイルシステムをもっていません。だいたい皆さんS3を使っているみたいなので、以下の手順でファイルの受け渡しをしてみることにしました。
  1. EC2をSFTPサーバとして外部からファイルを受け取る
  2. EC2はファイルのバリデーションをしてS3と必要なデータをSync
  3. HerokuのアプリケーションがS3からデータ取得
ちなみに、むかーしにEC2のインスタンスをちょっと立てて放置したことがあるくらいで、AWSはほぼ初心者です。

Amazon S3


とりあえずS3の設定をしてみます。このチュートリアル(Get Started With Amazon Simple Storage Service)に従って進めたらBucketを作ってAmazonのコンソールからファイルをアップロードする所まではすぐにできました。

まずはBucketを作ります。



とりあえずRegionはHerokuに合わせてUSにしておく。


$heroku info
=== $server_name
Addons:        heroku-postgresql:dev
Git URL:       git@heroku.com:$server_name.git
Owner Email:   test@example.com
Region:        us
Repo Size:     2M
Slug Size:     36M
Stack:         cedar
Tier:          Legacy
Web URL:       http://$server_name.herokuapp.com/

Amzon S3のConsoleから管理ができる。

Amazon EC2

次にFTP Server / Batch Serverとして使うEC2の設定です。これも基本的にはAmazonのチュートリアル(Getting Started with Amazon EC2 Linux Instances)通りに進めればすぐにインスタンスが立てられます。

まずはOSを選ぶんですがAmazon Linux、Red Hat、SUSE、UbuntuからWindowsまで選択肢が多くて困る。Debianに慣れているしUbuntu AMIにしようかとも思ったけれども、無難にAmazon Linux AMIにした。とりあえず基本的なパッケージが安全に動けば良いし、AmazonのDocumentもAmazon Linuxを基準にしてあるみたいなので。



後はメモリやCPUやストレージサイズなどを適当に選んでいけばインスタンスが立ちます。最後にKey Pairが作れるのでそのPrivate Keyを使ってsshで接続すればすぐにLoginできます。


$ ssh -i ~/.ssh/amazon_ec2.pem ec2-user@pub_dns
Last login: Mon Nov 25 10:14:54 2013 from host

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|


これもAmazon EC2のConsoleからInstance増やしたりVolume追加したりなどの管理ができます。

ついでにEBS Volumeの作成とマウントもしといた。VolumeはEC2のコンソールから作るだけ。初回のみはVolumeのフォーマットが必要なので注意。でないとマウントでエラーになります。とりあえずext4にしといた。

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/xvda1            7.9G 1023M  6.8G  13% /
tmpfs                 298M     0  298M   0% /dev/shm

$ sudo mkfs -t ext4 /dev/xvdf 
mke2fs 1.42.3 (14-May-2012)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
327680 inodes, 1310720 blocks
65536 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=1342177280
40 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
 32768, 98304, 163840, 229376, 294912, 819200, 884736

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done 

$ sudo mkdir /mnt/ebs
$ sudo mount /dev/sdf /mnt/ebs

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/xvda1            7.9G 1023M  6.8G  13% /
tmpfs                 298M     0  298M   0% /dev/shm
/dev/xvdf             5.0G  138M  4.6G   3% /mnt/ebs


EC2からS3へのデータ転送


まずは例のごとくAmazonのドキュメントを見ながらCliの初期設定をします(Getting Set Up with the AWS Command Line Interface)。Access Keyの設定とかPythonライブラリのインストールとかあって若干めんどくさい。

Pythonで書かれてるらしいんだけど、Localeの設定をしてあげないと以下のように盛大に死ぬので注意。

$ aws
Traceback (most recent call last):
  File "/usr/bin/aws", line 15, in
    import awscli.clidriver
  File "/usr/lib/python2.6/site-packages/awscli/clidriver.py", line 29, in
    from awscli.help import ProviderHelpCommand
  File "/usr/lib/python2.6/site-packages/awscli/help.py", line 20, in
    from docutils.core import publish_string
  File "/usr/lib/python2.6/site-packages/docutils/core.py", line 20, in
    from docutils import frontend, io, utils, readers, writers
  File "/usr/lib/python2.6/site-packages/docutils/frontend.py", line 41, in
    import docutils.utils
  File "/usr/lib/python2.6/site-packages/docutils/utils/__init__.py", line 20, in
    import docutils.io
  File "/usr/lib/python2.6/site-packages/docutils/io.py", line 18, in
    from docutils.utils.error_reporting import locale_encoding, ErrorString, ErrorOutput
  File "/usr/lib/python2.6/site-packages/docutils/utils/error_reporting.py", line 46, in
    locale_encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1]
  File "/usr/lib64/python2.6/locale.py", line 478, in getdefaultlocale
    return _parse_localename(localename)
  File "/usr/lib64/python2.6/locale.py", line 410, in _parse_localename
    raise ValueError, 'unknown locale: %s' % localename
ValueError: unknown locale: UTF-8

CredentialsのペアはSecurity Credentialの管理ページから作れる。aws configureでAccess Keyを設定すると~/.aws/configに設定ファイルが作られる。


$ aws configure
AWS Access Key ID [None]: $KEY_ID
AWS Secret Access Key [None]: $SECRET_KEY
Default region name [None]: us-west-2
Default output format [None]: json

やっとアクセスできるようになった。

 aws s3 ls s3://arita_test
2013-11-19 17:22:58        300 2013-11-19-17-22-57-69ED20ABB5DBF2F7
2013-11-19 17:27:56        326 2013-11-19-17-27-55-8498FB7A97E32996
2013-11-19 17:31:41        299 2013-11-19-17-31-40-81265F67425272DE
2013-11-19 17:33:08        584 2013-11-19-17-33-07-87A40B3132B18A50

regionの指定で少しハマったけど、US StandardではなくEC2と同じRegionでS3のBucketを作り直したら上手く行った。aws configureで指定したregionと違う場合は--regionオプションで指定してあげないと上手く行かない。

 $ aws s3 cp test s3://arita_test/  --region us-east-1
upload: ./test to s3://arita_test/test

 $ aws s3 sync s3://arita_test/  s3
download: s3://arita_test/test to s3/test

SFTP用のユーザ作成

さすがにec2-userでファイルを送るのは問題なので、SFTP用のユーザを作成。まぁ、これは素直にUnixのアカウントを作ってsshの設定をしてあげるだけ。
まずはユーザを作って

$ sudo adduser arita
$ sudo passwd arita
Changing password for user arita.
New password: 
Retype new password: 
passwd: all authentication tokens updated successfully.
公開鍵を置いてPerissionの設定。

$ su arita
Password: 
$ mkdir .ssh
$ chmod 700 .ssh/
$ vim .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys
これでとりあえず、SFTPでデータを置いてS3にSyncするところまでは完成。

2013年10月19日土曜日

Facebook 広告を試してみる

会社の求人の広告をするためにFacebook広告を利用してみました。ターゲティングの詳細さに感動したのでちょっと紹介してみます。

まず、右上のアイコンから広告の作成ページに飛ぶ事ができます。




で、Headlineや説明や画像を設定したりできるのですが、ターゲティング機能が恐ろしくすごいです。

まずは市町村区単位での地域、年齢や性別の絞り込みは当然として、興味などでも絞り込みができます。例えば以下では東京都港区在住の30才以下でArsenalというサッカーチームに興味のある男女に絞り込んでいます。




また、利用しているアプリや恋愛関係のステータス、言語や学歴等でも絞り込む事ができます。婚活サイトや出会い系は独身者をターゲットに、転職エージェントは出身大学をターゲットにして広告を打っているといるんでしょう。「早稲田大学出身のあなたに」とかでてきますからね。



ちなみに、絞り込みを調整しながら、ターゲットの数が何人くらいになるのか、広告のクリック単価がいくらになるのかというのも確認する事ができます。



後は、予算と期間を決めて広告を出すだけです。



実際に広告がはじまると以下の画面からPVやらクリック数やらの詳細情報が見られるようになります。また、広告のストップなどもすぐに行う事ができます。




ところで、広告が始まった瞬間に以下のような画面が現れて、広告のアカウントが止められてしまうという問題が発生しました。サポートに怒りのメールを投げてみたら2日ほどで対応して直してくれました。どうやらPay PalのBilling Addresと僕の現在地や広告先が違っていたのが原因だったようです。





2013年10月18日金曜日

Rails Operation in Heoku

Heoku上でdeployとdbのマイグレーションやseedの流し込みをしたので簡単にまとめてみる。

基本的にはHeroku Toolbeltを利用してオペレーションをする事になり、簡単な運用方法はHeokuのサイトにまとまっています。

Getting Started with Rails 4.x on Heroku

けっこーな数のコマンドがあります。

$ heroku help
Usage: heroku COMMAND [--app APP] [command-specific-options]

Primary help topics, type "heroku help TOPIC" for more details:

  addons    #  manage addon resources
  apps      #  manage apps (create, destroy)
  auth      #  authentication (login, logout)
  config    #  manage app config vars
  domains   #  manage custom domains
  logs      #  display logs for an app
  ps        #  manage dynos (dynos, workers)
  releases  #  manage app releases
  run       #  run one-off commands (console, rake)
  sharing   #  manage collaborators on an app

Additional topics:

  account      #  manage heroku account options
  certs        #  manage ssl endpoints for an app
  drains       #  display syslog drains for an app
  fork         #  clone an existing app
  git          #  manage git for apps
  help         #  list commands and display help
  keys         #  manage authentication keys
  labs         #  manage optional features
  maintenance  #  manage maintenance mode for an app
  pg           #  manage heroku-postgresql databases
  pgbackups    #  manage backups of heroku postgresql databases
  plugins      #  manage plugins to the heroku gem
  regions      #  list available regions
  stack        #  manage the stack for an app
  status       #  check status of heroku platform
  update       #  update the heroku client
  version      #  display version


Dynoのステータスの確認

heroku ps
=== web (1X): `bin/rails server -p $PORT -e $RAILS_ENV`
web.1: up 2013/10/17 17:38:06 (~ 1h ago)

Rails Consoleもリモートの環境のものがこんな感じで使えてしまいます。

$ heroku run rails console
Running `rails console` attached to terminal... up, run.2213
Loading production environment (Rails 4.0.0)
irb(main):001:0> User.all
=> #<ActiveRecord::Relation [#<User id: 2, email: ...

rakeのCommandもheroku runとタイプするだけで走ってしまう。例えばdb:migrateだったらこんな感じ。

$  heroku run rake db:migrate
Running `rake db:migrate` attached to terminal... up, run.6382
Migrating to DeviseCreateUsers (20131014182214)
==  DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
   -> 0.0243s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0076s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0087s
==  DeviseCreateUsers: migrated (0.0419s) =====================================

$  heroku run rake db:seed
Running `rake db:seed` attached to terminal... up, run.6750
ただ、rake db:migrate:resetとかのDBを書き換えちゃうようなコマンドはPermissionで怒られます。

$ heroku run rake db:migrate:reset
Running `rake db:migrate:reset` attached to terminal... up, run.2597
FATAL:  permission denied for database "postgres"
DETAIL:  User does not have CONNECT privilege.
そんな時はheroku pgコマンドで対応できるっぽい。

$ heroku pg:reset DATABASE

 !    WARNING: Destructive Action
 !    This command will affect the app: $host
 !    To proceed, type "$host" or re-run this command with --confirm $host

> $host
Resetting HEROKU_POSTGRESQL_AMBER_URL (DATABASE_URL)... done

うーん。超簡単ですね。今後はDatabase周りの細かいオペレーションが必要になってくると思うので、この辺をみながらいじってみて勉強しとこっと。