Google App Engineを辞めた

約1年前にGAEを使って立ち上げたこのサイトですが、今回のGAEの料金体系の変更によって無料クオータの限界を超える日が存在することが分かったため、GAEを辞めて既に契約しているVPSに移動することにしました。

最初に無料クオータの限界に達していたリソースは、Frontend Instance HoursとDatastore Readsの2つでした。
この2つのリソースの多く消費していたのが、全ポケモンJSONデータを生成していた箇所だったのですが、生成したJSONをmemcacheだけでなくデータストアにもキャッシュするようにコードを修正したところ、多数のデータストアの読み込みが1回に集約されたためDatastore Readsが減り、かつ遅延も小さくなったため余計なインスタンスが立ち上がることが少なくなりFrontend Instance Hoursも無料クオータに収めることができました。

ところが、今度はデータストア上のキャッシュが原因でSmall Datastore Operationsが無料クオータ限界付近まで増えてしまい、もはやJSONデータを静的ファイルとして予め用意しておいてデータストアの読み込み自体を無くす以外に、無料クオータ内に収める方法がなくなってしまいました。

幸い、GAEを辞めることを見越して別のデプロイ方法を考えていたので、移行はそれなりにスムーズに完了しました。

GAEは当初月間500万PV相当まで無料という売り文句でしたが、CPU時間の方は速いレスポンスを返す工夫とMin Pending Latency等の設定次第で新料金体系でもそれなりになんとかなりそうですが、データストアの方はかなり工夫して作っても、新料金体系下では500万よりも遥かに少ないPVで無料クオータの限界に達してしまうという印象です。

GAEの魅力はGoogleのインフラを使ってアプリケーション開発ができることでしたが、無料で運用されていたアプリにはもともと自動スケールは必要なく、無料であることを選択の理由にしていたアプリ・開発者にとって、GAEはもはや魅力的なプラットフォームでは無いと思われます。
(まあ、無料であることが持続可能であるわけがないので、当然ですが。)

Emacs LispとRubyを使ってGoogle Chromeを操作する

私は以前までブラウザはFirefoxを使っていましたが、Firefox4MacFlashが見れなくなったり、Firefox5でタブを切り替えても表示が切り替わらない(この現象は検索してもヒットしないので自分の環境だけの可能性有り)といった不具合があってから、メインのブラウザをGoogle Chromeに乗り換えています。

FirefoxからGoogle Chromeに乗り換えるのをためらった理由の1つが、自作したEmacsとの連携機能Chromeに移行すると使えなくなるというものでしたが、google-chrome-clientという別プログラムからChromeにアクセスするためのRubyライブラリの存在を知ったので、Emacs連携機能のGoogle Chrome版を作成しました。

この機能を使うためには、Google Chromeを次のオプションで起動します。

--remote-shell-port=9222

Macの場合、Google Chromeの実行形式へのパスは、/Applications/Google Chrome.app/Contents/MacOS/Google Chromeです。
このオプション付きで起動すると、「ChromeDevTools Protocol」というプロトコルが使えるようになります。

Ruby側のプログラムは、google-chrome-clientというライブラリを使用しているので、まずそちらをインストールします。

gem install google-chrome-client

続いて、以下のスクリプトを適当なファイル名で保存します。

require 'rubygems'
require 'google/chrome/client'

module Google
  module Chrome

    class Client
      def ping
        header, resp = self.request({ 'Tool' => 'DevToolsService' },
                                    { 'command' => 'ping' })
        resp['data']
      end
      def list_tabs
        header, resp = self.request({ 'Tool' => 'DevToolsService' },
                                    { 'command' => 'list_tabs' })
        resp['data']
      end
    end

    class Tab
      def evaluate_javascript(script)
        body = {'command' => 'evaluate_javascript', 
                'data' => script}
        number = @number
        @client.instance_eval do
          write_request({ 'Tool' => 'V8Debugger', 'Destination' => number },
                        body)
        end
      end
    end

  end
end

if $0 == __FILE__
  url = ARGV[0]
  client = Google::Chrome::Client.new('localhost', 9222)
  client.tabs[0].evaluate_javascript("window.open('#{url}');")
end

Emacs側のプログラムは先ほどのRubyプログラムのラッパーになっています。コードそのものはFirefox版のコピペです。chromerepl-open-uri-pathに先ほどのRubyプログラムのパスを指定します。

(defun uri-encode (str)
  (mapconcat
   (lambda (s)
     (mapconcat
      (lambda (x) (format "%%%x" x))
      (vconcat (encode-coding-string s 'utf-8))
      ""))
   (split-string str)
   "+"
   ))

(defvar chromerepl-open-uri-path "~/.emacs.d/bin/chromerepl-open-uri.rb")

(defun chromerepl-open-uri (uri)
  (interactive "suri: ")
  (let ((cmd (format "ruby %s '%s'" chromerepl-open-uri-path uri)))
  (shell-command cmd)))

(defun chromerepl-open-uri-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (chromerepl-open-uri str)))

(defun chromerepl-alc-search (word)
  (interactive "sword: ")
  (chromerepl-open-uri (format "http://eow.alc.co.jp/%s/UTF-8/" (uri-encode word))))

(defun chromerepl-alc-search-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (chromerepl-alc-search str)))

(defun chromerepl-google-search (keywords)
  (interactive "skeywords: ")
  (chromerepl-open-uri (format "http://www.google.co.jp/search?hl=ja&q=%s" (uri-encode keywords))))

(defun chromerepl-google-feeling-lucky (keywords)
  (interactive "skeywords: ")
  (chromerepl-open-uri (format "http://www.google.co.jp/search?hl=ja&btnI=&q=%s" (uri-encode keywords))))

(defun chromerepl-google-search-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (chromerepl-google-search str)))

(defun chromerepl-google-feeling-lucky-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (chromerepl-google-feeling-lucky str)))

(defun chromerepl-google-translate-region (begin end)
  (interactive "r")
  (let (str uri)
    (setq str (buffer-substring-no-properties begin end))
    (setq uri (format "http://translate.google.co.jp/translate_t?hl=ja&sl=en&tl=ja#%s%s"
		      (if (equal (find-charset-region begin end) '(ascii)) "en|ja|" "ja|en|")
		      (uri-encode str)))
    (chromerepl-open-uri uri)
    ))

(defmacro define-chromerepl-x-search (name site)
  (let ((sym-i (intern (concat "chromerepl-" (symbol-name name) "-search")))
	(sym-g (intern (concat "chromerepl-" (symbol-name name) "-search-result")))
	(url-i (format "http://www.google.co.jp/search?hl=ja&btnI=&as_sitesearch=%s&q=%%s" site))
	(url-g (format "http://www.google.co.jp/search?hl=ja&as_sitesearch=%s&q=%%s" site)))
    `(progn
       (defun ,sym-i (keywords)
	 (interactive "skeywords: ")
	 (chromerepl-open-uri (format ,url-i (uri-encode keywords))))
       (defun ,sym-g (keywords)
	 (interactive "skeywords: ")
	 (chromerepl-open-uri (format ,url-g (uri-encode keywords))))
       )
    ))
(defmacro chromerepl-x-search-expand ()
  (let ((chromerepl-x-search-list
	 '((ruby        . "doc.ruby-lang.org")
	   (python      . "docs.python.org"  )
	   (lisp        . "www.lispworks.com")
	   (gauche      . "practical-scheme.net/gauche/man")
	   (django      . "docs.djangoproject.com/en/1.2")
	   )))
    `(progn
       ,@(loop for elt in chromerepl-x-search-list
	       collect `(define-chromerepl-x-search ,(car elt) ,(cdr elt))))    
    ))
(chromerepl-x-search-expand)