2013年7月17日水曜日

chillを使ってクラス定義変更に強いシリアライズを実現

Java向けのシリアライズライブラリであるkryoと、そのscalaラッパーのchillを使って、クラスの変更に強いシリアライズ手法の実現方法のメモです。

javaのデフォルトのシリアライズだと、SerializableVersionをきちんと付けておかないと、クラスの定義を変更しフィールドを追加した時に過去のデータから復元できなくなります。
この挙動が悪いとは言いませんが、キャッシュの際にこの挙動をされるとクラス変更をするたびにキャッシュがクリアされてしまうことになったり、不意に過去のデータがよみなくなるなど、厳密な挙動では不便な場合が多々あります。なので、kryoを使って、クラスの定義変更に強いシリアライズが出来ないか試してみました。
まず、kryoをそのまま使うと、scalaのcase classがデシリアライズ出来ないので、ここは素直にtwitter様が作ってくれているchillを使いました。
また、kryoもデフォルトではクラスのフィールドを追加したり削除したりすると、過去のデータからはシリアライズできなくなるので、少々設定をいじる必要があります。以下がその方法です。


build.sbt
scalaVersion := "2.10.0"

libraryDependencies += "com.twitter" %% "chill" % "0.2.3"

ソース

import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.twitter.chill._

// kryoのインスタンスを取得
val kryo = KryoBijection.getKryo
// デフォルトのSerializerを、フィールド情報も保存してくれるCompatibleFieldSerializerに変更 
kryo.setDefaultSerializer( classOf[CompatibleFieldSerializer[Any]])

//新しいKryoInjectionインスタンスを作成
val Injection = KryoInjection.instance(kryo)

// シリアライズ
val bytes : Array[Byte] = Injection(Hoge(1,2,"hoge"))

// デシリアライズ
val des : Option[Any] = Injection.invert(bytes)

println(des)// Some(Hoge(1,2,"hoge"))


2013年4月17日水曜日

ローカルで削除されたファイルを一括でgit rmする

Mac環境で、ローカルのファイルを削除してgitのindexだけが残ってしまっている場合に、一括でgit rmする方法の紹介です。

コマンドは以下の通り。これをコピペして実行してください。
git status | grep deleted: | cut -c 15- | sed -e 's/ /\\ /g' | xargs git rm

やっていることは、

1. git statusでindexの変更取得
2. grep deleted: で削除されたファイルの行だけを抜き出す。
3. cut -c 15- でファイルのパスにあたる15文字以降だけを抜き出す
4. sed -e 's/ /\\ /g'で、ぱすに空白が含まれてしまっている場合にエスケープする
5. 抜き出されたパスをgit rmする

という手順になります。
行程3の部分が、gitのバージョンによって変化するかもしれないのでもしうまくいかない場合は
git status | grep deleted: | cut -c 15-
だけで実行してみて、正しくパスが取得できているかを確認してみてください。

2013年3月3日日曜日

Scala2.10のReflectionで、class constructorを取得する

まだ書きかけですが、とりあえずさらしておきます。
後日、もう少しバージョンアップします。

やりたい事は、ScalaのReflectionを使ってクラスのコンストラクタを取得して新しいインスタンスを作るです。
case classも、これでパラメータの取得が可能です。

build.sbt
scalaVersion := "2.10.0"

libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.0"
App.scala


import scala.reflect.runtime.universe._
import scala.reflect._

case class Hoge(val name : String)


object App{

  def main(args : Array[String] ) {
    println(newFromParamsMap[Hoge](Map("name" -> "hoge")))
  }
  
  
  def newFromParamsMap[T](params : Map[String,Any])(implicit tt : TypeTag[T],ct : ClassTag[T]) : T = {
    
    val t = typeOf[T]
    
    // Primary constructorの取得
    val primaryConstructor = t.declarations.collectFirst({
      case m : MethodSymbol if m.isPrimaryConstructor => m
    })
    
    if(!primaryConstructor.isDefined){
      throw new Exception(s"Class ${t} doesn't have primary constructor.")
    }
    
    //コンストラクタのパラメーターを取得してくる
    //後日型のチェック機能も入れます。
    val constructorValues = primaryConstructor.get.paramss.map( _params => {
      _params.map( {
        case t : TermSymbol => {
          val p = params(t.name.encoded.trim)
          /*if( ! t.typeSignatur =:= typeOf(p.getClass){
            throw new Exception("Parameter :${t.name.encoded} doesn't match type!)
          }*/
          p
        }
      })
    })
    // 実行のためのMirror取得
    val mirror = tt.mirror
    
    val classMirror = mirror.reflectClass(t.typeSymbol.asClass)
    val constructorMirror = classMirror.reflectConstructor(primaryConstructor.get)
    
    constructorMirror(constructorValues.flatten :_*).asInstanceOf[T]
  }
  
  
}

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です。


参考にしたページ