2010-11-16

CPU 使い過ぎ? - GAE 管理コンソール上の警告 (Blogger Glass)

管理コンソールに赤文字が!

前回(→「内部リンクを置き換える #2」)の実装を配備した後、管理コンソールを見に行くと、Dasshboard の Current Load のところに以下のスクショのように赤文字が出ていた。

GAE Admin Console
Dashboard の Current Load
「Avg. CUP (API)」が赤文字で表示されている。 黄色三角上にマウスカーソルを持っていくと「This URI uses a high amount of CPU and may soon exceed its quota.」という警告が出てくる。
Logs
Current Load で赤字になった URI をクリックすると Logs が開く。 cpu_ms と api_cpu_ms を使い過ぎているということらしい。

「/」というリクエスト URI の処理に時間がかかりすぎているようだ。この「/」は、「一覧」画面を表示するためのもので、つまりは「内部リンクを置き換える」機能の実装で変更を加えた部分でもある。「一覧」を作るために取得したフィードから、permalink と Post ID を取り出し、そのひもづけ情報をデータストアに書き出すという処理が付け加えられている。この処理に時間がかかっているようだ。

Quota Details で確認すると……

管理コンソールの「Quota Details」を開きリソースごとの「Daily Quota」を確認すると、どの項目も 0% のまま。「Rate」もすべて Okay となっている。リソースの割り当てを使い切りそうということではない。

困ったときはググってみる

何度かググってみて、Google グループの Google-App-Engine-Japan で関連しそうな情報を見つけた(→「ダッシュボードの「Current Load」の「Avg CPU (API)」が赤文字になる」)。これによれば、この赤文字はさほど気にする必要はないようだ。ただ、平均リクエスト処理に 1 秒以上かかるアプリには同時リクエスト数が 30 までという上限が科せられる、という点は覚えておいた方が良いかも。

GAE ではアプリへのリクエストがしばらく途絶えるとアプリのインスタンスが消される。その後にリクエストが届くと、アプリインスタンスを再生成することになり、このときは(インスタンスが存在するときにくらべると)処理時間がかかる。ログを見ていると、このアプリインスタンス再生成をともなったリクエスト処理のときにも警告が出ている。気付いていなかっただけで、単発の警告はこれまでも出ていたってことだ。これだけでどうにかなるなら、とっくになっている。

設計変更が必要か?

Current Load で赤文字が出るのは、「一覧」画面のリクエストで permalink と Post ID のひもづけ情報をデータストアに保存したときだけだ。一度保存してしまえば 2 回目以降のリクエストでは出なくなる。つまり、赤文字の警告が出るような状況は頻繁には起こらない。

仮に、「設定」画面等に「ひもづけ情報の保存」のためのボタンを設け、ユーザが明示的に指示するようにしたとしても、そのリクエストの処理ではやはり赤文字が出ることになるだろう。

データストアの呼び出しを減らすのは効果があるだろうか? 今の設計/実装では、「一覧」画面のリクエストでは一回につき最大 25 回のデータストアの書き込みが起きる(ページごとの記事数が 25 なので)。データモデルが以下のようになっているから(src/model.py より)、記事の数だけデータストアに書き込むことになる。

class PairOfLinks(db.Model):
    """
    PairOfLinks binds a permalink to a post_id of a blog specified
    with a blog_id.
    Each entity of PairOfLinks must be created specifying a permalink
    as its key.
    """
    blog_id = db.StringProperty()
    post_id = db.StringProperty()

これを例えば一ヶ月単位でまとめて書き込むことにすれば、1 度のリクエストでせいぜい 2 回の書き込みで済む。書き込むデータの総量は変わらないものの、API 呼び出しの回数が 25 回から 2 回に減れば効果はあるかもしれない。その分、前処理やら後処理が複雑になるけれど。

保存先をデータストアから memcache に変えるのは有効だろうか? 処理時間は確実に短くなるはず。その代わりに「ひもづけ情報」が揮発性になってしまう。

そこで登場するのが Task Queue だ。

Programming Google App Engine」、13. Task Queues and Scheduled Tasks より
Enqueueing a task is fast, about three times faster than writing to the datestore. [...snip...] For example, an app can write a value to the memcache, then enqueue a task to persist that value to the datastore. This saves time during the user request, [...snip...]

ここに挙げられている例がぴったり当てはまる。ユーザが「一覧」と「記事」画面を行ったり来たりしている間は memcache が有効だし、セッションが終了または長く中断する間に Task で「ひもづけ情報」がデータストアに書き出されるなら、次回のセッションにはデータストアから情報が取り出せるはず。

memcache、Task、データストア、という三段構えで対応するように実装してみようか。そのためには、Task と Queue について調べないと。

追記@2010-11-30

GAE の Task と Queue の使い方および、それを使った Blogger Glass の変更については以下の記事を参照のこと。

参考文献

Programming Google App Engine
Dan Sanderson
Oreilly & Associates Inc ( 2009-11-15 )
ISBN: 9780596522728

関連リンク

関連記事

twitter より (2010-11-15)

Powered by twtr2src.

2010-11-15

内部リンクを置き換える #2 (Blogger Glass)

#1 のデザイン(設計)にしたがい、最小限の機能を実装できた。

実装

permalink と Post ID をひもづける

ひもづけ情報としてデータストアに保存するのは、(1) permalink、(2) Blog ID、(3) Post ID の 3 つ。このうち、(1) をデータ実体へのキーに使う。というのも、この情報を利用するときには permalink に一致する Post ID を引き出すことになるからだ。permalink をキーにしておけばデータストアに問い合わせるクエリを組み立てる必要がない。

以下が、「ひもづけ情報」を保存するデータ実体の定義と、それを利用するための関数だ(src/model.py)。

class PairOfLinks(db.Model):
    """
    PairOfLinks binds a permalink to a post_id of a blog specified
    with a blog_id.
    Each entity of PairOfLinks must be created specifying a permalink
    as its key.
    """
    blog_id = db.StringProperty()
    post_id = db.StringProperty()

def get_post_id(permalink):
    post_id = None
    pol = db.get(db.Key.from_path('PairOfLinks', permalink))
    if pol:
        post_id = pol.post_id
    return post_id

def bind_post_id_with_permalink(permalink, blog_id, post_id):
    pol = db.get(db.Key.from_path('PairOfLinks', permalink))
    if not pol:                 # new bind
        pol = PairOfLinks(key_name=permalink)
        pol.blog_id = blog_id
        pol.post_id = post_id
        pol.put()

get_post_id 関数は permalink にひもづけた Post ID を取り出すためのもの。記事のテキストを置換する際に用いることを想定している。permalink に対してひもづけられた Post ID がないときには None を返す。

bind_post_id_with_permalink 関数は permalink と Post ID のひもづけを保存するもの。「一覧」系の画面を作る際に用いることを想定している。指定された permalink に対して、すでにひもづけが存在した場合には何もしない。これは、permalink と Post ID の組み合わせが変わることはない、という前提に立つもの。Blogger の場合、記事をポストした後にこの 2 つを変更する手段がないから、この前提が成り立つ。

ひもづけを生成する部分のコードは以下のようになる(src/listview.py)。

        feed = util.get_posts(q)
        if feed:
            [...snip...]
            for entry in feed.entry:
                util.regist_pair_of_links(entry)

実際に、ひもづけ情報を保存する regist_pair_of_links 関数は util モジュールで定義している。上述の bind_post_id_with_permalink 関数を呼び出すだけのものだ。

def regist_pair_of_links(entry):
    model.bind_post_id_with_permalink(entry.get_html_link().href,
                                      entry.get_blog_id(),
                                      entry.get_post_id())

permalink へのリンクを置き換える

記事テキスト内のリンクの置換は以下の関数で行う(src/util.py)。

def replace_permalinks(original):
    pat = re.compile('<a[^>]*href=\"([^"]*)\"[^>]*>')
    new_text = original
    pos = 0
    while pos < len(new_text):
        m = pat.search(new_text, pos)
        if not m:
            break
        permalink = m.group(1)
        logging.info("Permlink: %s" % permalink)
        post_id = model.get_post_id(permalink)
        if post_id:
            postlink = "/post/?id=%s" % post_id
            new_text = new_text.replace(permalink, postlink)
            pos = m.end() - len(permalink) + len(postlink) + 1
        else:
            pos = m.end() + 1
    return new_text

正規表現を使い href 属性を持った a 要素を探し、href 属性の値を取り出す。その値を permalink としてひもづけられた Post ID が記録されていれば、それを Blogger Glass の「記事」画面へのリクエスト URL に置き換える。この処理はテキスト中から a 要素が見つからなくなるまで行う。

テキスト中を(正規表現で)探す際には、開始位置を pos で指定しているが、置換を実行した場合は次の開始位置がずれるため 185 行目で調整している。

これを呼び出す部分が src/postview.py 中にある。

    def fill_view_attrs(self, post_id):
        [...snip...]
        entry = client.get_one_post(settings.get('blog_id'), post_id)
        if entry:
            [...snip...]
            self.view.content = util.replace_permalinks(entry.content.text)

これで完成か?

#1 でも書いたように、データストアに保存したひもづけ情報を「いつ消すか」という問題が残っている。経過時間で切るなら保存した実体の中に記録した日時を保管しなければならない。加えて、「消す」作業を開始するトリガーをどうするかという問題もある。「設定」画面でユーザに明示的に消させるか。その場合、リンクの置き換えが機能するにはログインが前提となる。それでも構わないかな。

関連リンク

関連記事

twitter より (2010-11-14)

Powered by twtr2src.

2010-11-14

Apple TV、届いた!

届いたのは昨日(2010-11-13)の昼すぎ。ちょっと放置してしまっていたが、本日、夕方に設置完了。簡単に使ってみたので第一印象などを書き留めておく。

コンパクトなボディ

すでにあちこちで実際の写真が公開されているから十分想像はついたことだけど、実際、自分の手に取ってみると改めて実感する。コンパクトっていうより小さい。これで電源内蔵だからね。

(多少かぶる部分があるとはいえ)使う目的が同一ではないから比較することに意味はないけど、PS3 と比べると驚愕の一言。価格も大きさも(多分消費電力も)。

設置もカンタン

つなぐのは 2 ヶ所。TV との間を HDMI ケーブル(別売)で、コンセントの間を電源ケーブル(付属)で。ネットワークへの接続は有線または無線で。ウチでは TV のところまでネットワークケーブルを引いてハブを置いてあるから有線接続にした。

Time Capsule と同様電源スイッチはない。電源ケーブルをつなぐことがそのまま電源オンになる。電源オフしたければ電源ケーブルを抜くしかない。使わないときはスリープ状態にしておく(再生やその他操作をせずに一定時間が経過すると自動でスリープになる)。これ、知っていないと戸惑うかもね。

最初に電源を入れると TV 画面に言語選択の画面が表示される。「日本語」を選択すればメイン画面に移る(途中、Apple TV の動作に関するデータを Apple に送信しても良いかという選択画面が出る)。メイン画面のメニューは以下のようになっている。

Apple TV: Main Menu
項目名 機能
ムービー iTunes Store (映画)を開く。「上映中」は US の情報っぽい。
インターネット YouTube、Podcast、MobileMe、Flickr、ラジオ。
コンピュータ ホームシェアリングを有効にした Mac (や PC) の iTunes ライブラリを開き、音楽や動画を再生する。
設定 各種設定と「今すぐスリープ」

使ってみる (機能/サービス編)

設定

何はなくとも初期設定。設定の項目は以下の通り。AirPlay は iPhone や iPad 上のコンテンツを Apple TV (経由で TV) に映す機能だが、まだ iOS アプリがないので使えない。

  • 一般
  • スクリーンセーバ
  • オーディオとビデオ
  • AirPlay
  • コンピュータ
  • 今すぐスリープ

最小限の設定は「一般」>「iTunes Store」と進み、Apple ID で iTunes Store にサインインすること。これがなくては「ムービー」でレンタルできない。無線 LAN 接続の場合は、「一般」>「ネットワーク」で設定できる(付属「設定ガイド」p.16)。

さて、ときどきおかしな翻訳でユーザを困惑させてくれる Apple だが(→「すべての無効化を許可、ってどういう意味?」参照)、ここでもやってくれた。「一般」>「ペアレンタルコントロール」と進むと、初期状態で画面に表示されるのは「ペアレンタルコントロールは入」という表現だ。これを読んでどう思う? ペアレンタルコントロールは有効なのか、それとも無効なのか。

「ペアレンタルコントロールは入」という表現だと、これはペアレンタルコントロール機能の状態を表示しているのだ、と思うのではないだろうか? 少なくともわたしはそう思った。しかし、それは間違い。「……は入」と表示されているとき、この機能は無効になっている。実際、その下にあるペアレンタルコントロールの設定項目はグレーになっていて選ぶことができない。で、この項目を選んで押すと「……は切」に変わり(パスコードを設定させられる)、設定項目を選べるようになる。表記と機能が合っていないと思わないか?

英語環境にして同じ項目を表示させると、初期状態は「Turn On Parental Control」。これを押すと「Turn Off Parental Control」になる。そう、日本語の翻訳が悪いってことだ。この場合の「Turn On」と「Turn Off」は「入りにする」「切りにする」と動詞として訳さないと意味が通らない。もちろん「入りにする」「切りにする」という表現も普段目にするものではないから、ここは意訳で「有効にする」「無効にする」とでもすべきところだろう。

販売開始前にアップルジャパンの社員は一人も使ってみなかったのか?

ホームシェアリング

メインメニューのところにも書いたように、Apple TV では、ホームシェアリング(iTunes の機能の一つ)を有効にした Mac の iTunes ライブラリのコンテンツを再生できる(たぶん PC でも可)。LAN 内に Mac があるとメインメニューのコンピュータに表示され、それを選ぶことで、音楽はもちろん動画もポッドキャストも iTunes U も再生できる。もちろん、コンテンツを入れた(そしてホームシェアリングを有効にした) Mac が起動していなければ使えない(スリープもダメ)。

Mac mini Server にコンテンツを入れようかな。

ムービー(予告編)を観る

Apple TV を買った理由(の一つ)に「予告編(プレビュー)が長い(3 分)」ことを挙げたが(→「Apple TV、買った」)。早速、あれこれ予告編を観てみた。Mac 上の iTunes で観る場合と違って、Apple TV では TV 画面一杯にフルスクリーンで表示される。良い感じ。予告編をザッピングするだけでオモシロイよ。

ちょっと残念だったのは、すべての映画で 3 分表示されるわけじゃないってこと。1 分のものもあれば、1.5 分のものもある。

レンタルしてみる

これはまだ試していない。この記事をポストしたら何か一つレンタルしてみよう。

使ってみる (操作編)

本体付属のリモコン

付属のリモコンは製品版の Remote と同じように見える。ということは、Mac の操作(Front Row とか iTunes とか)もこれで可能なはず。実際、iMac でリモコンを有効にしてみると(普段は無効にしてある; 「システム環境設定」>「セキュリティ」>「一般」>「リモートコントロール赤外線レシーバーを無効にする」)、「Menu」ボタンで Front Row が起動するし、iTunes の操作もできた。

また、古いタイプの Remote (→) でも Apple TV の操作ができた。

このリモコンは普段の操作に限れば問題はないんだけれど、設定のときなどにアカウント名やパスワードを入力する際にはやはり使いにくいと感じる。上下左右で文字を選ぶのはシンドイよ。ただ、キー入力画面(ソフトウェアキーボードの一種かな)で、小文字のアルファベットだけでなく、同時に大文字のアルファベットも表示されているのは評価できる。下手に QWERTY 型の配列を表示してシフトで大文字小文字を切り替えるっていう方式にしたら使いにくさを増すばかりだからね。

iPhone アプリ(Remote)

おもしろいところでは、iOS アプリにも「Remote」があること。ただ、純粋にリモコンをエミュレートするだけのようで、設定の入力なんかはできない。文字入力に iPhone (や iPad) のソフトウェアキーボードが使えたら良かったのに。

訂正@2010-11-16:
iPhone/iPad アプリの Remote で文字入力にソフトウェアキーボードが使えないというのは勘違いだった。Remote を使って操作すると、アカウントやパスワードの入力に加えて、検索画面でも iPhone (や iPad) 上のソフトウェアキーボードで入力できる。これを使うと検索画面で日本語も指定できるようになる。
コメントで指摘してくださった gakkey さんに感謝!

関連リンク

関連記事