« 2013年4月 | メイン | 2013年6月 »

2013年5月

2013年5月27日 (月)

Heroku Postgresでdblinkを使用する。

こんにちは

Heroku Postgresでdblinkを設定したのでその際に行ったことを備忘として残しておきます。

 

★下準備

「create extension」コマンドでdblinkのextensionをインストールします。

 

create extension dblink;

 

一般的にはcreate extensionの前に設定ファイルの変更等が必要なようですが、Herokuの場合はいきなりcreate extensionを実行すればOKです。

大仰に書き始めましたが、実は設定はこれだけで終わりです。(^^;;;

 

★dblink経由でSELECTしてみる。

dblink経由でSELECTを実行する場合のSQLは以下のようになります。

 

select * from dblink(
  'host=xxxx.compute-1.amazonaws.com ' ||
  'port=xxxx ' ||
  'dbname=xxxx ' ||
  'user=xxxx ' ||
  'password=xxxx', 
  
  'select id, email from accounts'
  ) as T1(id int, email varchar(255)) 
  where email like '%flect%';

 

構文よりdblink関数はTABLE句相当になることがわかります。

第1引数が接続情報で第2引数が実行するSQLです。上の例では必要な接続情報を明確にするために「||」でのコンカチを行っていますが、通常はもちろん1ラインで記述すれば良いです。

接続情報は「heroku config」で表示されるDATABASE_URLから取得できます。

dblink経由でSELECTした結果に対してWHERE句も機能しますし、例にはありませんがローカル側のテーブルとのJOINも問題なく行えます。

ちなみにWHERE句は第2引数の中に含めることもできます。

 

  ...
  'select id, email from accounts where email like ''%flect%'''

 

この場合、おそらく条件の絞り込みはリモート側のサーバで行われて条件にマッチしたデータのみが転送されてくると思われます。

対して外側にWHERE句を書く方法の場合はすべてのデータが転送されてきた後にローカル側で条件が評価されると想像します。つまり両者では結果が同じでもデータ転送量が異なるためパフォーマンス的には大きな差があることが予想されます。

今回試したテストデータでは顕著なパフォーマンス差は見られませんでしたが、可能な場合はリモートで実行するSQL内にWHERE句を書いた方が良いと思います。(この程度の単純なSQLの場合はオプティマイザの最適化によってどちらでも同じになる可能性もありますが、基本的な考え方としてはこれであっているはずです。)

 

★dblink接続をセッション内で永続化する。

先の記法はSELECTの実行毎に毎回接続情報を書かなければならないという点でかなり冗長です。

これを回避する方法としてdblinkに名前をつけて接続を開きっぱなしにすることができます。

 

select dblink_connect('mycon', 'host=xxxx.amazonaws.com port=xxxx ....');

 

一度接続が確立するとそれ以降は接続情報の代わりに名前を使用してdblink経由のSQLを実行できます。

 

select * from dblink('mycon',
  'select id, email from accounts'
  ) as T1(id int, email varchar(255)) 
  where email like '%flect%'

 

ただし定義したdblinkの有効期間はセッションなので接続ごとに再度dblink_connectを実行する必要があります。

同一セッション内で全く同じdblink_connect文を複数回実行した場合には2回目以降は「duplicate connection name」というエラーになるのでコネクションプールのある環境では扱いが面倒な気がします。。。(--

リモートユーザーの権限設定で参照/更新範囲を制限できるのでDBインスタンス単位で有効なdblinkだって作れても良さそうなものですけどね。

まぁ、どの道Heroku Postgresではcreate userできないので権限の制限はできません。。。(--

今回要件にないので更新系のSQL実行は試していませんがドキュメント見る限りHerokuでも特に問題なく実行できると思います。「dblink_exec」などの更新用の関数の実行をSELECT文内で実行するという仕様には最初びっくりしましたけど、SQLに新たな構文を追加することなく実装するために実行結果がSELECT結果として返ってくるようにしたんですかね。(^^;;;

2013年5月24日 (金)

HerokuにPlay2アプリをpushする際にキャッシュをクリアする

こんにちは

最近GitHub上に自前のMaven-Repositoryを作成しました。

僕は普段の仕事では汎用のライブラリ(Java)を書く時間とWebアプリ(Play1 or Play2)を書く時間が半々くらいなんですが、これまではアプリから自前のライブラリを使用する場合は作成したjarファイルを直接アプリのlibフォルダにコピーしていました。

それがMaven-Repositoryをインターネット上に置くことによってアプリのライブラリ管理フレームワーク(Play1ではivy、Play2ではSBT)から直接取得することができるようになるわけです。

作成したアプリはたいていHerokuで動かしているんですが、Play2の場合はBuild.scalaにリポジトリの情報を設定することで自前のリポジトリからもライブラリを取得することができます。

残念ながらPlay1の場合はHerokuから自前のリポジトリを使うことはできません。Play1の枠組みではリポジトリの設定は「ivysetting.xml」で行うのですが、Herokuのbuildpackが内部でこのファイルを上書きしてしまうためです。(自分でカスタムbuildpackを作れば対応は可能なはずですが。)

 

★Play2で設定してみる

せっかくリポジトリを作成したので現在作成中のアプリでjarをリポジトリから取得するように修正してみます。

修正するファイルはBuild.scalaの1ファイルのみです。

 

import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

  val appName         = "sqlsync"
  val appVersion      = "1.0-SNAPSHOT"

  val appDependencies = Seq(
    // Add your project dependencies here,
    jdbc,
    anorm,
    "postgresql" % "postgresql" % "9.1-901.jdbc4",
    "jp.co.flect" % "flectSalesforce" % "1.0-SNAPSHOT"
  )


  val main = play.Project(appName, appVersion, appDependencies).settings(
    // Add your own project settings here      
    resolvers += "FLECT Repository" at "http://shunjikonishi.github.io/maven-repo/"
  )
}

 

こんな感じ。

元々はlibフォルダにflectSalesforceとそれの依存するflectCommon, flectSoapの各jarを置いていたんですけど、それらをまるっと削除して起動してみます。

すると起動時にSBTによる依存性解決のログがずらずらと流れて無事に起動。ちゃんと動きます。

次いでHerokuにpushしてみると、Slugコンパイル時に依存性が解決されてこちらも無事に動作しました。

素晴らしい。

 

★ライブラリ開発中はこんな設定するもんじゃない

と、喜んではみたものの実は現在の開発はライブラリの開発が主でアプリは単純にそれにUIをかぶせているだけのものだったのでした。。。

つまり、これまでは日常的に以下のような操作を行っていました。

  1. エディタでライブラリのコードを修正
  2. Mavenでビルド
  3. Antで生成したjarファイルをアプリのlibフォルダにコピー
  4. アプリ再起動して動作確認

めんどくさい。(--
(ちなみにIDEの類は普段まったく使いません。)

これがリポジトリを使用するようになると以下のように手順が変わります。

  1. エディタでライブラリのコードを修正
  2. Mavenでビルド
  3. MavenでGitHubのリポジトリにデプロイ
  4. play cleanコマンド実行
  5. アプリ再起動して動作確認

「play clean」が必要なのはそうしないとSBTのキャッシュが効いて更新されたjarファイルがリポジトリから取得されないためです。

そもそも手順が増えてるし、GitHubへのデプロイはかなり時間がかかるし、キャッシュがなくなっているせいで起動時に毎回依存性の全解決が走るし、とまったく良いことがありません。。。(--

ていうか、コード1行修正しただけでもデプロイが必要とか明らかに間違ってますよね。

ライブラリの安定ビルドを使用するだけのアプリならリポジトリから持ってくれば良いですが、今回のようにライブラリ開発とアプリ開発がセットになっているケースでは、これまで通りjarを直接libにコピーする方式を取った方が良いように思います。

ここに至って初めてMavenがプロジェクト新規作成時に付加するバージョン番号「1.0-SNAPSHOT」の意図がちゃんとわかった気がします。(^^;;;

 

★HerokuのSBTキャッシュをクリアする

ところでこの話、まだ続きがあります。

ライブラリを何回か更新したところで、再びHerokuにpushしたんですがこれはコンパイルエラーになりました。

理由は追加したはずのメソッドが見つからないため。そう、Herokuのスラグコンパイル時にもSBTのキャッシュは有効なため新しいjarを持ってきてくれないんですよね。。。

今回の場合リポジトリを使う設定を外してlibコピー方式に変更すれば良いのですが、なんか方法があるだろうと思って探してみたところ見つけました。

http://stackoverflow.com/questions/15945263/heroku-play-framework-2-sbt-dependencies-cache

要約するとheroku configに「BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-scala.git#cleancache」を指定するだけ。そうすると次回ビルド時にはキャッシュクリアバージョンのbuildpackがスラグコンパイルしてくれるという寸法です。

実のところこの解決方法自体は予想通りのモノでした。探し始めた時点でbuildpackにキャッシュクリアする1行を足せばできるよなぁ、と思っていたので。

そんな中で今回の発見はこれ。

BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-scala.git#cleancache

buildpackは元々GitHubで公開されたものを直接持ってくるという大胆な仕組みで動いてますが、URLの末尾に「#xxxx」をつけるとmasterではなく指定のブランチを持ってくることができるのでした。

そう思ってみると、実はbuildpackのブランチには若干の機能違いと思われるブランチがいくつか並んでいたりもします。

いや~、知らなんだ。。。

残念ながらブランチの説明というのは(git的にも)ないので、どこが違うのかを見るためにはdiffを見るしかないですがGitHubのブランチはこういう使い方もアリなんですね。(^^;

採用情報

株式会社フレクトでは、事業拡大のため、
Salesforce/Force.comのアプリケーション
開発
HerokuやAWSなどのクラウドプラッ
トフォーム上でのWebアプリケーション開発

エンジニア、マネージャーを募集中です。

未経験でも、これからクラウドをやってみた
い方、是非ご応募下さい。

フレクト採用ページへ

会社紹介

株式会社フレクトは、
認定コンサルタント
認定上級デベロッパー
認定デベロッパー
が在籍している、
セールスフォースパートナーです。
heroku partnersにも登録されています。
herokuパートナー
株式会社フレクトのSalesforce/Force.com
導入支援サービス
弊社の認定プロフェッショナルが支援致します。
・Visualforce/Apexによるアプリ開発
・Salesforceと連携するWebアプリ開発
も承っております。
セールスフォースご検討の際は、
お気軽にお問合せください。
Powered by Six Apart