読者です 読者をやめる 読者になる 読者になる

White Box技術部

WEB開発のあれこれ(と何か)

最近の学び

Spring Server Twitter Java Groovy Oracle

手軽なネタがないので、今月学んだことなんかを。

サーバ関係


RedmineSubversion

Redmine上で参照しているSubversionリポジトリ表示がリビジョン300近くになってから、ものすごく遅くなった。(Redmine以外からのアクセスも遅くなった)

どうも根本原因は利用しているSubversionのバージョンにあるようなのですが(v1.7以上だと解消しているらしい)、 RedmineSubversionが同一サーバであれば、アクセスプロトコルをfileに変更することでRedmine上だけは改善できます。

対策

svnリポジトリの位置が

/var/svn/myrepo

なら、Redmineリポジトリのパスを

file:///var/svn/myrepo

に変更すればOK。

ただ、Redmineに作成済みのリポジトリパスは変更できないので、一度当該リポジトリ設定を削除して、新しくfileプロトコルを使った設定を作り直す必要があります。 リポジトリ設定を付け替えても、コードレビューに紐付くチケットの情報は維持されるので、安心してください。(ドキドキでした

でもこれ、RHELの公式リポジトリがsvn1.8を配布するようになればいいだけな気がするんですけど、1.6で止まっているのは何か理由があるんですかねぇ。

Oracle

仮想化してる開発環境がディスクフルになったので、サイズの大きいところを調べていったら、Oracleのalert_ORCL.logが20G超えてました。
ログを見ると「リカバリ領域がなくなってリカバリできないよ」というログが延々と出続けてました。

20G・・・途中で出力諦めろよと・・・

対応

DB止めようにもコマンドの応答が返ってこないので、消しても出続けるアラートログを一旦空にして、再起動後に以下の対応をしました。

  1. リカバリファイル削除
  2. Oracleコマンドでリカバリ情報削除
  3. ログ削除
  4. 通常のDB起動プロセスを実施

一時運用なので考えていませんでしたが、実運用ではここら辺のファイルを、定期的に退避・削除するスクリプトを組むんですかね。インストールマニュアルのデフォルト設定でインストールしている場合の対象Pathを以下に上げておきます。

  • アラートログと肥大化していたログの場所
/u01/app/oracle/diag/rdbms/orcl/ORCL/trace
/u01/app/oracle/diag/rdbms/orcl/ORCL/alert
  • リカバリログの場所
/u01/app/oracle/fast_recovery_area/ORCL/archivelog

これらのディレクトリ配下のファイルは物理削除で良さそうです。 alert_ORCL.logは最初消すのが怖かったのでechoで空埋めしました。

$ echo -n > alert_ORCL.log
Oracleコマンドでリカバリ情報の削除

RMAN(Recovery Manager)を使って削除します。以下は不要なリカバリログを削除してから実行します。

  • RMANの起動
$ rman target /
  • RMANでのコマンド実行
crosscheck archivelog all;
delete expired archivelog all;
ファイルサイズ確認に使うコマンド

サイズ確認はdfとdu使いました。duはこのままだとサイズの大きいところのディレクトリに移動して再実行していかないといけないので、もうちょっとワンライナー考えたほうがいいかもしれません。

# df -h
# du -sBM * | sort -nr
参考

Spring関係


@Serverや@Componentを付けたクラスのbean名は、通常クラス名のキャメル形式になりますが、先頭2文字が大文字の場合、クラス名そのままがbean名になります。
これはSpringがJavaBeansの命名規則に従っているからだそうで、よくある説明サンプルだと「uRLとかになったら変じゃろ?」ということみたい。

Twitter関係


Twitter Developer's site

久しぶりアクセスしたら、新しいSDKを全面アピールしていて、アプリケーション登録のページへの動線がページ下部の小さいリンクのみになってました。

それでいいのかTwitter・・・

あと新規開発者アカウントの登録に、電話番号登録が必須になっていたのは知っていましたが、作成済みアカウントでも、新規にアプリケーション登録するためには登録が必要でした。なんというか、もうちょっとそういう大事なところを明示しないと、新SDKをアピールしても開発者が寄り付かないのでは?と思いますが、 Twitterのことなので、むしろ望むところなんですかね。

Twitter4j

そんな状況とはいえ、私みたいに今頃Twitterアプリ作って遊んでいる人間にはTwitter4jのようなライブラリはありがたいです。

クラスパスとか

今回はTwitter Streamを使うプログラムを作ったのですが、その場合はTwitter4jのcoreライブラリだけではダメで、streamもパスに通す必要があります。

これ、名前から推測はついたのですが、特に公式サイトやJavadocに記述がなく、確信が持てずにいました。結局、公式で配布しているzip内のreadmeに書いてあるのを見つけて納得したのですが、gradleで依存性解決させてると配布ファイルの方の確認が疎かになるなぁ、なんて思った一幕でした。

asyncは結局必要なのか不要なのかいまいちハッキリしませんでしたが、また後で足すのも面倒だったので入れてコンパイルしました。

リプライの作り方とか

Twitter4jのTweetサンプルやJavadocを見ながら、reply処理を推測で作ったのですが、別にTweetの先頭に@ユーザ名がなくてもreplyになるんですね。StatusUpdateのinReplyToStatusに対象のStatusが持つIdさえ設定してあげれば、replyになりました。以下がfilterのListener中でreplyする処理になります。

    Twitter twitter = TwitterFactory.getSingleton();

    // 返信用メッセージに「@ユーザ名」付けているが、これがなくてもreplyにはなる
    String message = "@${status.getUser().getScreenName()} $replyMessage"
    StatusUpdate tweetstatus = new StatusUpdate(message)
    tweetstatus.setInReplyToStatusId(status.getId())

    // リプライ実行
    Status replyStatus = twitter.updateStatus(tweetstatus)

同一メッセージTweet時のエラー

別の人へのreplyであろうと、同一メッセージは連続でつぶやけないので、無くてもreplyになるとはいえ、@ユーザ名はメッセージに入れた方がよいです。

ですが、これだけではまだ、『同一人物に対する連続発言』に対応できません。
対応したい場合は、メッセージを変える必要があります。メッセージの文言自体を変えたくない場合は、日時や乱数を入れるといい感じです。個人的には日時の方が「何?この値」とメッセージの受け取り側に思わせることが少ないと思うので、日時を入れてごまかすの良いと思います。

考慮するとreply部分は以下のようになります。

    // リプライ実行
    try {
      Status replyStatus = twitter.updateStatus(tweetstatus)
    } catch (TwitterException te) {

      // 同一ユーザへ連続してリプライした場合、重複メッセージになり
      // つぶやきが失敗するので、その場合だけ日時を付けてつぶやく
      Date date = new Date()
      def dateFormat = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
      tweetstatus = new StatusUpdate("$message\n${dateFormat.format(date)}")
      tweetstatus.setInReplyToStatusId(status.getId())
      twitter.updateStatus(tweetstatus)
    }
「痩せよう」「そのままがいいよ」

こんな処理が入ってるプログラムの全体が気になる場合は、以下からどうぞ。

peach/PeachStatusAdapter.groovy at master · seriwb/peach · GitHub

Groovy関係


上記で提示しているコードはGroovyのコードになります。このくらいのちょっとしたプログラムを書くのであれば、 Groovyは強力なのでお勧めです。最後にwarやjarにしてしまえば実行速度とかも気になりませんし。

結構ハマったプロパティ読み込みについて

とはいえGroovyを使っていると、ダックタイピングできるがゆえにハマることがあります。
今回ハマったのはプロパティ値の取得についてでした。

valueがnullだったりkey自体がなかったりするのに値が取れる

今までGroovyでプロパティファイル使う場合は、Groovyでプロパティを扱えるConfigObjectを取得して、以下のように使っていました。

def config = new ConfigSlurper().parse(new File('./conf/Config.groovy') .toURI().toURL())
def replyMessage = config.peach.reply.message

ですがこれだとkeyであるところのpeach.reply.messageがなかったり、keyに対するvalueがなくてもconfigにはコンフィグのクラス名だったかが入ります。defをStringに変えても入ります。なので後続のnull・空文字チェックを華麗にスルーして予期せぬ結果を引き起こしていました。

取得したプロパティ情報をJavaのPropertiesに変えてしまう

うまく言った対策はこれでした。以下のようにするとJavaのPropertiesクラスに変換でき、こうするとpeach.reply.message が存在しない場合、replyMessageの値がちゃんとnullになります。

ConfigObject config = new ConfigSlurper().parse(new File('./conf/Config.groovy').toURI().toURL())
Properties props = config.toProperties()
String replyMessage = props.getProperty("peach.reply.message")
まだだ・・・まだ終わらんぜよ・・・

これでようやく意図する動作となることを確認でき、さてjarにして最終確認だ!と実行したら、

うまく動作しませんでした。

なんだろうかとしばらく考えて、「あ、これ文字化けだ」と気付きました。実行環境がWindowsの場合はまず文字コードを疑うのがいいですね。しばらく気付かなかった敗因は、log4j2のMS932指定が結局わかってないので、文字の化けるがままにコンソール出力させていたことですね。

ファイル読み込みを改善して完成(コンソールは化けっぱなし)

以下が文字コード対応した最終版です。

ConfigObject config = new ConfigSlurper().parse(new File('./conf/Config.groovy').getText("UTF-8"))
Properties props = config.toProperties()
String replyMessage = props.getProperty("peach.reply.message")

全量が気になる方は先程のプロジェクトのMain.groovyを参照ください。

書こうと思ってたことに関係する話とか

軽くまとめるつもりが結構な量に・・・

最初書こうと思っていたのは、プログラミングという行為に焦点を当てた話だったのですが、 書いていたらそっちはそっちで結構な量になっているので、書き終わるまでもうちょっとかかりそうです。

今回の記事中に出てきたTwitterプログラムをベースにしたものになりますので、 「GroovyでTwitter bot作りたい!」という私と全力握手するような方はお楽しみに!(ハードルを上げていくスタイル