Hadoop / Spark Conference 2019 に行ってきた

2016年以来、実に3年ぶりとのことです。

自分は最近業務でHadoopやSparkを触るようになってきたんですが、 元々この領域は全くといっていいほど経験が無く、社内にもガッツリ触ったことのある人はほんの一握りといった状況です。 多分遅れ取りまくってるんだろうなぁと思いつつ、一度他社のレベルの高さに打ち負かされてこようと思い、参加してきました。

以下、自分が見たセッションのメモです。(自分の記憶力と理解の無さ故に間違えてる可能性が十二分にあります。ご注意ください)

入場

これが噂の象。

f:id:romiogaku:20190315170041j:plain
あいつ・・・あの目

セッション

ご挨拶、ご案内 / 濱野賢一朗さん(日本Hadoopユーザー会)

資料

メモ

  • 本カンファレンスは2016年以来の開催。間が空いたのには特に理由はありません。
  • Hadoopは終わりつつあるのではと言われている
    • たしかにMapReduceは収束
    • HDFSは健在。でも実は大きな変化点を迎えた。
    • 並列分散処理を支える基盤として主流。まだまだ進化中
  • このカンファレンスは Hadoop,Spark に限らず並列分散システムに関する総合イベントです。

Apache Hadoopの現在と未来 / 鯵坂明さん(Hadoop PMC member)、Arpit Agarwal(Hadoop PMC member)

資料

要約

Hadoop2系、3系の利用者が増えてきた。 Hadoopはこれからも進化を続け、ディープラーニングまわりのHadoop {Submarine} ProjectやオブジェクトストレージのOzoneの開発が進んでいる。

メモ

  • 事前のアンケート結果紹介
    • 2系,3系の利用者が進む
    • クラウド利用も増えているがオンプレミス優勢。
    • クラスタ台数は10台までが多数。データ量はきれいに割れた。~10G~10PB~
    • 使ってるMWはHive、Kafka,Jupyter,Fluentd,Presto...
  • スケーラビリティ
    • Federationでクラスタを束ねる機能が追加され、マスタの負荷を分散できるようになった。
    • オブジェクトストレージ機能の開発(Ozone)が進んでいる。
    • HDFS Erasure Codingによるディスクの節約!
  • プロジェクト構成の変化
    • SubmarineOzone の追加。
  • Submarine
    • ディープラーニングに関する機能。
    • YRANの最新機能を活用して、TensorFlow, PyTorchなどをHadoop上で分散実行させる
      • GUP isolation, Docker on YARN, Container-DNS support
  • Ozone
    • Arpit氏がこのあと紹介します。
  • Hadoopの未来はどうなる>k8sとできることは変わらないはずなのに、k8sが今流行っている理由は? 利用しやすさ?
    • 元々の論文は同じだった。
  • 野望: Java11対応等、リリースサイクルの加速、プロダクトのさらなる分割?

The Ozone Object Store / Arpit Agarwal(Hadoop PMC member)

資料

...

要約

HDFSは従来、小さいファイルの格納が非効率だった。 ストリームデータ処理等、新しいニーズに対する処理を効率よく行えるようOzoneというオブジェクトストレージを開発している。 HDFSの完全上位互換? f:id:romiogaku:20190315170518j:plain

メモ

  • Ozoneとはオブジェクトストレージである。HDFSがあるのになぜ?
  • HDFSは小さいファイルの格納が非効率。3億ファイルぐらいが一つの壁。
  • 新しいニーズもでてきた
    • ストリームデータ処理
    • データソースとしてのS3
  • 既存のHDFSからそのまま移行できる、数十億ファイルを格納できるデータストアが必要だ!
  • Ozoneとは
    • HDFSの精神的続編
    • HDFSのよいところはそのまま
    • HDFSと併用可能
    • HDFSのデータをそのまま以降可能
    • NameNode (マスターノード)がボトルネックにならない
    • アプリケーションの回収は不要
    • 現状
      • 2つのアルファ番がリリース済み、3番目のアルファ版が準備中。ベータ版も3つ出す予定。
    • 設計方針
      • 強い一貫性
      • シンプルなアーキテクチャ
      • 実証済みの部品を使う(Raftによる冗長化、データはRocksDBで管理)
      • Hadoop,Sparkエコシステムの各種プロダクトが使える

The upcoming Spark 3.0: What’s Next / 猿田浩輔さん(Sparkコミッタ)、Xiao Li(Spark PMC member)

資料

...

要約

Spark2.4までの機能振り返り

Spark3.0、新機能続々!主に機械学習周りがアツそう。

f:id:romiogaku:20190315170729j:plain
spark2.4

f:id:romiogaku:20190315170810j:plain
spark3.0

メモ

  • Sparkはデータ活用のトレンドとともに進化してきた。機械学習とか。
  • 2.0では
    • Structured Streaming
    • Pandas UDF、ビルトイン関数の拡充。Pythonからの活用利便性向上..etc
    • Project Hydrogenの発表
    • 分散コンピューティングのトレンド
      • 2.3からk8sサポートが始まった。
      • でも今はやはりYARNと組み合わせた利用が圧倒的に多い。(事前のアンケートによると)
      • k8sサポートはまだ始まったばかり。安定性もこれから。
      • SparkのプラットフォームとしてはまだまだYARN一強。
      • 大量の計算資源を必要とするバッチならYARN、組織内の計算資源をシェアしてアドホックにサービスがが実行されるようなケースならk8s、のような選択も。
    • ハードウェアのトレンド
      • Project Tungsten(Spark1.5) では資源を使い切る、ということにフォーカスしてた
      • 3.0 Project Hydrogen
    • 使いやすさはユーザにしかわからない。ユーザはがんがん要望を送ってくれ!
  • 3.0では
    • シャオ氏にバトンタッチ。
    • 今年リリース。新機能は?
      • AI
      • Big data vs AI tecnology
    • Project Hydrogen: Spark + AI
      • Sparkのスケジューラを刷新し、分散実行されるDeep Learningのジョブを効率的に行えるようにし、これで分散学習のワークフローを簡素化。
      • GPUを考慮したスケジューリング。
      • 機械学習の本番運用には困難が伴う。
        • なぜ?
          • 機械学習のライフサイクルは手作業で統一性がなく、連携されてない。(前処理、モデルの構築、モデルの配信。。
      • mlflow tracking
      • mlflow projects
      • mlflow models
    • 既存のグラフライブラリの課題
      • GraphX : DataFrameに対応してない、あまりメンテされてない
      • GraphFrame: グラフのパターンマッチに制限がある、意味的に弱いグラフデータモデル
    • Cyper for Spark
    • Spark Graph
      • プロパティグラフ(属性が付与されたグラフ)とCyperクエリが与えられると、SparkがDataframeを返す
    • Data Source API V2
    • クエリ実行時の再最適化
    • Spark本体でのk8sサポート

Cloud-Nativeなデータ分析基盤におけるPrestoの活用 / 廣瀬智史さん(SmartNews, Inc.)

資料

要約

スマートニュースではチーム毎にクラスタがあり、データが分散している。 それらを跨いだ集計処理のためにPrestoを活用している。

OASISApache Spark を活用した LINE 全社のデータ分析ツール / 吉田啓二さん(LINE株式会社)

資料

要約

LINEではOASISというwebベースのアナリシスプラットフォームを開発し、様々な部署で様々なスキルセットを持つ人が利用している。 Apache Zeppelinが求めているものに近かったが、セキュリティと安定面で採用を見送った経緯がある。 OASIS上でsparkアプリケーションが書け、ステップごとに異なる言語で書くことも可能。(sparkのcreateTempViewを使っている) 今年中にOSS化を目標にしている。

[LT] スキーマ付き分散ストリーム処理を実行可能なFlinkSQLClientの紹介 / 木村宗太郎さん(dotData Japan)

資料

要約

弁当に夢中であまり聞けませんでした...

[LT] データサイズ2ペタ ソネット・メディア・ネットワークスでのImpala活用とHadoop運用 / 菅沼嘉一さん (ソネット・メディア・ネットワークス)

資料

要約

CDHのバージョンアップは本当に鬼門らしい。弊社でも古いCDHをどうするか検討してるけど、軽い気持ちでやっちゃダメなんだと認識。

[LT] SparkをRESTfulに利用できるApache Livyを導入した話 / 植草智輝さん(ヤフー株式会社)

資料

要約

Apache LivyはSparkをRESTfulに利用できるAPIサーバ。(OSS)

ヤフーで導入してみたが、Livy自体がHA未対応だったりして妥協したところもあった。

sparkジョブがLivy経由でできるようになった。これにより各自のjupyter notebookやzeppelinからSparkを利用可能になり、社内のspark利用者が増えた。

[LT] Introduction to Apache Hivemall v0.5.2 and v0.6 / 油井誠さん(トレジャーデータ株式会社)

資料

要約

諸事情で途中までしか聞けず。

DataFrameとDatasetの内部をのぞいてみる / 石崎一明さん(日本アイ・ビー・エム株式会社)

資料

要約

Datasetはラムダ式を使って処理が書けるので、ループを書けたりして便利。

が、DataFrameとDatasetで単純な処理を書いたところ、DSはDFに比べて何倍も遅いことが判明した。 Spark2.2ではコードを改良し高速化を実現できた。(まだ改善の余地はある)

ぜひDatasetを使ってみてほしい。issueを投げて声を上げてほしい。

Deep Dive into Spark SQL with Advanced Performance Tuning / 上新卓也さん(Databricks)

資料

要約

SparkSQLの内部構造について紹介。

最適化のためには実行プランを見たり、節度を持ってCacheを利用したり、Equal JOINしたりしましょう (自分にとっては難しい内容でした。)

スキーマレスカラムナフォーマット「Yosegi」で実現するスキーマの柔軟性と処理性能を両立したログ収集システム / 井島洸二さん(ヤフー株式会社)

資料

要約

多様なスキーマを扱え変更に強いJSONと、処理性能の良いカラムナフォーマット(ORC,Parquet)のいいとこ取りをしたようなデータフォーマットの「Yosegi」を開発した。

ネストスキーマが課題だったが、フラットスキーマへ変換することで対応した。

OSS化してるのでぜひ使ってみてください。 https://github.com/yahoojapan/yosegi

Hive/Spark/HBase on S3 & NFS -- HDFSを運用しない気軽なHadoop/Spark / 蒋 逸峰さん

資料

要約

Hadoopコンポーネントで一番事故りたくないのはHDFSHDFSの代わりにNFSやS3を使ってみよう。

sparkならURIスキーマs3a://とすれば良い。 rename処理に関してS3は遅いという特徴がある。LIST - COPY - DELETEという操作になる。 最適化コミッターを使いましょう。

Apache Kafkaって本当に大丈夫?~故障検証のオーバービューと興味深い挙動の紹介~ / 土橋昌さん(株式会社NTTデータ

資料

要約

Kafka検証環境で様々な故障をおこして実験してみた。

概ね想定通りだが、Zkのアンサンブル故障とデータファイルの欠損内容によってはBrokerが起動しないことがあった。

検証時のversionは1.1.0だったので、最新だとまた異なるかもしれない。

メモ

  • 故障検証のoverview
    • version 1.1.0 (検証当時の最新)、Broker4台、3台がZookeeper同居
    • 故障例
      • 物理コンポーネントの故障を検証(物理
      • Brokerを物るじゃなくてなんらかの方法で故障させたり
      • zkを故障したり
      • マシンリソース不足を再現したり
      • ログファイルとインデックスファイルを何らかの形で故障させたり
    • 結論
      • 基本的に想定通り
      • Zkのアンサンブル故障は一部注意が必要
      • データファイルの欠損内容によってはBrokerが起動しないこともあった。
    • 公式ドキュメントに記載されたアーキテクチャから想像される挙動と概ね合致する。
    • Exactry Onceのメッセージ重複に関しては話が違ってくるので横に置いとく
  • Zkアンサンブル故障
    • Zk3台中2台が故障したときの挙動
    • client(producer,consumer)使ってエラー無くメッセージ送受信できるか調べた。
    • APIを使ってるConsumerは故障後はメッセージ送受信できなくなるが、新APIは故障後もできた。(故障後数十分程度は)
    • 旧はクラスタ情報の取得やOffsetの記録毎にZKを利用してる。新は直接は依存して無くて、BrokerがZK利用してるだけで、Clientは大丈夫。でも、Topicの情報取得はできなくなる。
    • 新でZK故障した後に一部のBrokerが停止した場合、一部Brokerが停止した際からメッセージの送受信が行えなくなる。
    • 考察
      • ZKを直接利用しないAPI、機構はアンサンブル故障後の刹那に停止するわけではない。
      • その後Broker故障などクラスタに状態に変化を及ぼす事象が生じたらその限りではない。
  • データファイル(ログファイル)異常
    • インデックスファイルとログファイルそれぞれに対して...
      • 存在すべきファイルが無い
      • 規定サイズと違う、内容がおかしい
      • ファイル自体をダミーファイルにして破損してる状況にしてみる
    • 結論
      • BrokerがGracefulでない停止後の起動では可能な限りのリカバリ処理が行われ、いずれのケースでもBrokerが起動する。(すごい) 
      • Graceful shutdownの場合、一部の状況において正常にリカバリされないケースが存在する。
        • Active segmentがおかしい場合、Brokerが起動しない
        • not active segmentの場合OK
    • 極力Kafka側でリカバーしてくれる挙動をしている。
    • 起動時のサニティチェックが、検証当時と今とで結構かわってきている。spark sanityCheckメソッド。1系と2系で結構違ってくるみたい。
  • クロージング
    • 概ね想定通り、一部注意。アンサンブル故障とログファイル異常。どちらも重要なものなので注意してね。

以下、見れなかったもの

Apache HBaseの現在 - 火山と呼ばれたHBaseは今どうなっているのか / 鈴木俊裕さん(Cloudera)

資料

機械学習、グラフ分析、SQLによるサイバー攻撃対策事例(金融業界) / 小野寺誠さん(マップアール・テクノロジーズ株式会社)

資料

...

1日100個以上のHadoopクラスターを使い捨てる方法 & Spark Streamingで全世界の混雑状況を20分ごとに集計 / 中里浩之さん(ソフトバンク) 濱田佑さん(ソフトバンク

資料

HDFSのスケーラビリティの限界を突破するためのさまざまな取り組み 〜エクサバイト級の分散ストレージを目指して〜 / 浅沼孝信さん(ヤフー株式会社)

資料

Hadoop/Spark で Amazon S3 を徹底的に使いこなすワザ 関山宜孝さん(Amazon Web Services Japan)

資料

Ozone: An Object Store for Apache Hadoop / Arpit Agarwal(Cloudera)

資料

...

HDFS におけるサポータビリティ(保守性)の改善について / 小林大輔さん(Cloudera)

資料

An Insider’s Guide to Maximizing Spark SQL Performance / Xiao Li(Databricks)

資料

Automation of Hadoop cluster operations in ARM Treasure Data / Yan Wang(トレジャーデータ株式会社)

資料

...

Spark SQL の性能改善の取り組み / 吉田啓二さん(LINE株式会社)

資料

マルチテナント Hadoop クラスタのためのモニタリング Best Practice / 平野智巌さん(楽天株式会社)

資料

Arrow_Fdw - PostgreSQLで大量のログデータを処理するためのハードウェア最適化アプローチ / 海外浩平さん(HeteroDB)

資料

confdの簡易版みたいなやつを作ってる話

皆さんアプリケーションの設定値の管理ってどうやってますか?

例えばLaravelだと以下のような.envファイルを用意すると思います。

APP_NAME=MyApp
APP_ENV=local
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=mydb
DB_USERNAME=mydbuser
DB_PASSWORD=mydbpassword
OTHER_APP_API=http://example.com/api/v1/hoge

APP_NAME等アプリケーション固有のものもあれば、DB_HOSTのように環境(ローカル環境、テスト環境、ステージング環境、本番環境...etc)によって値を変えたいものもあります。社内の特定のAPIに依存している場合はOTHER_APP_APIのように書くこともあるでしょう。

アプリケーション毎・環境毎に設定ファイルを用意すると、設定値の更新が必要になった場合、依存するアプリケーション全てに更新作業が発生してしまいます。 特にマイクロサービス寄りのアプリケーションの場合この傾向は顕著です。

これを回避するため、etcd等のKVSに設定値を保存しておき、各アプリケーションはそちらを参照する方法があります。

confd

etcd等のデータストアから値を取り出し、設定テンプレートに反映して設定ファイルを出力するconfdというツールがあります。

github.com

以下のようなテンプレートファイルと、tomlで書かれたテンプレートリソースという2つのファイルを用意することで、テンプレートに値を埋めて出力してくれます。

テンプレートファイル

upstream {{getv "/subdomain"}} {
{{range getvs "/upstream/*"}}
    server {{.}};
{{end}}
}

server {
    server_name  {{getv "/subdomain"}}.example.com;
    location / {
        proxy_pass        http://{{getv "/subdomain"}};
        proxy_redirect    off;
        proxy_set_header  Host             $host;
        proxy_set_header  X-Real-IP        $remote_addr;
        proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
   }
}

{{getv "/subdomain"}} と書くと /subdomain というkeyで保存された値を埋めてくれます。

テンプレートリソース

[template]
prefix = "/yourapp"
src = "nginx.tmpl"
dest = "/tmp/yourapp.conf"
owner = "nginx"
mode = "0644"
keys = [
  "/subdomain",
  "/upstream",
]
check_cmd = "/usr/sbin/nginx -t -c {{.src}}"
reload_cmd = "/usr/sbin/service nginx reload"

keys にはテンプレートファイル中で使用するkeyを並べます。

confdには値の変更をwatchする機能もあり、check_cmdreload_cmdでファイルの検証と反映のコマンドを書くことができます。

confgen

confdは多数のバックエンドにも対応しており非常に有用なのですが、テンプレートリソースを用意するのが少し面倒だなと感じました。

watch機能に関しても、実運用において特定の設定値に依存している各アプリケーションが一斉に切り替わってほしいという要件はほとんど経験したことが無く、そこまで必要かな?という印象です。(逆にアプリケーション側のタイミングで更新したいというケースが多かった気がする)

仮に必要だったとしても、watchに関しては他のツールにおまかせして「KVSから値を取得し、設定ファイルを出力させる」ことだけに特化したツールが欲しくなり、作ってるところです。

github.com

使い方

confgenはgoで書かれたコマンドラインツールです。

confdと同じように、以下のような設定ファイルテンプレートを用意します。 テキストファイルだったらなんでも構いません。

db {
  host = "{{v "/myapp/database/host"}}"
}

バックエンドとして以下のKVSに対応しています。

  • etcd(v3)
  • consul
  • zookeeper

事前に以下の値をKVSに保存しておきます。

Key: "/myapp/database/host"
Value: "localhost"

コマンドは以下のように書きます。

$ confgen -file [テンプレートファイルパス] -backend [etcd|consul|zookeeper] -node [バックエンドノード]

etcdなら以下のように書きます。

$ confgen -file example/application.conf.tmpl -backend etcd -node localhost:2379

実行すると、標準出力に

db {
  host = "localhost"
}

と出力されます。 ファイルに出力したい場合は> application.conf のようにしてあげてください。

テンプレートリソースが不要になり、動的に差し込みたい部分を{{v "/key"}}とするだけで良いので既存のアプリケーションからの移行も楽にできるのではないかと思います。 実際の利用ではアプリケーションのデプロイ時にCI等でconfgenを実行しファイルを出力したり環境変数に利用したりすることになるかと思います。

今後

  • バックエンドには認証機能を持つものがほとんどなので、それらにも対応する必要がありそう。
  • 配列の展開等、テンプレート表現力を豊かにするか否か。してもいいけどgoテンプレートの書き方を利用者に覚えてもらう必要がある。
  • 機密情報の扱いをどうするか(confgenの責務ではないと思うけど)
  • バックエンドに値を入れるフローのベストプラクティスはなんだろう(これもconfgenの責務ではない)

最後に各バックエンドの紹介とローカルで爆速構築する手順を自分用メモとして残しておきます。

etcd

etcdとは

分散キーバリューストア。golang製。

公式

ローカルで動かすには

起動

$ etcd

操作

※ etcdのバージョンがv3.3以下なら環境変数ETCDCTL_API=3 を追加しておくこと。v3.4からはこれがデフォルトになる。

保存

$ etcdctl put mykey "this is awesome"

取得

$ etcdctl get mykey

値だけ取得

$ etcdctl get foo --print-value-only
bar

削除

$ etcdctl del mykey

一覧

$ etcdctl get "" --prefix

出力フォーマットを変える

$ etcdctl --write-out="simple" get mykey

"fields", "json", "protobuf", "simple", "table" が使える

Consul

Consulとは

  • サービスディスカバリ
  • ヘルスチェック
  • キーバリューストア

等の機能を持ったツール。

よくKVSとしてetcdと比較されるが、それ以外の機能もある欲張りなやつ。(むしろサービスディスカバリがメインだと思う)

クライアント・サーバ構成でクラスタリングできる。両方共consulというバイナリ一つで動作する。(golang製)

f:id:romiogaku:20190203213835p:plain

わかりやすいスライド

https://www.slideshare.net/ssuser07ce9c/consul-58146464

公式

チュートリアルが充実している。

ローカルで動かすには

起動

$ consul agent -dev

devモードで起動。

操作

保存

$ consul kv put mykey "this is awesome"
Success! Data written to: mykey

取得

$ consul kv get mykey
this is awesome

詳細表示

$ consul kv get -detailed mykey
CreateIndex      64
Flags            0
Key              mykey
LockIndex        0
ModifyIndex      64
Session          -
Value            this is awesome

メタ番号(flag)も付与できる

$ consul kv put -flags=42 hoge fuga
Success! Data written to: hoge

$ consul kv get -detailed hoge
CreateIndex      69
Flags            42
Key              hoge
LockIndex        0
ModifyIndex      69
Session          -
Value            fuga

全てのkey-value表示

$ consul kv get -recurse
hoge:fuga
mykey:this is awesome

削除

$ consul kv delete mykey
Success! Deleted key: mykey

prefixで始まる全てを削除

$ consul kv delete -recurse hoge
Success! Deleted keys with prefix: hoge

※prefixを省略すると全て削除。

WebUI

$ consul agent -dev -ui

-uiをつけて起動すると http://localhost:8500/ui でWebUIにアクセスできる。

ZooKeeper

ZooKeeperとは

分散システムでよく利用される分散コーディネーションサービス。 Hadoop使うとセットでついてくる例のアレ。

高いパフォーマンスを出せるため、大規模分散システムに向いている。 単純な設定保存のためのKVSとして使うだけならオーバースペックだと思う。

ドキュメント

http://oss.infoscience.co.jp/hadoop/zookeeper/docs/current/index.html

ローカルで動かすには

macならbrew install zookeeperでインストールできる。

公式がdockerイメージを作成しているのでそれを使うのも良い。まんま公式のパクリだが以下をcloneしてdocker-compose upすれば3ノードで立ち上がる。

github.com

接続は

のどれか。

操作

f:id:romiogaku:20190203213517j:plain

ZooKeeperの提供する名前空間は一般的なファイルシステムによく似ている。

ノードはデータを持てると同時に複数の子を持てる。ファイルが同時にディレクトリにもなれるファイルシステムのようなものになっている。

以下

$ bin/zkCli

でZooKeeperに接続した状態とする。

ノード確認

[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

ノード作成

[zk: localhost:2181(CONNECTED) 1] create /mykey "this is awesome"
Created /mykey

ノード情報取得

[zk: localhost:2181(CONNECTED) 2] get /mykey
this is awesome
cZxid = 0x200000012
ctime = Thu Jan 31 08:25:32 GMT 2019
mZxid = 0x200000012
mtime = Thu Jan 31 08:25:32 GMT 2019
pZxid = 0x200000012
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 15
numChildren = 0

データ更新

[zk: localhost:2181(CONNECTED) 3] set /mykey "updated."
cZxid = 0x200000012
ctime = Thu Jan 31 08:25:32 GMT 2019
mZxid = 0x200000013
mtime = Thu Jan 31 08:28:01 GMT 2019
pZxid = 0x200000012
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0

削除

[zk: localhost:2181(CONNECTED) 4] delete /mykey

GUI

qiita.com

sbt-assemblyで指定したサブプロジェクトのみjarファイルを作る方法

情弱だったのでメモ。

こんな感じのマルチプロジェクトの設定がbuild.sbtにあるとします。

import sbt.Keys.{libraryDependencies, organization, resolvers}
import sbt._
import sbtassembly.AssemblyPlugin.autoImport.assemblyMergeStrategy

lazy val commonSettings = Seq(
  scalaVersion := "2.11.12",
  resolvers ++= Seq(
    "mvn" at "https://mvnrepository.com/artifact"
  ),

  libraryDependencies ++= Seq(
    "mysql" % "mysql-connector-java" % "5.1.46"
  ),

  assemblyJarName := {
    s"${name.value}-${version.value}.jar"
  },
)

lazy val common = (project in file("common"))
  .settings(commonSettings: _*)

lazy val subProject = (project in file("subProject"))
  .settings(
    commonSettings,
  )
  .settings(
    name := "subproject",
    version := "0.1",
    mainClass in assembly := Some("com.romiogaku.SubProject")
  )
  .dependsOn(common)

通常

$ sbt assembly

でFatJarを生成すると思います。実行すると全てのプロジェクトのjarファイルができあがります。

以下のようにすると、指定したプロジェクトのjarファイルのみを生成します。

$ sbt subProject/assembly

subProjectbuild.sbtlazy valで定義した変数名。

ErgoDoxを分解した

キースイッチが欲しかったのでもう使ってない手持ちのErgoDoxをバラすことにしました。

ところで、 romiogaku.hateblo.jp

こちらの記事の冒頭で

ErgoDox EZ買ったことがある

と書いてしまったのですが、完全に僕の記憶違いでした。 組み立てた覚えが無かったのでEZだと思ってましたが、本当はFalbaTechでパーツを選んで、組み立て済みの無印ErgoDoxを購入したのでした。

f:id:romiogaku:20190203134146j:plain

EZだったらホットスワップらしいので、ケースを外すだけで良かったんですが・・・(というかホットスワップがどういうものになってるのか見たかったので残念)

スイッチ救出のためがんばってはんだを取り除いていくしかありません。

ちなみに残ってた注文履歴を見るとGreetechの青軸だったらしいです。

f:id:romiogaku:20190203134939p:plain
FalbaTech

f:id:romiogaku:20190203134942p:plain
こっちはWASDで買ったキーキャップ

キーキャップを外す

f:id:romiogaku:20190203134157j:plain

ケースを外す

f:id:romiogaku:20190203134209j:plain

特に道具も必要ありませんでした。

裏返すとこんな感じ。

f:id:romiogaku:20190203134236j:plain
これを全部外すのか・・・

はんだシュッ太郎ではんだ除去

www.amazon.co.jp

はんだ吸い取り線も持ってたんですが、左手塞がるわ線が熱くて持てないわちゃんと吸えないわで文明の利器に頼ることにしました。

f:id:romiogaku:20190203134248j:plain

f:id:romiogaku:20190203134312j:plain

吸ってる最中はとにかく楽です。ただ、景気よく吸いまくってるとポンプがカチッと押せるところまで進まなくなっていくので細かく掃除していかないとだめでした。あとめっちゃ熱い。

救出完了

f:id:romiogaku:20190203134325j:plain

青軸76個ゲット!

無謀にも組み立て難易度が鬼と噂のcolosseum44を注文してしまったので、届いたらこちらに流用しようと思います。

swanmatch.booth.pm

make.dmm.com

あとErgoDoxについて昔プリキュアハッカソンで発表したスライドが残っていたので供養しておきます。

speakerdeck.com

Sparkアプリケーション(Scala)でNoSuchMethodErrorになったら

経緯

BigQueryをDataFrameとして扱うため、こちらのライブラリに依存していました。 https://github.com/samelamin/spark-bigquery

build.sbt

  libraryDependencies ++= Seq(
    "com.github.samelamin" %% "spark-bigquery" % "0.2.5",
  ),

よしなに実装しspark-submitすると以下のようなエラーに遭遇。

Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V
    at com.google.cloud.hadoop.io.bigquery.BigQueryStrings.parseTableReference(BigQueryStrings.java:68)
    at com.google.cloud.hadoop.io.bigquery.BigQueryConfiguration.configureBigQueryInput(BigQueryConfiguration.java:246)
    at com.samelamin.spark.bigquery.BigQueryClient.selectQuery(BigQueryClient.scala:126)
    at com.samelamin.spark.bigquery.BigQuerySQLContext.bigQuerySelect(BigQuerySQLContext.scala:90)

原因

件のライブラリは google/guava というJava用のGoogleコアライブラリに依存しています。 guava はSpark自体にも含まれてるんですが、これが古いバージョンになってます。 spark 2.4.0なら SPARK_HOME/jars/guava-14.0.1.jar というjarファイルが見つかると思います。

sparkは「起動時」に SPARK_HOME/jars 以下をクラスパスに追加するため、アプリケーション側に依存関係を書いても上書かれてしまいます。 その結果 NoSuchMethodError が発生していたようです。

解決方法

sbt-assemblyの shade を利用する方法がgoogleのブログに紹介されていました。 https://cloud.google.com/blog/products/data-analytics/managing-java-dependencies-apache-spark-applications-cloud-dataproc

assemblyShadeRules in assembly := Seq(
  ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll
),

こうすると依存する名前を変更することができ、依存の上書きを回避することができます。

また、名前が変わったので Merge Strategy に以下を追加しました。

case PathList("repackaged", "com", "google", xs@_*) => MergeStrategy.last

これで動くようになりました。

Shading はもっと細かいルールが決められるようなので、適宜設定しましょう。 https://github.com/sbt/sbt-assembly#shading

参考URL:

Mint60を組み立てた

何年か前、ErgoDoxが流行ったときに組み立て済みのErgoDox EZ買ったことがあるんですが、なかなか手に馴染まず結局日常的に使うことはありませんでした。(今でも家の棚にしまってあります)

しかし2018年の後半あたりから自作キーボードに関する記事をよく見かけるようになり、Mint60を衝動買しました。 今度は組み立て済みのキーボードではないので、はんだ付けが必要になります。

組み立ては以下を参考に進めました。

eucalyn.hatenadiary.jp

ダイオードをひたすら刺す

f:id:romiogaku:20190127222352j:plain

ダイオードはこのようなリード線折り曲げ器があると便利でした。

Amazon CAPTCHA

f:id:romiogaku:20190127222414j:plain
この位置で折ると丁度良い塩梅

f:id:romiogaku:20190127222435j:plain

f:id:romiogaku:20190128005430j:plain
全部刺してはんだ付けまでやったところ

ProMicroピンヘッダはんだ付け

はんだ付けの間隔が狭く、ピンヘッダ自体も太いため自分にとっては難易度高めでした。 ちなみにはんだ付けは中学校の授業以来です。

f:id:romiogaku:20190127222805j:plain
持っててよかったブレッドボード

スタビライザ組み立て・取り付け

自分の空間把握能力が足りないのか、公式の組み立て手順写真見てもどこに針金はめるねん、と数十分格闘してました。

f:id:romiogaku:20190127222328j:plain
組み立て

f:id:romiogaku:20190127222340j:plain
取り付け

キースイッチはめ込み

f:id:romiogaku:20190127222606j:plain

以前ErgoDox EZを買ったときは青軸にしてましたが、今回は会社で使うかもしれないことを考慮しキースイッチはGateron Sirent Redを注文しました。

赤軸以外の種類のキースイッチもおまけでついてきており、Escキーは青軸に、Fnキーは黒軸にしてみました。

次にピンヘッダに囲まれている箇所のキースイッチの足がProMicroに当たらないように短くしていきます。

f:id:romiogaku:20190127222730j:plain
足が長いと

f:id:romiogaku:20190127222710j:plain
こんな感じで浮いてしまう

f:id:romiogaku:20190127222554j:plain
短くした状態

ProMicroモゲ防止

USB差し込み口がモゲてしまわないようエポキシ接着剤で固めるんですが、どのあたりまで盛っていいのかはっきり写ってる写真がネット上で見つからなかったので恐る恐るやっていきました。

f:id:romiogaku:20190127222540j:plain

はんだ付け完了!

f:id:romiogaku:20190127222500j:plain
最終的に300回近くはんだ付けしたと思う

この時点で押せるか確認

ケースに入れる前にPCにつないですべてのキーが押せるか試します。

f:id:romiogaku:20190127222312j:plain

幸いにも反応がおかしなところはありませんでした。 むしろおかしな場合はどうやって原因を突き止めれば良いのだろう・・・?

LED取り付け

普段からLED光らせることは無いだろうからこの手順はスルーしてもいいかなと思ったんですが、せっかくなので取り付けました。

f:id:romiogaku:20190127222816j:plain

ただ、基盤は飛び出した端子でボコボコしておりはんだ付けには苦労しました。

ケース組み立て

f:id:romiogaku:20190127222645j:plain

f:id:romiogaku:20190127222606j:plain

と、ここで問題発生。 ケースに取り付けた状態で改めてPCに接続すると、右手N列の挙動が荒ぶってしまいました。

原因はLEDのテープがキースイッチの端子に干渉していたことでした。(公式にもよくあるパターンとして紹介されてました) 一旦ケースをバラしてN列のキースイッチを短くし、LEDのテープは押し付けないよう遊びをもたせるようにすれば治りました。

後で知りましたが、N列に絶縁テープを貼っておけば問題ないようです。今後使っていくうちにまたおかしくなったらそちらで対応しようと思います。

完成!

f:id:romiogaku:20190127222233j:plain

すごい。かっこいい。かわいい。

やはりLEDがあると俺はキーボード作ったんだぞ感が溢れ出てきます。 光るパターンも豊富に用意されており、人に見せるときはレインボーに輝かしておこうと思います。

キーマップの変更

デフォルトのキーマップからほんのちょっと変更しました。

github.com

自分はランチャーとしてAlfredを多用しているのですが、従来のキーボードだと 左手Alt+右手Space で起動していました。 そこで右手左下のキーを LALT(KC_SPC) とすることで違和感無く、かつシングルキーでショートカット起動できるようにしました。

右手下の長いキーのうまい利用方法が思いつかない・・・とりあえずSpaceにしておきました。 あとはMacのキーボードに近い配置にして、CapsLockとCtrlは入れ替えました。(元々入れ替えに使っていたKarabinerはOFFにしました)

最後に

仕事終わりに毎日少しずつ組み立てていったのですが、最終的にちゃんと動いて感動です。 このキーボードがEndgameになるかはしばらく使ってみないとわかりませんが、せっかくなので他のいろんなタイプのキーボードも触ってみたい欲が高まってます。

興味があるもの↓

scrapbox.io

scrapbox.io

www.pixiv.net

でも、気づいたら「自分だったらこういう配置にする!」みたいな妄想が頭を巡っていたりもします。

いつか基盤から作れるようになったら最高ですね。誰か僕と一緒にこの沼入ってみませんか?よろしくおねがいしますよ。

この記事はもちろんMint60で書かれました。

Amazon Echoのウェイクワードをカスタマイズすることに成功した(物理)

以前の記事ではAlexaスキルを作成することでココちゃんが応答してくれるようになりました。

romiogaku.hateblo.jp

しかしAmazon Echoで変更できるウェイクワードは、現時点では以下の4つです。

  • Alexa
  • Echo
  • コンピューター
  • Amazon

Amazon Echoを純度100%ココちゃんにするには、ウェイクワードを「ハローココちゃん」にする必要があります。

ここで抜け道があることを発見します。

f:id:romiogaku:20190106232251p:plain

アクションボタンです。

アクションボタンを押すことでウェイクワードを言う必要がなくなります。つまり、「ハローココちゃん」と呼びかけたらアクションボタンが自動で押されるようにすれば良いのです!

f:id:romiogaku:20190106011917p:plain

実現するにあたって、以下が課題となります。

  • どうやってアクションボタンを押すか
  • どうやって音声認識させるか

どうやってアクションボタンを押すか

物理的に押すものが必要になります。

今回はRaspberry Piサーボモータを動かすことにします。「ハローココちゃん」という音声を認識したらモータを動かしてアクションボタンを押すという流れにします。

どうやって音声認識させるか

ググってみると音声認識の選択肢としては以下がありました。

Julius

julius.osdn.jp

フリーの音声認識ソフトウェアです。

本体とは別に音響モデル・言語モデルが入った基本的なディクテーションキットが配布されており、以下のように実行できます。

$ julius -C ~/julius/julius-kit/dictation-kit-v4.4/main.jconf -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf

f:id:romiogaku:20190106233427p:plain

「ハローココちゃん」と呼びかけてますが、うまく認識ししてくれません。

認識率を上げるために独自辞書を作成し実行してもみましたが、関係ない音声も認識してしまいました。

良いやり方があると思うのですが、脇道に逸れてしまいそうだったので断念しました。

音声認識 API

qiita.com

GoogleやMSなどが提供している音声認識APIです。

最初は無料だけど基本的にお金がかかります。認識精度に関しては非常に高いです。

試しにGoogleCloud Speech-to-TextAPIを利用してマイク音声を認識させてみました。

golangでの実装例はこちらが参考になりました。というかこれをそのまま動かしました。

go-talks.appspot.com

良い感じでしたが、今回のケースでは常時起動させておく必要があり、すぐ無料枠を超えてしまいそうです。実際そういう方も居るようでした。

blog.be-dama.com

Web Speech API

ブラウザで動作する「音声合成」と「音声認識」機能です。現在Chromeのみ対応してます。 Googleのデモページはこちらです。

www.google.com

初回だけマイク許可する必要がありますが、内部ではGoogle音声認識APIを実装しているらしく精度は非常に高いです。

無料だしJSでさくっとできそうなので今回はこちらを採用することにしました。

用意したもの

物理的に必要なものを買い揃えました。

ラズパイはBTC採掘を夢見て5年程前に購入したものが眠ってましたが動きませんでしたので、マルツの新春セールで最後の一個を買いました。3980円なり。

ラズパイOSインストール

公式に従ってインストールします。

https://projects.raspberrypi.org/en/projects/raspberry-pi-getting-started/2

SDカードフォーマットはこちらを利用します。

www.sdcard.org

USBマイクの優先度変更

デフォルトだと標準のオーディオ出力端子が優先されてしまうので、この順番を変更する必要があります。

順番は以下で確認できます。

$ cat /proc/asound/modules
0 snd_bcm2835
1 snd_usb_audio

変更するためには /lib/modprobe.d/aliases.conf を編集します。

options snd_usb_audio index=0
options snd_bcm2835 index=1
options snd slots=snd_usb_audio,snd_bcm2835

オリジナルのファイルには snd-usb-audio と書かれているのですが、これはタイポだそうで正解はsnd_usb_audio(アンスコ繋ぎ)です。自分はこれで時間を費やしました・・・

参考:

raspberrypi.stackexchange.com

編集後再起動したら

$ cat /proc/asound/modules
0 snd_usb_audio
1 snd_bcm2835

となっていることを確認します。

サーボモータ接続

f:id:romiogaku:20190107205450p:plain

3.3V電源、GND、GPIO18(ピン番号12)をサーボモータの各ケーブルと接続します。SG90のデータシートはこちら

f:id:romiogaku:20190107210018j:plain
バランス悪い

サーボモータをプログラムから動かす

ラズパイで電子工作のプログラミングといえばpythonが多い印象ですが、調べてみるとGobotというロボティクスフレームワークがあるっぽいのでこちらを使いました。(普段Go書くことが多いので...

Gobotにはサーバ機能があります。API化することで特定のエンドポイントにリクエストが来たらサーボモータを動かす、なんてことができます。今回の目的にぴったりです。

github.com

func main() {
    master := gobot.NewMaster()
    adaptor := raspi.NewAdaptor()
    servo := gpio.NewServoDriver(adaptor, "12") //GPIO(18)

    work := func() {
        // 13 ~ 41 = 0度 ~ 180度
        servo.Move(uint8(27)) //center
        gobot.After(1*time.Second, func() {
            servo.Move(uint8(35))
            gobot.After(500*time.Millisecond, func() {
                servo.Move(uint8(27))
            })
        })
    }

    robot := gobot.NewRobot("servoBot",
        []gobot.Connection{adaptor},
        []gobot.Device{servo},
        work,
    )

    server := api.NewAPI(master)
    server.AddHandler(func(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    })
    server.Port = "3000"
    server.Start()

    servoEndpoint := master.AddRobot(robot)
    servoEndpoint.AddCommand("move", func(params map[string]interface{}) interface{} {
        reqParam := fmt.Sprintf("Params: %+v\n", params)
        robot.Start()
        return reqParam
    })

    master.Start()
}

JSから呼ぶのでCORSの設定もしています。

http://localhost:3000/api/robots/servoBot/commands/move にリクエストを送ればサーボモータが動きます!

f:id:romiogaku:20190107203916g:plain
キュイキュイいってる

Web Speech API

サンプルコードはこちらが参考になりました。

https://w3c.github.io/speech-api/#examples-recognition

以下のように実装しました。 github.com

const recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.lang = "ja";
recognition.onresult = event => {
  for (let i = event.resultIndex; i < event.results.length; ++i) {
    if (event.results[i].isFinal) {
      const word = hiraganaToKatakana(
        removeSpace(event.results[i][0].transcript)
      );
      console.log(word);
      if (word.match(/ハローココチャン/)) {
        axios({
          method: "get",
          url: "http://localhost:3000/api/robots/servoBot/commands/move",
          headers: { "Content-Type": "application/json" }
        })
          .then(function(response) {
            console.log(response);
          })
          .catch(function(error) {
            console.log(error);
          });
      }
    }
  }
};

onresult で認識結果を取得します。「ハローココちゃん」にマッチしたらhttp://localhost:3000/api/robots/servoBot/commands/move にリクエストを送っているだけです。

こちらはserve コマンドかなんかでサーバを立ち上げてアクセスし、事前にマイク許可しておきます。

サーボモータを固定する

ココちゃんをぐるぐる巻きにします。

f:id:romiogaku:20190107211437j:plain
急に雑になる

完成!

youtu.be

最後に

まさか本格的IoTがこんなことになろうとは自分でもびっくりです。

ラズパイ初心者ということもあって結構時間かかった割に電子工作の知識はほとんど吸収できなかった気がします。でも普段Web系ばかりなので良い刺激になりました。組み込み系のひとすごいなぁ・・・

WebSpeechAPIに関してですが、こういう使い方(常時付けっぱなし)は想定してないと思うので、そのうち制限されるんじゃないかなと思います。ブラウザリロードするだけで延々と使い続けられるというのは流石に抜け道臭いです。

あとAlexaのスキルですが、著作権的にアウトなのは個人で楽しむとして、ストアに出しても問題無いようなスキルも思いついたのでそのうち出せたらいいなと思います。