2013年1月11日金曜日

lift-mapperのモデルのコードジェネレータ作ってみた

lift-mapperのmodelの雛形のコードジェネレータ作ってみました。 Lift-mapper model generator ソースは全部htmlにベタ書きしているので、htmlを保存するだけでローカルでも動きます。

2012年10月23日火曜日

sbt 0.12.Xでscala.tools.nsc.MissingRequirementErrorが出て、compile出来ない件

sbtのバージョンを0.11.3から、0.12.1に上げてみたところ、プロジェクトをコンパイルしようとすると
@sbt console
> compile

[info] Compiling 1 Scala source to PROJECT_PATH\target\scala-2.9.2\classes...
[trace] Stack trace suppressed: run 'last compile:compile' for the full output.
[error] (compile:compile) scala.tools.nsc.MissingRequirementError: object scala not found.
[error] Total time: 0 s, completed 2012/10/23 12:38:06
な感じのエラーが出るようになりました。いろいろ調べた結果、
libraryDependencies := ...
サンプルコード(というほどでもないが;; みたいな感じに、依存ライブラリを上書きしてしまっていることが原因でした。 解決方法は、
  1. :=は使わず、+=や<+=などを使う
  2. :=で上書きした後に、
    libraryDependencies <++= (autoScalaLibrary, sbtPlugin, scalaVersion) apply Classpaths.autoLibraryDependency
    
    で、必要なライブラリを追加する。
のどちらかになります。

2012年8月6日月曜日

MySQLでupdateしてinsertするとDeadLockが発生する

負荷テストしているときに、Deadlockの起きるはずのないところでDeadlockが起きたので、いろいろ調べた記録。
原因は、1つのTransaction内でupdateをかけてみて更新件数が0の場合、insertをするという操作をしているところでした。updateを行った際にそのKeyが存在しないと行ロックを広く取ってしまうことが原因みたいです。(あくまでらしいです。ソースコードなどまではおっていません。)
始めはO/R Mapper周りのバグかなと思ってたので、MySQL側の挙動であることを確定させるために検証コードも書いてみました。
ちなみに、Thread数が1ではこの問題は発生しません。

Sample code

App.scala
import java.sql.DriverManager
import scala.util.Random

object App{


  def main(args : Array[String]) {
  
    Class.forName("com.mysql.jdbc.Driver")
    val con = DriverManager.getConnection("jdbc:mysql://localhost/test", username, password);
    val st = con.createStatement()
    st.executeUpdate("""drop table DeadLockCheck""")
    st.executeUpdate("""create table if not exists DeadLockCheck(
    id INT PRIMARY KEY,
    c INT DEFAULT 0);""")
    st.close()
    con.close()
    val threads = (0 until 5).map(i =>{
      val t = new MyThread()
      t.start()
      t
    })
    threads.foreach(_.join)
    
  }
}

object MyThread{
  var random = new Random
}
class MyThread extends Thread{
  
  override def run() = {
    
    for(i <- 0 until 50){
      val con = DriverManager.getConnection("jdbc:mysql://localhost/test", username, password);
      con.setAutoCommit(false);
      val st = con.createStatement()
      val id = MyThread.random.nextInt.abs
      
      if(st.executeUpdate("""update DeadLockCheck set c = c + 1 where id = %s""".format(id)) <= 0){
        st.executeUpdate("""insert DeadLockCheck values(%s,1)""".format(id))
      }
      con.commit()
      
      st.close()
      con.close()
    }
  
  }

}


build.sbt
libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.21"

結果

[info] Running App
[error] (Thread-82) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
[error] (Thread-85) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
[error] (Thread-84) com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
        at com.mysql.jdbc.Util.getInstance(Util.java:386)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1065)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1794)
        at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1712)
        at MyThread$$anonfun$run$1.apply$mcVI$sp(App.scala:43)
        at scala.collection.immutable.Range.foreach$mVc$sp(Range.scala:75)
        at MyThread.run(App.scala:36)
[success] Total time: 1 s, completed 2012/08/06 19:01:10

参考

InnoDBのネクストキーロックによるデッドロックの例

2012年7月29日日曜日

Windows上でredmine2.0.3 + MySQLを動かす

1. Rubyの環境構築

Rubyインストーラーダウンロードページから、
  • Ruby 1.9.3-p194
  • DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe
をダウンロードする。
Rubyは、installerを実行すればOK。
※この時、Ruby のインストール先を変更する場合は、ディレクトリにスペースが含まれていないところにする。
DevKitは、実行すると解凍先を聞いてくるので、適当な場所に解凍して解答したディレクトリでコマンドプロンプトから
  ruby dk.rb init
  ruby dk.rb install
を実行してインストールする。

2. MySQLの準備

MySQLを準備し、
  • redmine
  • redmine_develop
のデータベースを作成しておく。

3. Redmineのインストール

Reamine から、2.0.3をダウンロードしてきて、サーバーを実行したい所に解凍する。
redmineを解凍したディレクトリで
  gem install bundler
  bundle install --without development test rmagick
を実行する。 (RMagickはWindows環境だとインストールが大変なので、除外しておきます。)

4. conf/database.yml設定

conf/database.yml.exampleをコピーして名前をdatabase.ymlに変更する。
database.ymlのadapterをmysqlからmysql2に変更し、DBのusernameとpasswordも変更する。
  rake generate_secret_token
  rake db:migrate
  rake redmine:load_default_data


5. サーバーの起動

  script/rails server webrick
を実行すればOK。

production modeで起動したい場合は、
  RAILS_ENV=production rake generate_secret_token
  RAILS_ENV=production rake db:migrate
  RAILS_ENV=production rake redmine:load_default_data
でDBを準備して、
  ruby script/rails server webrick -e production
でサーバーを起動。

とらぶるしゅーてぃんぐ

 `require': Incorrect MySQL client library version! This gem was compiled for 6.0.0 but the client library is 5.0.20a. (RuntimeError)
のエラーが出る場合 まず
gem install mysql2
を実行し、結果に表示されるzipファイルをダウンロードする。
※私が実行したときは6.0.2だったので以下のURLからダウンロードしました。 http://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-noinstall-6.0.2-win32.zip/from/pick ダウンロードしファイルを解凍して、中のlib\libmysql.dllをRubyの$RUBY_HOME$\bin以下にコピーしてあげればOKです。


参考にしたページ

2012年5月30日水曜日

ScalaからAWS SDK for Javaを使ってDynamoDBをいじったTips

AWS SDK for Javaのバージョンは、1.3.10です。
使ってみた時に詰まった点をメモってみました。


1. ResourceNotFoundExceptionが出る原因


以下のような例外が発生する場合の原因と対処法です。

[error] (run-main) Status Code: 400, AWS Service: AmazonDynamoDB, AWS Request ID: QSQDQO1RMAIDU4S2G6QNCR7S3JVV4KQNSO5AEMVJF66Q9ASUAAJG, AWS Error Code: ResourceNotFoundException, AWS Error Message: Requested resource not found
Status Code: 400, AWS Service: AmazonDynamoDB, AWS Request ID: QSQDQO1RMAIDU4S2G6QNCR7S3JVV4KQNSO5AEMVJF66Q9ASUAAJG, AWS Error Code: ResourceNotFoundException,AWS Error Message: Requested resource not found

原因1 Endpointが設定されていない

val client : AmazonDynamoDBClient = ...// init
client.setEndpont("http://dynamodb.ap-northeast-1.amazonaws.com"); // <- 東京RegionのEndpoint設定。
自分のDynamoDBを使用しているRegionを設定しましょう。Endpoingの一覧はこちら

原因2 Tableが作成されていない

指定したTableが存在しない場合にも同じ例外が発生します。Tableが正常に作成されているかと、プログラム中でTable名の指定が間違ってないかを確認しましょう。
Tableがまだ作成されていない場合は、ManagedConsoleから作成してあげましょう。


2. ValidationExceptionが出る原因


以下のような例外が発生する場合の原因と対処法です。
[error] (run-main) Status Code: 400, AWS Service: AmazonDynamoDB, AWS Request ID: HE5SG08RJAMBQQUBJ031DRGRTVVV4KQNSO5AEMVJF66Q9ASUAAJG, AWS Error Code: ValidationException, AWS Error Message: The provided key element does not match the schema
Status Code: 400, AWS Service: AmazonDynamoDB, AWS Request ID: HE5SG08RJAMBQQUBJ031DRGRTVVV4KQNSO5AEMVJF66Q9ASUAAJG, AWS Error Code: ValidationException, AWS Error Message: The provided key element does not match the schema

原因1 HashKeyまたはRangeKeyの型が違う


HashKeyとRangeKeyの型が間違っている場合に良く起こります。 Table定義を確認して、StringとNumber型の指定が正しいことを確認してください。

new AttributeValue().withS("hogehoge") //Table定義ではNumberになっている場合コレが原因になる



3. O/R mapperを使ってRangeKeyにDate型を指定したけど、queryで上手くフィルターできない


O/R mapperでDateを使用する場合は、DynamoDB上ではISO8601の日付フォーマットの文字列として保存されます。
import com.amazonaws.util.DateUtils

DateUtils dateUtils = new DateUtils();

dateUtils.formatIso8601Date(date)
dateUtils.parseIso8601Date(dateString)

で、変換できます。queryやscan時のConditionで指定する場合は、このメソッドを使いましょう。


4. ScalaからAWS SDKのO/R mapperを使う


Java Beansにしか対応していないので、通常のScalaPlainObjectでは上手く認識してくれません。
基本的には、@BeansInfoアノテーションをつければ、getter/setterを生成してくれるので変数の定義に関しては問題ありません。
しかし、varにつけたアノテーションは、コンパイルされるタイミングで、同名のprivate fieldのほうに付けれられてしまいます。
そのため、AWS SDKのアノテーションが有効にならないので、地道に手作業でgetter/setterを作成してアノテーションをつけないと行けません。
また、case classにしておくと勝手にequalsやhashCode関数をオーバーライドしてくれるので便利ですが、引数なしのコンストラクタもあわせて定義しておいてあげましょう。

NG

import scala.reflect.BeanProperty
import com.amazonaws.services.dynamodb.datamodeling._

case class Hoge( @BeansInfo @DynamoDBHashKey(attributeName="Id") var id : Long,
 @BeansInfo var nickname : String){
  def this() = this(0,"")
}

OK

import scala.reflect.BeanProperty
import com.amazonaws.services.dynamodb.datamodeling._

case class Hoge(var id : Long,
 @BeansInfo var nickname : String){
  def this() = this(0,"")

  @DynamoDBHashKey(attributeName="Id")
  def getId() : Long = id
  def setId(id : Long) = this.id = id;
}



5. O/R Mapper時にクラスの継承を行うと変数が上手くマップされない


幾つかのテーブルが同じようなテーブルで同じ変数持ってるから、継承で共通化しちゃえと思って継承を使ってみたところ、上手くO/R Mapperがフィールドをマップしてくれませんでした。
回避方法は、「継承元のクラスにDynamoDBTableアノテーションを付ける」になります。

NG

class Hoge(... , var hoge : String){
  def this() = this(...) 
  ...
   def getHoge():String = hoge
   def setHoge( hoge : String){ this.hoge = hoge}
}
@DynamoDBTable(tableName="FugaTable")
class Fuga extends Hoge

OK

@DynamoDBTable(tableName="DummyAnnotation") //<- これ重要
class Hoge ...


@DynamoDBTable(tableName="FugaTable")
class Fuga extends Hoge

ちなみに、こうなっちゃう原因はDynamoDBRefrector@isRelevantGetter(DynamoDBRefrector.java:92)の
private boolean isRelevantGetter(Method m) {
    return (m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterTypes().length == 0
        && m.getDeclaringClass().getAnnotation(DynamoDBTable.class) != null
        && !m.isAnnotationPresent(DynamoDBIgnore.class);
}
で、メソッドのDeclaringClassのアノテーションを取ってしまっちゃってるからです。

2012年5月11日金曜日

pythonでファイルやディレクトリを列挙 まとめ

いつもファイルとかディレクトリを列挙しようとするたびにググり続けているので、自分でまとめてみた。


指定したディレクトリのすべてのファイルとディレクトリを列挙

import os

for onlyFilename in os.listdir("mydir"):
    print onlyFilename

--------------------
'dir1'
'dir2'
'file1.txt'
'file2.txt'
'file2.py'
常にファイル、ディレクトリ名だけを返します。

特定の拡張子のファイルだけを列挙

import glob

for filenameWithPathAsString in glob.glob("mytexts/*.txt"):
    print absFilePathAsString

--------------------
'mytexts/hoge.txt'
'mytexts/wahoo.txt'
...

出力には、指定したPathも付けられています

ディレクトリのみを列挙

import glob
import os
import os.path

for filename in glob.glob("mydir/*"):
    if os.path.isdir(filename):
        print filename

--------------------
'mydir/dir1'
'mydir/dir2'
...



再帰的に全てのファイル、ディレクトリを列挙

for path,dirs,files in os.walk("mydir"):
  print path,dirs,files
深さ優先でディレクトリを掘って行きます。 例
"." -> "./dir1" -> "./dir1/hoge" -> "./dir2" -> "./dir2/hoge"

2012年4月24日火曜日

specs2で並列テストをOffにする

ScalaのBDDテストフレームワークのspecs2に関するTips
ありがた迷惑な機能として、specs2ではテスト実行時に自動で並列実行してくれます。
テスト実行は早くなりますが、DBの絡んだテストやファイルの絡んだテストでは問題になることも多々あります。
ただし、次のようにするだけで簡単に逐次実行に変更可能なので並列テストでの問題が出た場合は試してみてください。


import scala.specs2.Specification
// or
import scala.specs2.mutable.Specification

class MediaProxyTest extends Specification{
  
  sequencial // <- この1行を付けるだけ

  /* test codes +/


}