2012年11月30日金曜日

[GAE/J] DataStore更新時のConcurrentModificationException エラー


背景

アプリ「大喜利部」のユーザ情報の更新ロジックを追加したところ、更新が成功するときと、失敗する時があった。
成功/失敗の切り分けが難しく、原因も特定できずに、ハマりにハマって4時間以上も時間を費やしてしまったので、メモをとっておくことに。

エラー

Google App Engine のログを参照したところ、commitのタイミングで「ConcurrentModificationException」エラーが発生してました。

java.util.ConcurrentModificationException: too much contention on these datastore entities. please try again.

原因に辿り着くまで

「ConcurrentModificationException」エラーは以下が原因で発生します。
 (1)Datastoreの楽観的ロックが失敗した場合
 (2)複数のリクエストが同じエンティティを並行して「get~put」した場合

アプリ「大喜利部」ではAPIを作成して、XMLによる通信を行い、データのGET/POSTをしているわけですが、単純なファイルの更新なので、デッドロックしているような個所は見当たらない。
よって(1)の可能性はないため、考えられる可能性としては(2)。

HTMLファイルに更新するformを作成してみて、アクセスしてみる。
この場合、問題なく更新されるので、更新プログラムは問題ないはずと判断し、クライアント(APIの使用側)を疑うことに。

ユーザ情報更新ロジックに問題はなさそうだけど、なんどやっても成功と失敗を繰り返す。
ソースをデバッグ実行するとほぼ成功する。(このへんでかなり混乱しました)
悩みに悩んだ挙句、更新を非同期で行っていることに気づき、続きのロジックを読み込むと、その流れでユーザ情報を再取得していました。
Google App Engine の DataStore 更新は JDO で行っているのですが、更新を非同期でputし、更新中の情報を同時にgetしていたということになります。
まさに(2)の状態(同じエンティティをput-get)であったわけです。

対処方法

更新を同期処理に変更することで、「ConcurrentModificationException」エラーは起こらなくなりました。
※今回の場合はユーザ情報の更新なので、基本は1人しか更新と取得はしないですが、複数の人が更新するエンティティの場合は、失敗時にリトライするなどの工夫が必要です。

参考





2012年11月18日日曜日

[GAE/J] DatastoreのM/SからHRDへの移行 体験記


[Google App Engine(GAE)] のDatastore を [Master/Slave Datastore(M/S)] から [High Replication Datastore(HRD)] への移行した時の体験を記録しておきます。
対象としたサービスは大喜利部のサーバサイドプログラムです。

背景

2012/08/24 GAEからM/S利用者へ向けてHRDへの移行を推奨メールが送信されました。
要約すると、M/Sは廃止します。ツールを用意したので、HRDへの移行をお勧めします。
といった内容です。
また、GAEの管理サイトでは以下のような画面が表示されるようになり、移行を決意しました。


対象アプリのデータ量は目安として、大きなKindは以下の2つ。
 お題Kind 200万レコード
 ボケKind 3万レコード
 他にも15 Kind 程 存在します。

事前準備

2012/11/10 00:00〜開始しました。(この記事を書きながらなので、目安程度に考えてください)
事前準備としてサーバメンテナンスをお知らせしてありましが。
アプリに大してもメンテナンス中には操作できなくなるように細工をしてあり、その細工を開始します。

サーバの複製をしていきます。
管理ツールメニュー(左側)の[Administration]-[Application Settings]を開きます。
下の方に[Duplicate Application Settings]がありますので、複製を開始していきます。

複製アプリ名を[New Application Identifier]に入力し、[Check Availability]で使用できる名称かチェックし、[Duplicate Application...]を選択します。
My Applications へ移動し、HRDのアプリが複製されました。

複製アプリに現行アプリをデプロイ

複製アプリに、現アプリをデプロイします。
[appengine-web.xml]ファイルの[application]タグの名称を更新してデプロイを実行。(このとき私はversionを1に戻しました)

(ここまでの完了時間 11/18 00:50)※これを書きながらなので非常に時間がかかっています。

[Datastore Indexes]が、インデックスが[Serving]に変わるまで待ちます。
インデックスは25ほどありますが、全て0Entityなので、結構すぐ終わります。
(完了したのが、11/18 01:00 <約10分かかりました>)

データの移行

ツールを使ってデータを移行していきます。
現行サーバの管理ツールメニュー(左側)の[Administration]-[Application Settings]を開き、[Migrate Application]-[View Migrate Tool...]を選択します。

[Desination Application]でMigrationするデータベースを選択し、[Start Migration]を選択して、移行を開始していきます。(メールに通知するかのチェックボックスがあります)
[All blobs and references to those blobs will automatically be migrated. However, any serialized references to blobs must bemigrated manually.]の一文が気になりますが、後で対応します。


ステップは以下の通りです。
1.Catch up
2.Copy
3.Waiting    (このステップではここまで進むことになります)
4.Sync
5.Read-only
6.Catch up
7.Sync
8.Alias

移行を開始したのは、11/18 01:30です。
進行状況は[Administration]-[Application Settings]を開き、[Migrate Application]-[View Migrate Tool...]と進むと進行画面を再度見ることができます。

[3.Waiting]になり完了メールが来たのが、11/18 02:30 で約1時間かかりました。

現行アプリをRead-onlyにする

現行アプリをRead-onlyにします。(このステップでは[8.Alias]まで進みます)
[Activete Read-only]実行できますが、[Launch Incremental Copy]を実行すると、移行中の増減などの取りこぼしを防げるようになります。

[Launch Incremental Copy]を押して、開始します。(11/18 02:45 開始)
更新がほとんどなかったので、10分程度で終了。

続けてActivate Read-only をクリックし、読み取り専用にします。(11/18 02:55 開始)
11/18 03:10 <15分程度>で完了しました。
Syncしてるので、再同期してると思いますので、[Launch Incremental Copy]は不要かもしれません。

完了!

最後に[Finish Migration]して、移行を完了します。

比較的簡単に完了できました。
後はアプリで確認などしていきます。
[Billing Setting]がクリアされていますので、再度設定してください。

気になったこと

移行後、[Datastore Viewer]を参照してもデータがない場合がありました。
移行直後は時間がインデックスが更新されるまでに時間がかかるのかな?
アプリで普通に参照はできるのに、[Datastore Viewer]では参照できない状態にありますした。
時間が立った今は、1秒もすればインデックスとして適用され表示されますが、始め混乱しました。
皆様も混乱しないようにしてください。


もう一つ困ったことが。
[Blob]を利用している場合、新旧で[Blob key]が変わっています。これの対策もする必要があります。
詳細は別記載しましたので、下の記事を参照ください。

[GAE/J] M/SからHRDへ移行時のBlobkey振り替え対応

背景

[Google App Engine(GAE)] のDatastore を [Master/Slave Datastore(M/S)] から [High Replication Datastore(HRD)] への移行しました。
対象としたサービスは大喜利部のサーバサイドプログラムなのですが、Blobを使用しています。

移行した際、Blobkeyは振り直しされるようで、移行後は旧DatastoreのBlobkeyをデータベースに保持するようにしていると、移行後に参照できなくなてしまいます。

[GAE]では[Python]で使用できる、[BlobMigrationRecord]クラスが用意されており、
[.get_new_blob_key(old_key)]というメソッドが用意されているのですが、Javaではこのクラスがなく、以下の方法で回避したので、記録しておきます。
The BlobMigrationRecord Class

解決方法

あまり記載がなく苦労したのですが、どうやら移行した際のBlobKeyの新旧のBlobkeyは「__BlobMigration__」というKindで管理されているようです。

よって、管理ツールの[Datastore Viewer]でSelect分を実行すると新旧の紐付けが分かるということになります。
[ SELECT * FROM __BlobMigration__ ]

つまり、これらを紐づけるようにすれば良い訳です。

[BlobMigrationRecord Class] get_new_blob_key() method for Java

Pythonと同じような[BlobMigrationRecord]の[get_new_blob_key()]メソッドと同じことができるJava版を作ってみましたので掲載しておきます。
自由に使っていただいて問題ないです。
(「いいね」とか「+1」とか「コメント」とかもらえると喜びます)

eclipse4.2インストール 体験記

背景

eclipse3.7を利用していたのだが、なぜか起動すると、システムエラーに。
仕方がないので、この際なので最新バージョン(4.2)を導入することにしました。

eclipse ダウンロード

下のページで[Eclipse IDE for Java Developers]をダウンロード。
Eclipse Downloads

解凍し、eclipse起動まで問題なく動作。

Plugin インストール

とりあえず、直近で必要なPluginは[Google App Engine]だけなので、下のページの通りに、インストールを実施。
Google Plugin for Eclipse 4.2(Juno)
以下のプラグインをインストールする訳だが、
http://dl.google.com/eclipse/plugin/4.2Android関連のダウンロードが過去の経験上分かっていたので、[Google App Engine]に必要な内容だけに絞りインストールしました。(他はまた時間があるときにインストールしようと考えています)
・Google App Engine Tool for Android
・SDKs
   Google Web Toolkit SDK
   Google App Engine SDK

なんなくインストールが完了しました。

[Google App Engine]へデプロイ

既存の[Google App Engine]アプリを開いたときにバージョンがあがったためだと思いますが、[App Engine SDK]へのパスが外れていたので、設定し直しました。

いざ、デプロイしようとすると以下のようなエラーが。

どうやら、appengine-web.xml に<threadsafe>タグ(マルチスレッドとするか)がなかったためにエラーとなっていました。

これで、デプロイすると、無事配置ができました。

参考

2012年11月11日日曜日

ソフトバンク iPhone下取り体験記

iPhone 4S(16GB)  ※裏面に少し割れ目があり
    ↓
iPhone 5

に機種変更したことに伴い、ソフトバンクの下取りに申し込みました。

体験記として記録しておきたいと思います。

下取りプログラムの詳細は本家HPを参照下さい。
私の場合、「iPhone 4S(16GB)」ですので、\16,000が下取り価格で、\1,000を毎月の利用代金から16ヶ月間割り引いてくれます。
(\16,000がもらえるわけではありません。)



iPhone5への機種変更(2012/11/02)

機種変更に関しては、まあなんてこと無い手続きをして完了します。
「はいはい」言って、任せておけば勝手に手続きは終了します。
(多分アクティブティまで済ましてくれると思うので、あとは家に帰ってデータの引き継ぎをするだけになります)

機種変更の手続きの際、窓口で下取りプログラムにしたい旨を伝えると、以下の2点を渡されました。
  1.「スマホ下取りプログラム 申込書 兼 同意書」
  2.「下取りプログラム専用」封筒
(家に帰ってから気づいたのですが、私の場合1の申込書に「販売店記入欄」があり、取次店コード、販売店舗名を書くところに記載がありませんでした。販売店が記載忘れたのかな。大丈夫でしょうか...)

家に帰ってからの作業

家に帰ってから「1、2への記入」と「下取りするiPhone4Sの初期化」を行い、下取りプログラム窓口へ送付します。
※機種変契約してから10日以内に送付が必要です。私の場合、忙しくて契約から(2012/11/10)8日目に作業しました。これが後々どう影響するのか。

「スマホ下取りプログラム 申込書 兼 同意書」の記入
特に難しいところはなく、契約者の情報を記載します。
IMEI(製造番号)も書く必要がありますが、丁寧に申込書に調べ方が記載されています。
([設定]-[一般]-[情報]で参照できます)

「下取りプログラム専用」の記入
ご依頼主の情報を記載します。

下取りするiPhone4Sの初期化
初期化はiPhoneだけで完了します。
 Step1.[設定]-[一般]と進み、下の[リセット]-[すべてのコンテンツと設定を消去]をタップ
 Step2.パスコードを聞かれるので入力
 Step3.初期化して良いか再確認されるので、[iPhoneを消去]をタップ(2回)
 Step4.再起動されるのを待ち、「iPhone(次のページは「ようこそ」画面)」と書かれた画面になれば完了

下取りプログラム窓口にiPhoneを送付(2012/11/10)

『下取りに出すiPhone4S』と『1の申込書』を『2の封筒』へ入れて、下取りプログラム窓口に送ります。
佐川急便での送付でした。(調べると佐川は個人配送に消極的で、コンビニの取り扱いがないみたいです。結構、不便ですね...)
集荷専用電話番号があるので、電話して取りにきてもらいます。(「佐川急便」の「営業店・最寄店検索」が便利)

下取り結果

--2013/01/11追記--
2ヶ月以上かかりやっと下取りの結果が出ました。
「下取り不可」との結果でした。
理由は「外装の破損状態が悪いため」だそうです。裏面にひび割れがあるため、どうかとは思ってたんですが、ディスプレイではないからいけるかもと思っていて、結局だめでした。下にひび割れ状態の写真を載せておきます。

返却時に理由の紙が同封されていたのですが、その紙のチェック欄には以下のものがありましたので参考にどうぞ。
「SIMトレイ不具合のため」
「電源関係不具合のため」
「水濡れの形跡があるため」
「下取り対応機種でないため」

参考

2012年11月3日土曜日

[Git] コード別 gitignore

Gitでソースコード管理してほしくない(無視したい)ファイルがあります。
.gitignoreで設定すれば良いのですが、githabで言語ごと公開されている「github/gitignore」が参考になります。
迷った場合はそのままコピーして利用すれば良いと思います。

リンク

github/gitignore

[XCode] iOS 4インチスクリーン対応

大喜利部のver1.8.0で4インチスクリーンだと画面レイアウトが崩れる不具合が発生し、早急に対応が必要になった。
対応した方法をメモしておく。
(もっとスマートな対応方法があれば誰か教えていただけると嬉しいです。)

対応方法

結論としては、どうやらコードで対応していくしかないようでした。

まずは、[Attributes Inspector]の[Simulated Metrics]-[Size]を[Retina 4 Full Screen]に変更します。
これにより、特定のコントロールは縦にのばして調整してくれるみたいです。
(私のやってみた感覚ではUIView,UITavleView,UITextViewが調整されたように思います。配置によるものなのかもしれませんが。)


これだけで調整できないものはコードでコントロール配置を調整していきます。

※ちなみに上記コードは「4インチスクリーン判定」の他に、「iPad判定」「Retinaディスプレイ判定」も実装されています。


2012年11月2日金曜日

[iOS] App Store Review で Reject された時の話

個人的に「大喜利部」というアプリを公開しているのですが、ver.1.7.0の公開でReject(=拒否)された時の話を覚えてる限りで残しておきたいと思います。

Rejectされた時の状況

「大喜利部」はユーザ投稿型(お題もボケも)のアプリなのですが、ver1.7.0では通常の「テキスト」ベースの大喜利だけではなく、「写真で一言」のお題に写真を指定できる機能を追加しました。
これをAppStoreに申請出したら、Rejectされてしまいました。

本当はRejectされた時のやり取りを載せようと探したのですが、メールベースではなく、やりとりがWebベースでApple側のサーバにしか残っておらず、ちょっと載せれませんでした。

やり取りの概略は、
「あなたのアプリの機能はパブリックで閲覧できるイメージを登録する機能を持っているので、Ratingを17+に上げなさい」
というものでした。(元は全てNoneの4+)
あと、
「削除が申請できるか、申請が会った場合、1週間以内に削除できる体制になっているか」
などの質問がありました。

全ての質問に
「対応準備できてます。大丈夫です。」
と書いて、
「Ratingが17+にするほどではないと思うので、12+にしました。」
と返答したのですが、かたくなに
「17+に上げなさい」
の一点張りだったので、仕方なしに17+にして公開までこぎ着けました。

そもそもなぜRejectされたのか

審査のガイドラインは下記を参照してください。
審査ガイドライン少し古いが日本語訳
おそらく、
・人間や動物に対するリアリスティックな暴力画像はリジェクト
・ユーザーがポルノに遭遇する可能性の高いCGMコンテンツはリジェクト
このあたりに引っかかったのだと思われます。
確かにユーザの使い方によっては、アウトになる機能ではありました。
アプリ作成や、新規機能の追加の前に、一度審査ガイドラインは読んでおくべきですね。
アプリを作っている人で、読んでない人はぜひ読むことをお勧めします。