redshift-fake-driverでAmazon Redshiftをモックする
Amazon Redshiftは便利でコスパのよいDWHですが、時間に対する従量課金なので、ちょっとした動作確認のために立ち上げっぱなしにしとくのが気がひける場合もあります。
もしJVMプロジェクトを開発しているなら、redshift-fake-driverを使うことでRedshiftを代替できます。 github.com
What is redshift-fake-driver
Redshift固有のSQL syntaxを無視したり同等のものに置き換えてPostgresqlまたはH2にプロキシする、JDBCドライバーです。
UNLOAD, COPYのようなコマンドもサポートしています。
その場合、Java system propertyでS3エンドポイントを渡すことで、fake-s3のようなS3互換のモックを使うことも可能です。
Usage
では、fake-s3とPostgresqlを使って、使い方を見ていきます。
言語はScalaで、実行はAmmonite REPLで行います。
0. Postgresqlのセットアップ
$ createuser sample_user $ createdb -O sample_user sample_database
1. fake-s3のセットアップ
$ gem install fakes3 $ fakes3 -r ./fakes3_root -p 9444
2. 依存の追加
redshift-fake-driverは、artfifactにAWS SDKやPostgresql driverを含まないため、それらの依存を追加する必要があります。
以降、Ammonite REPLは立ち上げっぱなしと仮定します。
$ amm scala> import $ivy.`com.amazonaws:aws-java-sdk-s3:1.11.43` scala> import $ivy.`org.postgresql:postgresql:9.4.1211`
3. バケット作成
scala> import com.amazonaws.auth.BasicAWSCredentials scala> import com.amazonaws.services.s3.{S3ClientOptions, AmazonS3Client} scala> val options = S3ClientOptions.builder().setPathStyleAccess(true).build() scala> val s3Client = new AmazonS3Client(new BasicAWSCredentials("DUMMY", "DUMMY")) scala> s3Client.setEndpoint("http://localhost:9444/") scala> s3Client.setS3ClientOptions(options) scala> s3Client.createBucket("bar")
4. redshift-fake-driverのインストール
scala> import $ivy.`jp.ne.opt::redshift-fake-driver:1.0.7`
Scala以外のJVM言語の場合、artifact名はredshift-fake-driver_2.11
などとすればよいです。
また今回fake-s3を使うため、Java system propertyを以下のように設定します。
scala> sys.props.put("fake.awsS3Endpoint", "http://localhost:9444/") scala> sys.props.put("fake.awsS3Scheme", "s3://")
5. CREATE TABLE
Redshift固有の情報を含んだDDLでテーブルを作成します。
redshift-fake-driverを使うときは、JDBC接続文字列をjdbc:postgresqlredshift
で始めます。
scala> import java.util.Properties scala> import java.sql._ scala> val url = "jdbc:postgresqlredshift://localhost:5432/sample_database" scala> val prop = new Properties() scala> prop.setProperty("driver", "jp.ne.opt.redshiftfake.postgres.FakePostgresqlDriver") scala> prop.setProperty("user", "sample_user") scala> Class.forName("jp.ne.opt.redshiftfake.postgres.FakePostgresqlDriver") scala> val conn = DriverManager.getConnection(url, prop) scala> val stmt = conn.createStatement() scala> stmt.execute(""" |CREATE TABLE foo_bar(a int ENCODE ZSTD, b varchar(255)) |DISTSTYLE ALL |DISTKEY(a) |INTERLEAVED SORTKEY(a, b); |""".stripMargin)
ちゃんとテーブルが出来ています。
$ psql -U sample_user -d sample_database sample_database=> \d foo_bar Table "public.foo_bar" Column | Type | Modifiers --------+------------------------+----------- a | integer | b | character varying(255) |
6. UNLOAD
まずデータを入れます。
$ psql -U sample_user -d sample_database sample_database=> INSERT INTO foo_bar (a, b) VALUES (1, 'one'); sample_database=> INSERT INTO foo_bar (a, b) VALUES (2, 'two');
UNLOADを打って、fake-s3のデータを確認します。
scala> stmt.execute("""UNLOAD ('select * from foo_bar') to 's3://bar/result' |CREDENTIALS 'aws_access_key_id=DUMMY;aws_secret_access_key=DUMMY' |ADDQUOTES""".stripMargin)
ちゃんとデータが出来ていることがわかります。
Redis cluster + lettuceでmget/msetすると複数リクエスト飛ぶ
Redis clusterに対してmget/msetしたとき、slotが異なるものが混在しているとCROSSSLOT error
が返ります。
したがってhash tagを用いてslotを指定するか、まとめたい単位でhash型のvalueに突っ込むなどのkey設計にする必要があります。
Redis Cluster Specification – Redis
いっぽう、lettuce(async対応のJava Redis client)では、mset/mget中にslotの異なるkeyが混在している場合、エラーを返すのではなく「slotごとにリクエストを投げる」実装になっています。(2018/4/4時点)
なので、key設計ミスっててもちょっと動かす分には問題なくて、負荷をかけた時にめっちゃリクエストが飛んでパフォーマンス出なくてミスに気づく、みたいなことになりえます。(それでハマった)
goofysを使ってS3以外のストレージをfstabでマウントする
AIKATSU! STREAMというChrome extensionを作った
生産性をあげるため、アイカツ!のライブシーンを延々と再生し続けるAIKATSU! STREAMというChrome extensionを作りました。
(dアニメストアへのログインが必要です)
とはいえ、jsで動画を解析してライブシーンを抜き出すようなことをしてるのではなく、各話のライブシーンの開始/終了をあらかじめ(人力で)抜き出してあって、それを元にシークしているだけです。
いまのところプレイリストに含まれるのは第一部のみです。dアニメストアには第二部〜スターズもあるので、やる気が出たら追加します。
そして後から気づきましたが、YouTubeのaikatsuTV channelでも十分だったかもしれない...
2017年の振り返り
1-3月
- 広告効果測定ツールADPLANのリリース
- 自分は集計周りをメインで担当してましたが、無事にリリースすることができました。
- ScalaMatsuri 2017登壇
- Introduction to ScalikeJDBCというタイトルで喋りました。
4-6月
- #bq_sushi tokyo #5 @ Google Cloud Community fes登壇
- scalikejdbc-bigqueryというライブラリを作った経緯を技術ブログに書いたのですが、それがきっかけでGoogleの方よりお声がけいただき、登壇しました。 www.slideshare.net
- オプト社内ISUCON開催 tech-magazine.opt.ne.jp
- このために、private-isuのScala版も書きました。
- NieR:Automataにハマった
- キャラもアクションもストーリーも音楽も最高過ぎました。
7-9月
- オプトを退職
- 2016年1月から在籍していたオプトを退職しました。
- とても暖かく送り出していただき、感謝の念に堪えません。
- 実はオプトのOSSのメンテナーは続けてたりします。
10-12月
- LINE株式会社に入社
- 1ヶ月有休消化したのち、10月からLINEで働き始めました。
- 強い人たちばかりで、がんばるぞいという気持ちです。
- アドカレ記事を書いたりしました。 engineering.linecorp.com
- ISUCON7予選に参加
- 初ISUCON参加でした。
- 結構色々なチームがハマってたのと同じく、帯域律速を突破できずにあえなく敗退...
- でもまた参加したい。
反省点
- 音楽を全然やっていない。
来年
JavaとしてもScalaとしてもコンパイルできるHelloWorld
タイトルの通り、以下のコードはJavaとしてもScalaとしてもコンパイルできます。
/*/**/ class JavaMain { public static void main(String args[]) { System.out.println("Hello, World"); } } // */ /*/**/ class App {} // */ /*/**/ class A { int object = 1; int ScalaMain = 1; void foo() { int i = // */ object /*/**/ ; i = // */ ScalaMain /*/**/ ; } class B // */ extends App { /*/**/ void bar() { // */ System.out.println("Hello, World"); /*/**/ } // */ } /*/**/ } // */
↑をPolyglot.java
, Polyglot.scala
として保存して試してみましょう。
$ javac Polyglot.java $ java JavaMain Hello, World $ rm -f *.class $ scalac Polyglot.scala $ scala ScalaMain Hello, World
わぁい動いた!
$ javac -version javac 1.8.0_77 $ scalac -version Scala compiler version 2.12.3 -- Copyright 2002-2017, LAMP/EPFL and Lightbend, Inc.
解説
ScalaとJavaにおける、ブロックコメントの挙動の違いがポイントです。
Scalaでは、ブロックコメントをネストさせた場合に/*
と*/
の対応をきちんと取りますが、Javaでは、何個/*
を開こうが、*/
が来た時点でブロックコメントが終了します。
Java:
/* -- (1) /* */ -- (1)が閉じる */ -- 余ってるのでsyntax error
/* -- (1) /* -- (2) */ -- (2) */ -- (1)
これを利用すると、javacにだけ認識されてscalacには無視されるコードを書くことができます。
いっぽう、scalacにだけ認識されるコードを書くのは難しいので、javacでコンパイルできるようにうまいこと色々足しています。
- L2..L6: javacだけに認識されるMainクラス
- L10: scala.Appを使ってScalaのMainクラスを作るので、javacで通るように空のAppを用意しておく
- L14..L39: javacで通るように色々挟みながら、
object ScalaMain extends App { System.out.println("Hello, World"); }
と書いている
まとめ
今回のコードはもちろんGroovyでもコンパイルできるので、Java, Scala, Groovy
のPolyglotができたことになります。
同じ感じでKotlinもいけるかな?
Ammonite REPLで任意のclasspathを追加する
MavenやGradleプロジェクトでも、sbtみたいに手軽にREPLで動作確認したいときがあります。
お手元の開発環境にAmmonite REPLを入れておけば、以下のようにScalaを使って動作確認できるので便利。
$ amm Loading... Welcome to the Ammonite Repl 1.0.2 (Scala 2.12.3 Java 1.8.0_144) If you like Ammonite, please support our development at www.patreon.com/lihaoyi @ interp.load.cp(ammonite.ops.Path("/path/to/classes")) @ import com.example.MyClass
/path/to/classes
は、Mavenプロジェクトだったらtarget/classes
だし、Gradleプロジェクトだったらbuild/classes/main
あたりになるでしょう。
ソース書き換えたらまた読み込み直さないといけないのが面倒ではありますが。