スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

【ハンズオンあり】オンラインマイグレーションツール gh-ost を導入した話

最近、一気に寒くなったせいで、

寒暖差疲労なるものに襲われ

調子が悪いアルバイトの本田です😷


それはさておき、

先日のブログご覧いただけたでしょうか?

エイチームさん主催の勉強会

オンラインマイグレーションツール gh-ost について紹介したと思います。

labs.spookies.co.jp

すると、弊社CEOとアンバサダー @masayuki14 より ↓こんなメッセージが!

f:id:spookies_honda:20181218102507p:plain:w400:left










ということで、

今回は、実際にスプーキーズの開発に導入してみた話です。

(ローカル環境で動作確認したところまでのお届け🎅🤶🦌)

gh-ost とは

先日のブログを読んでいない方もおられると思うので(ぜひ読みに行ってください!→先日のブログ

まずは、簡単なgh-ostの紹介↓

  • オンラインマイグレーションツール
  • 正式名称:GitHub's Online Schema Migrations for MySQL
    • 名前から見て分かる通り、GitHub製です
  • OSSとして開発が進む
  • 本番で動いているテーブルのコピー(ゴーストテーブル)を作成し、そっちにmigrateかけて、できあがったら本番のものと入れ替え
  • 本番用のテーブル内のデータが更新されたときは、非同期でゴーストテーブルも更新
    • Slave DBのbinary log を監視することにより実現

ローカル環境で動かしてみた

導入にあたり、まずは、ローカル環境でgh-ostの動作を試してみました。

ここからの作業は

エイチームさんの勉強会で、gh-ostについてお話されていた

s2terminal さんが書かれたQiita記事↓をもとに進めていきます。

qiita.com

  • テスト環境
    • macOS(Ver. 10.13.4)
    • Docker for Mac(Ver. 2.0.0.0-mac78 (28905))
    • gh-ost(Ver. 1.0.47)

1. gh-ostインストール

  1. GitHubからgh-ostをダウンロード
  2. ダウンロードした圧縮ファイルを解凍し、実行ファイルgh-ostを取り出す
  3. gh-ostの実行ファイルをパスが通っているところに移動
  4. 動作チェック
$ gh-ost -version
1.0.47

OK!

2. Docker上でMySQL 環境を構築

  1. Docker公式サイトからDockerをダウンロード(アカウント作成必須)
    • 詳細は割愛
  2. Dockerfile一式をクローン $ git clone https://github.com/s2terminal/mysql-repl.git
    • mysql-slave.cnfの記述を下記のとおり修正
[mysqld]
server-id=2
log-bin
log_slave_updates

3.docker-compose up で起動

3. DB起動確認

以下コマンド実行してMySQLに入れたらOK

$ mysql -u root -h 127.0.0.1 -P 13306

$ mysql -u root -h 127.0.0.1 -P 23306

4. Master/Slave の設定

  1. Master側での作業
$ mysql -u root -h 127.0.0.1 -P 13306 -e 'FLUSH TABLES WITH READ LOCK'
$ mysqldump --all-databases -u root -h 127.0.0.1 -P 13306 --master-data --single-transaction --order-by-primary -r backup.sql
$ MASTER_LOG_FILE=`mysql -u root -h 127.0.0.1 -P 13306 -e 'SHOW MASTER STATUS\G' | grep File | awk '{ print $2 }'`
$ MASTER_LOG_POSITION=`mysql -u root -h 127.0.0.1 -P 13306 -e 'SHOW MASTER STATUS\G' | grep Position | awk '{ print $2 }'`
$ mysql -u root -h 127.0.0.1 -P 13306 -e 'UNLOCK TABLES'

2.Slave側でレプリケーションスタート

$ mysql -u root -h 127.0.0.1 -P 23306 -e 'STOP SLAVE'
$ mysql -u root -h 127.0.0.1 -P 23306 -e 'SOURCE backup.sql'
$ mysql -u root -h 127.0.0.1 -P 23306 -e "CHANGE MASTER TO MASTER_HOST='db-master', MASTER_PORT=3306, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='${MASTER_LOG_FILE}', MASTER_LOG_POS=${MASTER_LOG_POSITION};"
$ mysql -u root -h 127.0.0.1 -P 23306 -e 'START SLAVE'
$ mysql -u root -h 127.0.0.1 -P 23306 -e 'SHOW SLAVE STATUS\G'
$ mysql -u root -h 127.0.0.1 -P 23306 -e "SET GLOBAL binlog_format = 'ROW'"

これで Master/Slaveの設定は完了です。 実際にMasterにDBを作成して確認してみます。

【Master DBで実行するSQL例】
CREATE DATABASE test_db;
USE test_db;

CREATE TABLE books(
  id   INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20),
  INDEX(id)
);

INSERT into test_db.books (name) VALUES ('Spookies News');
INSERT into test_db.books (name) VALUES ('Story of Spookies');

上記のクエリで適当なDBとテーブル、テストデータをMasterに作成します。

【Master DBで実行】
mysql> select * from books;
+----+-------------------+
| id | name              |
+----+-------------------+
|  1 | Spookies News     |
|  2 | Story of Spookies |
+----+-------------------+
2 rows in set (0.00 sec)

ちゃんとMasterにデータが入っていますね。

次にSlaveを確認します。

【Slave DBで実行】
mysql> select * from books;
+----+-------------------+
| id | name              |
+----+-------------------+
|  1 | Spookies News     |
|  2 | Story of Spookies |
+----+-------------------+
2 rows in set (0.00 sec)

MasterのデータがSlaveにレプリケーションされていますね!

4. 準備は整った。いざ、gh-ost始動!

今回は

Slaveのbooksテーブルに新しいカラムを追加してみます。

gh-ostの動作モードは3つあります。→ 参照

今回は、上記参照のa. Connect to replicac. test-on-replica(テスト)のモードで動かします。

まずは、テストモードで実行します。

テストモードでは、ゴーストテーブルが作成されるだけで、

MasterおよびSlaveの内容が変更されることはありません。

4.1. テストモードで動かしてみる

--test-on-replica がテストモードであることを示します。

$ gh-ost --user="root" --password="" --host="127.0.0.1" --port="23306" --database="test_db" \
  --table="books" \
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" \
  --test-on-replica \
  --gcp \
  --assume-master-host="127.0.0.1:13306" \
  --execute

上記コマンドを実行すると、

_books_ghoテーブルが作られています。

中身を確認すると

【Slaveで実行】
mysql> select * from _books_gho;
+----+-------------------+-------+------------+
| id | name              | price | created_at |
+----+-------------------+-------+------------+
|  1 | Spookies News     |   100 | NULL       |
|  2 | Story of Spookies |   100 | NULL       |
+----+-------------------+-------+------------+
2 rows in set (0.00 sec)

確かに、pricecreated_atカラムが追加されています!

では、次は実際にSlaveに反映→Masterに反映というプロセスをやっていきます。

さきほどお作成したゴーストテーブルは削除しておきましょう。

mysql> DROP TABLE _books_gho;
Query OK, 0 rows affected (0.00 sec)

4.2. 「a. Connect to replica」モードで動かしてみる

1. Migration実行後に自動でMasterに反映するバージョン

gh-ost --user="root" --password="" --host="127.0.0.1" --port="23306" --database="test_db" \
  --table="books" \
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" \
  --gcp \
  --assume-master-host="127.0.0.1:13306" \
  --execute

上記コマンドを実行後にMaster DBを確認すると…

【Masterで実行】
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| _books_del        |
| books             |
+-------------------+
2 rows in set (0.00 sec)

mysql> select * from books;
+----+-------------------+-------+------------+
| id | name              | price | created_at |
+----+-------------------+-------+------------+
|  1 | Spookies News     |   100 | NULL       |
|  2 | Story of Spookies |   100 | NULL       |
+----+-------------------+-------+------------+
2 rows in set (0.00 sec)

できてますね!

先述しておりますが、

gh-ostでは、Masterテーブルの複製(ゴーストテーブル)を作成し、

そのゴーストテーブルに対してMigrationを行います。

そして、Migrationが完了次第、

本番稼働してるテーブルと名称を入れ替える形で切り替えを行います。

したがって、_books_delテーブルは置き換える前に本番で動いていたテーブルです。


2. Migration実行後に手動でMasterに反映するバージョン

Masterに先程作成されたテーブルは消しておきましょう。

【Master DBで実行】
mysql> drop tables _books_del;
Query OK, 0 rows affected (0.01 sec)

まずは、手動切り替える用のフラグファイルを作成します。

$ touch /tmp/ghost.postpone.flag

(↑gh-ostコマンドを実行するPC上での実行)

それから、

--postpone-cut-over-flag-fileオプションにより

切り替え用フラグファイルを指定。

$ gh-ost --user="root" --password="" --host="127.0.0.1" --port="23306" --database="test_db" \
  --table="books" \
  --alter="ADD COLUMN updated_at DATETIME" \
  --gcp \
  --assume-master-host="127.0.0.1:13306" \
  --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag \
  --execute

上記コマンドを実行すると、

2018/12/18 12:09:19 binlogsyncer.go:79: [info] create BinlogSyncer with config {99999 mysql 127.0.0.1 23306 root   false false <nil>}
2018/12/18 12:09:19 binlogsyncer.go:246: [info] begin to sync binlog from position (9ef9788f80f5-bin.000004, 3333278)
2018/12/18 12:09:19 binlogsyncer.go:139: [info] register slave for master server 127.0.0.1:23306
2018/12/18 12:09:19 binlogsyncer.go:573: [info] rotate to (9ef9788f80f5-bin.000004, 3333278)

このようなログが出力され続けると思います。これでOKです!

Migrationが終わり、いつでも切り替えられる状況です。

この状況のままで Master を確認します。

【Masterで実行】
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| _books_ghc        |
| _books_gho        |
| books             |
+-------------------+
3 rows in set (0.01 sec)

mysql> describe books;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(20) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> describe _books_gho;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | int(11)     | NO   | PRI | NULL    | auto_increment |
| name       | varchar(20) | YES  |     | NULL    |                |
| price      | int(11)     | YES  |     | 100     |                |
| created_at | datetime    | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

確かに、booksテーブルは変化なしですが、

_books_ghoテーブル(ゴーストテーブル)にはカラムが追加されている!

では、実際に本番DBに反映します。


本番DBに反映するための切り替え作業はフラグファイルを消してあげるだけです。

$ mv /tmp/ghost.postpone.flag /tmp/ghost.postpone.flag.unpostpone

(↑gh-ostコマンドを実行するPC上での実行)

実行すると、

ログ出力が止まります。

では、Master DBを見てみましょう。

【Masterで実行】
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| _books_ghc        |
| _books_gho        |
| books             |
+-------------------+
3 rows in set (0.00 sec)

mysql> select * from books;
+----+-------------------+-------+------------+------------+
| id | name              | price | created_at | updated_at |
+----+-------------------+-------+------------+------------+
|  1 | Spookies News     |   100 | NULL       | NULL       |
|  2 | Story of Spookies |   100 | NULL       | NULL       |
+----+-------------------+-------+------------+------------+
2 rows in set (0.00 sec)

反映できてますね!

すごい!


ちなみに、

手動切替を待つ状態で

Masterのデータに変更があった場合を試してみましょう。

切り替え用フラグをまた作成し、

$ touch /tmp/ghost.postpone.flag

下記コマンド実行

$ gh-ost --user="root" --password="" --host="127.0.0.1" --port="23306" --database="test_db" \
  --table="books" \
  --alter="ADD COLUMN favorite B DEFAULT 0, ADD COLUMN created_at DATETIME" \
  --gcp \
  --assume-master-host="127.0.0.1:13306" \
  --execute

また、ログが出力され続ける状態になったと思います。

では、この状態でMaster DBの方にデータを追加してみましょう。

【Masterで実行】
mysql> insert into books (name, price) values ('Spo Paper', 200);
Query OK, 1 row affected (0.01 sec)

mysql> select * from books;                                                              
+----+-------------------+-------+------------+------------+
| id | name              | price | created_at | updated_at |
+----+-------------------+-------+------------+------------+
|  1 | Spookies News     |   100 | NULL       | NULL       |
|  2 | Story of Spookies |   100 | NULL       | NULL       |
|  3 | Spo Paper         |   200 | NULL       | NULL       |
+----+-------------------+-------+------------+------------+
3 rows in set (0.00 sec)

この変更はもちろんSlaveに反映されています。

【Slaveで実行】
mysql> select * from books;
+----+-------------------+-------+------------+------------+
| id | name              | price | created_at | updated_at |
+----+-------------------+-------+------------+------------+
|  1 | Spookies News     |   100 | NULL       | NULL       |
|  2 | Story of Spookies |   100 | NULL       | NULL       |
|  3 | Spo Paper         |   200 | NULL       | NULL       |
+----+-------------------+-------+------------+------------+
3 rows in set (0.00 sec)

問題はMasterに作成されいるゴーストテーブルにも反映されているのか否か…。

では、手動切替を行った後にMasterを確認した結果がこれです↓

【Masterで実行】
mysql> select * from books;
+----+-------------------+-------+------------+------------+----------+
| id | name              | price | created_at | updated_at | favorite |
+----+-------------------+-------+------------+------------+----------+
|  1 | Spookies News     |   100 | NULL       | NULL       |        0 |
|  2 | Story of Spookies |   100 | NULL       | NULL       |        0 |
|  3 | Spo Paper         |   200 | NULL       | NULL       |        0 |
+----+-------------------+-------+------------+------------+----------+
3 rows in set (0.00 sec)

反映されています!

こちらも先述したとおり、

gh-ostではSlaveのバイナリログを見て、

非同期でデータを同期しています。

したがって、常に本番で動作しているMaster DBとの同期が取れています

最後に

このようにオンラインマイグレーションツール gh-ostを使用することで

サービスを止めることなくマイグレーションが

安全(サービス的にも肉体的にも精神的にも!)に行えることができます!

Spookiesでも早速導入しました。

みなさんも機会があればぜひ使ってみてください👩‍💻👨‍💻

エイチームさん主催の勉強会に行ってきた

技術ブログの方では「はじめまして」になります、アルバイトの 本田 です!

急ではありますが、スプーキーズには勉強会参加支援制度があります。

勉強会参加支援制度とは、勉強会に参加する費用を会社から出してもらう代わりに、

学んできたことを社内にフィードバックする制度です!

今回は、僕が勉強会参加支援制度を使って参加してきたエイチームさん主催の勉強会について、

社内だけでなく、社外にもお伝えできれば!と思います。


↓ 以下本編↓

TL;DR

  • エイチームさんが実際のサービスで使った便利なミドルウェアやツールの紹介
  • MySQLオンラインマイグレーションツールgh-ostで深夜メンテ回避
  • RUNDECKでジョブを一元管理
  • VSCodeが熱い!(Emacs民に人権なし。悲しみ。)
  • MySQL界隈が熱い!
  • 俺はエンジニアだ!俺は速くしたい!(パフォーマンスチューニングの話)

勉強会情報

  • イベント名:ATEAM TECH 大阪「見せます!Webサービス開発を支えるミドルウェア/ツール」
  • 主催:エイチームさん(大阪支社)
  • 主題:Web開発において便利なミドルウェアやツールを紹介。主にサーバサイド
  • イベントリンク:https://ateam.connpass.com/event/104825/

キーノート おおざっぱまとめ

【発表01】MySQLオンラインマイグレーションツールgh-ostで深夜メンテナンスを無くした話

発表者:s2terminal さん

ある日、深夜メンテナンスが必要となった

  • エイチームさんのとあるサービス

    • DAU:数十万
    • Monthle Page View:億以上
  • そのサービスで使用しているDB

    • MySQL
      • 構成:master/slave
      • donwloads:600万件以上
      • データベースI/O:数十/秒(同社サービス内トップ?)
  • あるテーブルに新しくカラムを追加しないといけなくなった

    • 現在DBに格納されているレコード数は膨大
    • migrateに時間がめちゃくちゃかかる

↓でもでも

  • サービスを止めるわけにはいかない…(じゃあ、深夜メンテ?)
  • 深夜メンテは絶対にしたくない…
  • なにか良い方法はないか…

GitHubがいい感じのツールをOSSで開発してるよ

その名は 『gh-ost』

  • Github:https://github.com/github/gh-ost
  • 正式名称:GitHub's Online Schema Migrations for MySQL
  • オンラインマイグレーションツール
  • 本番で動いているテーブルのコピー(ゴーストテーブル)を作成し、そっちにmigrateかけて、できあがったら本番のものと入れ替える
  • 本番用のテーブル内のデータが更新されたときは、非同期でゴーストテーブルも更新する。
    • レプリケーションの作成って非同期ですよね?それと同じ仕組みを使うことで、gh-ostも非同期で、データ同期できます!
      • すなわち、binary logを見てる

gh-ostのいいところ

  • 実行状況をGUIで確認できる
  • migrateが完了したゴーストテーブルと本番テーブルの入れ替えは手動で行える
    • gh-ostにmigrate作業を深夜のうちにやっておいてもらい、担当者が出社してきたら、テーブルの入れ替えをコマンドひとつでぽちーっとやって、動作チェックできる!
    • もちろん全て自動で行うことも可能!

つまり、深夜メンテしなくてよくなった!

感想

エイチームさんが運用している大きなサービスで実際に使ったツールに関する話で、動かしてみてどうだったかというところも聞けたのでかなりおもしろい話だった。

自分も来年から大規模なサービスに関わるので、こういった知識は大変ためになる。

【発表02】サービスを支えるジョブ管理システム

発表者:kytiken さん

エイチームにおけるジョブ(バッチ処理)

  • アフィリエイトサービスにおける、クライアント企業へのレポートメールなどはジョブとして、定期的に自動で行っている。

ジョブ管理システム有名所

  • cron
    • 管理するジョブが増えてくると分かりづらい(ただのコマンドの列挙だし)
    • サーバが増えてくると、あのcronどこで管理してたっけ?ってなる
    • エラー時のログが分かりづらい

解決策 『RUNDECK』

  • RUNDECK
    • ジョブスケジューラ
    • 実行されるジョブ一覧を管理画面で確認できる
    • 実行状況をリアルタイムで確認できる
      • パーセンテージ表示
    • rundeckがssh接続でサーバにアクセスしジョブを実行する(エージェントレス)
    • cronと同じ形式で記述可能(cronからコピペするだけでいい)
    • webhook対応(slackへの通知も可能)
    • fluentdにログ保存可能
    • rundeckのGUIでジョブ登録可能(コマンドとかジョブ名、実行サーバ名とか入力するだけ)

rundeckで運用してみて

  • rundeck自体が落ちたら全ジョブが死ぬ(当たり前)
    • 対策:クラスタモード
  • 動作が重い
    • t2.smallで運用していたが、ジョブが増えるにつれて重くなった
    • 対策:JVMのチューニング
  • h2databaseがデフォルトDB
    • Java動作で負荷が高くなった(重い?)
    • 対策:外部DBに変更
  • ジョブの頻度が多い場合に負荷が高くなり始めた
    • ログが大量になったため負荷が高くなった
    • 対策:ログを定期的に削除
      • APIが存在するが、全削除しかなくて困る(対策要検討)

感想

こちらの話も実際にRUNDECKで運用してみてどうだったかという話まで聞けたので、参考になった。

ジョブは一元管理するか、ドキュメント作ったりしないと、すぐに迷子になってしまうものだと思っているので、こういうツールはいいと思いました。

ただ、デメリットも多い模様で、使うときは要検討!

【発表03】サービスで活用したRailsパフォーマンスチューニングツールの話

発表者:@sakupa さん(エイチームの方)

パフォーマンスチューニングをやることになった経緯

  • 開発優先でパフォーマンスチューニングできてなかった
  • ISUCONに出て刺激を受けた
    • 大会でやったことを業務に活かす!
  • なぜパフォーマンスチューニング?
    • 俺はエンジニアだ!俺は速くしたい!
      • ↑かっこよすぎる

パフォーマンスチューニングにおける優先度を決定

  • CV(コンバージョン)に繋がるページ
    • コンバージョン:Webサイト上で獲得できる最終的な成果
  • できるだけ小さい改修
  • あくまでリファクタリング

パフォーマンス監視サービスの導入

  • New Relic(ニューレリック)
    • ローカル段階から導入できる!
    • Railsならgemいれるだけで動く

発行クエリ数が多いぞ!?

  • New Relicにより発見
  • 続けてrack-mini-profilerで調査するとN+1問題が発生していることも見つけた
  • bulletでN+1問題の場所特定
  • チューニング!

パフォーマンスチューニングにおいて大事なこと

  • ボトルネックを細かく追うこと

感想

パフォーマンスチューニングをやるさいに、先輩からなぜやるのか聞かれたそうですが、「俺はエンジニアだ!俺は速くしたい!」と言い切ったそうです。 こういうエンジニアになりたいです。

プロファイラは僕も触ったことがありますが、導入が大変だったのを覚えています。 RailsならGem入れるだけでごにょごにょしてくれるらしい!(それがいいのか悪いのかは置いといて…)

LT大会 おおざっぱまとめ

【LT01】Effective Debugging React Apps in VSCode

発表者:むらじゅん(@murajun1978)さん

宣伝

どのエディタ使ってますか?

  • VSCodeが大半
  • Emacs民の人権なし

No more Print Debug

  • print debug は無駄が多い
  • VSCodeのデバッグ機能をどんどん活用していこうという話
    • ただし、Railsでは ruby-debug-idedebase gemが必要
      • 最新版はうまく動作しないから、Beta版使わないといけない
  • VSCode Recipes にデバッグのサンプルいっぱい
  • なにやらVSCodeのデバッグに関する設定ファイル?について話されていたが、そもそもVSCode使っていない僕にはなんのことか分からなかった…(ここが本題だったのに…)

感想

最近、VSCodeが熱い!って話をよく聞くけど、会場の8割型がVSCodeユーザだったのは焦った。

自分もちょっと試してみようかな!

【後日談】

家に帰って試してみました。 結論としては、以下の理由で即アンインストールしました。 - 自分の用途の範囲では、別にVSCodeだからできることがなかった - Emacsのキーマップにしましたが、デフォルトのキーマップとコンフリクトしまくり

感想:エディタひとつでなんでもやってしまいたい人にはいいのかも。でも、僕はIDEはIDE。エディタはエディタで分けたい人。… アンインストール!

【LT02】MySQL InnoDB Cluster

発表者:@masayuki14 さん(スプーキーズのアンバサダーです!)

発表資料:https://speakerdeck.com/masayuki14/mysql-innodb-cluster-woshi-tuteyun-yong-woshou-ba-kisiyou

MySQL InnoDB Cluster がすごい

  • 3つのコンポーネントを使って作る高可用性構成
  • セットアップが簡単
    • MySQL Shell で簡単にセットアップ
  • 自動フェイルオーバー
  • マスターの自動昇格
  • M/Sの接続を自動で切り替える
  • スケールアップが容易

感想

他の参加者の方がMySQL Routerが単一障害点になるからどうなんだろう…って議論されていて、それを聞くのも、またおもしろかったです! (確かにMySQL Routerが死ぬと終わりですね…)

↓ 社内勉強会にて @masayuki14 さんからもらった補足コメント

MySQL Routerは、Appサーバーに同梱して動作させる使い方がいいのだけど、 昨今のコンテナを使ってやる場合、AppコンテナとMySQLRouterコンテナに分離すると MySQLRouterが単一障害点になるんじゃないか、っていうはなし。

MySQLRouterコンテナも一つじゃなくて複数建てれば分散できる。 MySQLRouterの死活をチェックして別の接続先に切り替える、 みたいな冗長化するのはあんまり意味ないんじゃないか、というところ。


全体を通じての感想

f:id:spookies_honda:20181206201659p:plain:w350
お酒!ピザ!お寿司!

Meet Up 形式のイベントだったため、 勉強会スタート前からお酒飲んだり、ご飯を食べたり、かなりアットホームな雰囲気で始まりました。

エイチームさんが実際に使っているツールを知ることができ、加えて、実運用においてどうだったかというところまで教えてもらえたので、かなり有意義な時間でした。

次は来年の1, 2月ごろ開催だそうです。 また参加したい!

はんなりPython#3 で発表してきました

アンバサダーの id:masayuki14です。はんなりPython#3という勉強会で発表してきました。 この勉強会は「はんなりPythonの会」という京都のpythonコミュニティが主催していて、私も運営で参加しています。

今回は「JupyterNotebook入門」というタイトルで発表を行いました。 JupyteNotebookはPythonでのデータサイエンスには必須のツールなので、セットアップ方法や簡単な使い方を紹介しました。

speakerdeck.com

スライドなどの資料はこちらでも公開しています。 例として福井県の積雪量をグラフにしたんですが、今年以上に昭和の豪雪がすごかったことがわかりました。

f:id:masayuki14:20180226110359p:plain

さて、次回は 3/16 にはんなりPython #4 LT大会 with Beerが開催されます。 我らが専務 id:spookies-nishimura 氏も参加しLTを行います。 お近くの方はぜひ参加して下さい。

connpass.com

3Dモデルを作りたい2

こんにちはntykです。

f:id:spookies_nytk:20170714100033p:plain

以前は↑の画鋲モデルを作成しましたが、今回はこれより凝ったやつを作りたいと思います。

前回についてはこちら

labs.spookies.co.jp

今回もMetasequoia LEで作成しています。

今回はタイヤを作りましょう!

f:id:spookies_nytk:20171109121254p:plain

まず断面を作ります。 前回書いた回転体を使えばタイヤっぽくなりそうです。

でも、下半分がないように見えますね。

下半分はミラーリングで作っちゃいます。 ミラーリングとは画像を反転させてくっつけるやつですね。

f:id:spookies_nytk:20171109122112p:plain

Y軸に対して対称になるように設定します。 ついでに回転体も使っています。

f:id:spookies_nytk:20171109122323p:plain

なんだかタイヤっぽく見えませんか

f:id:spookies_nytk:20171115184148p:plain

今回はフリーズという機能を使って回転体の情報から頂点を作成します

f:id:spookies_nytk:20171115184235p:plain

ミラーも回転体も使ったので両方チェックを入れますね。(使ってない時はそもそもチェックできないです)

f:id:spookies_nytk:20171115184112p:plain

このように頂点を作成します。

f:id:spookies_nytk:20171109122639p:plain

さらに黒っぽい材質を設定して

f:id:spookies_nytk:20171109122717p:plain

タイヤっぽくします。 このままではゴムだけで味気ないですね。

f:id:spookies_nytk:20171109122852p:plain

真ん中に置く板っぽいやつを作りましょう。

枠の部分は回転体で、十字部分はミラーリングで簡単に作れます。

f:id:spookies_nytk:20171109123112p:plain

板をはっつけたらだいぶタイヤっぽくなりました。 次回以降もパーツを作って最終的に車のモデルを作ろうと思います。

Are you Spookies?? 機械学習で顔の画像認識をしよう

最近、アニメを見まくっているIoT開発部の西村です。
こんにちは。
感動シーンでナミダしている僕の横で、犬がうるせーなって顔してるのがたまらないです。
どうぞよろしくお願い致します。

課題・モチベーション

ということで 人の顔を機械学習で覚えさせ、メンバーが来たかもよ君にバージョンアップだ!

Spookiesメンバーの顔画像認識パターンモデルを作ってみる

顔画像の収集

毎日顔を合わせて一緒に仕事してますが、いざメンバーの顔写真が必要となっても、あまり持っていないもんです。 携帯で何枚か撮ったかもしれませんが、そんなに数は無いですよね。

合宿等の写真を拾い集める

会社の共有フォルダーに、合宿の写真が入ってました。

  • 沖縄合宿
  • 和歌山合宿
  • 淡路島合宿

こちらを使いましょう。

DLしてフォルダを開くと、こんな写真がいっぱいあります。

f:id:spookies-nishimura:20171014200226p:plain

顔画像のみ抽出

さて、ここから顔画像を取得しましょう。
一つのフォルダの中に全ての画像をぶち込んで、OpenCVを使って画像を切り出しましょう。

OpenCVには、画像から顔を抽出するCascade Classifier=分類器と呼ばれるものが公開されてるので、それを使って顔だけを切り出します。

さらにデータが少ないので、反転・コントラスト調整を行い、画像の水増しを行います。

寺師君の結果
f:id:spookies-nishimura:20171014200300p:plain

ではこれらのデータを使って、モデルを作ります。

モデルの作成

機械学習ライブラリといえば、TensorFlow
但し、素人にはなかなか扱いづらい為、日本語ドキュメントが完備されているラッパーライブラリの Keras を使いました。

from keras.models import Sequential
from keras.layers import Dense, Activation

# 画像データ読込(txtファイルは画像パスとラベルを設定した一覧)
(X_train, y_train)= train_input.read_data('./face_detect/train.txt') # X_train: 学習データ, y_train: 学習ラベル
(X_test, y_test)= train_input.read_data('./face_detect/test.txt') # X_test: テストデータ, y_test: テストラベル

### モデル定義 ###
# Sequentialはただ層を積み上げるだけの単純なモデル
model = Sequential()

model.add(Conv2D(32, (3, 3), padding='same', input_shape=X_train.shape[1:])) # 2次元畳み込み層
model.add(Activation('relu'))   # 活性化関数(レイヤをつなぐもの)
model.add(Conv2D(32, (3, 3)))
model.add(BatchNormalization()) # 誤差・分類性能向上の層
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))   # レイヤ縮小を行い、扱いやすくする層
model.add(Dropout(0.25))    # 過学習の防止

model.add(Flatten())    # 入力を平滑化
model.add(Dense(512)) # 全結合(クラス分類)
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(nb_classes)) 
model.add(Activation('softmax')) # 活性化関数

# compile
model.compile(loss='categorical_crossentropy',  # 損失関数
              optimizer='adam', # 最適化関数
              metrics=['accuracy']) # 評価指標のリスト

### 学習させる ###
model.fit(X_train, Y_train,
          batch_size=batch_size,
          nb_epoch=nb_epoch,
          validation_data=(X_test, Y_test),
          shuffle=True,
          callbacks=[csv_logger, cp_cb, stopping])

...

これでモデル作成です。(MacBookPro で 2時間くらいかかりました)

実験

では、早速できたモデルを使って画像認識してみました。

f:id:spookies-nishimura:20171014203702p:plain:w300 (パジャマですみません)

oops!!!! takanoになってる!

京都オフィスでも何名か試してもらいましたが、だいたい takano になってしまいました orz

原因

今回、精度が出なかった原因としては

  • 元画像の質が悪すぎる
  • 学習データ量が圧倒的に少ない

ですかね。

まとめ

残念ながら現在は takano が来たかもよ君になってしまいましたが、新しい事をするのは凄く楽しいですね。
引き続き精度を向上を図り、ラズパイにも組み込みたいと考えております。 f:id:spookies-nishimura:20171014200226p:plain

イエェェェイ!

f:id:spookies-nishimura:20171014193126j:plain:w100

labs.spookies.co.jp

プログラミングをやってみよう

プログラミングに興味はあるけど何やったらいいの?

こんにちは、ももです。

スプーキーズで働きはじめて1ヶ月。

ちょっとプログラミングの話もしてみたいなと思っておりました。

ただ、スーパープログラマではないひよっこですので技術的なことはまだまだです。

そこで今回は、プログラミング初心者である私がどんなサービスを利用してきたのかご紹介したいと思います。

私は、基本的にはWEBで提供されているものを利用していました。

プログラミングをやってみたいけど...

何したらいいかわからない

どんなことするの?

状態の方はまずは無料のWEBプログラミング学習サービスを利用してみましょう。

有名なものは

・ドットインストール

https://dotinstall.com/

・Progate

prog-8.com

・CODEPREP

codeprep.jp

でしょうか

私はこの3つをまんべんなく利用しています。

こちらは基本無料で、興味が湧いてきたら、月額の有料会員に切り替えが可能になります。

私は、ドットインストールとCODEPREPは無料会員ですが、Progateは有料会員です。

3つもあるけどどれがいいの?と思われるかもしれませんが

オススメはProgateとCODEPREPです。

ProgateかCODEPREPから始めると、プログラミングに必要な環境が整った状態ですので

簡単にプログラミングを体験することができます。初心者にはわかりやすいと思います。

私はとてもわかりやすかったです。

無料の部分でまずはプログラミングってどんな感じか試してみましょう。

触ってみると自分がプログラミングが好きか嫌いかもわかるのではないかと思います。

次に、ドットインストールですが、こちらは一回3分の動画をみる形の学習スタイルになります。

私がドットインストールに大変お世話になったのは、開発環境の構築です。

ドットインストールのいいところって隣でプログラミングを教えてくれてる感じがあるんですよね。

実際に画面上のどの部分を選択するのだとか、動作がとてもわかりやすいです。

私は、いろんなものを試したかったのでまんべんなくスタイルですが

一つに絞ってやるのも、もちろん人それぞれだと思います。

プログラミングに慣れてくるときっとWEBサービスだけでは、物足りなくなってくると思います。

そんな時がくれば、興味のある言語を選んで参考書を買ってみましょう!

もっと楽しい世界が広がると思いますよ!!私もそうでした

ちなみにスプーキーズでは、会社の参考書の貸し出しも行っていて自由に借りて、勉強できる制度も整っています。

プログラミングに興味があって勉強する意欲のある方!ぜひ、一緒にスプーキーズで働いてみませんか?

スプーキーズでは、一緒に働く仲間を募集しています!

f:id:spookies_momo:20170928191623j:plain

JavaScriptを高速化する5つの手法

こんにちは、モリタです。

JSを高速化 する

f:id:moritanian:20170926151527p:plain:w160

nativeに比べて遅いと言われるjsですがパフォーマンスチューニングによりずいぶん改善しているようです。しかしながらWebGLなどまだまだ高速化が必要な分野も数多くあります。そこで、今回はjavascriptを高速化する手法を簡単に見ていきたいと思います。

Typed Array

動的な配列であるjsの通常の配列に対し、固定長の配列であるTypedArrayが用意されています。低機能ですが、各要素に高速でアクセスする事ができます。以下のようにまずサイズを決めてバッファを確保してからTypedArrayを生成します。 canvasの getImageData () で取得できる画像データもTypedArrayのようですね。

var buffer = new ArrayBuffer(8); // 8byteのサイズのバッファ確保
var arr   = new Int32Array(buffer); // 32bit(4byte)のint配列
console.log(arr); // [0, 0]

WebAssembly

動的型付け言語であるJsは実行時に型チェックや型変換が行われ、それがネイティブに比べて遅くなる原因のひとつになっていました。そこでMozillaからasm.jsという手法が提案されました。asm.jsでは全ての変数が静的な型付けを行います。TypedArrayの考え方をさらに推し進めたものと言えるでしょう。

このasm.jsに対して各ブラウザベンダーが協力して策定しているバイナリフォーマットの規格がwebAssemblyです。 webAssemblyを利用するにはc, c++ コードをEmscriptenツールによって.wasmバイナリに変換するのが一般的なようです。  各ブラウザの実装状況は以下のようにデスクトップではほぼすべてのブラウザ最新で実装されています。 f:id:moritanian:20170926114309p:plain

また、unityのwebGLビルドでもオプション設定でwebAssemblyを使用できるようです。

WebWorker

jsは本来シングルスレッドで動作しますが、webWorkerを利用することでマルチスレッド動作を実現できます。 以下のように new Worker("ファイル名") で実行することができます。 また、postMessage() , onmessage() でスレッド間でデータをやり取りできます。

main.js

var myWorker = new Worker("worker.js"); // worker.js  を別スレッドで実行
 myWorker.postMessage([first.value,second.value]); // Sending message as an array to the worker
myWorker.onmessage = function(e) {
        result.textContent = e.data;
        console.log('Message received from worker');
    };

worker.js

onmessage = function(e) {
  console.log('Message received from main script');
  var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
  console.log('Posting message back to main script');
  postMessage(workerResult);
}

参考

github.com

gpgpu

gpgpuとは本来画像処理に使われるgpuの計算資源を画像処理以外の演算に使うことです。gpuは行列計算など計算密度が大き(条件分岐などが少ない)く、並列度が大きい演算に関してCPUに比べて性能を発揮します。最近は機械学習が利用例としてホットですね。 gpgpuを利用するために以下のライブラリがあります。 gpu.rocks

github.com

gpu.jsでは以下のように記述できます。

const gpu = new GPU();
const multiplyMatrix = gpu.createKernel(function(a, b) {
  var sum = 0;
  for (var i = 0; i < 512; i++) {
    sum += a[this.thread.y][i] * b[i][this.thread.x];
  }
  return sum;
}).setOutput([512, 512]);

const c = multiplyMatrix(a, b);

Simd

こちらも並列化により高速化を実現する試みの一種で命令セットレベルの並列化を実現します。 以下ではベクトル演算を並列化しています。

var a = SIMD.Float32x4(1, 2, 3, 4);
var b = SIMD.Float32x4(5, 6, 7, 8);
var c = SIMD.Float32x4.add(a,b); // Float32x4[6,8,10,12]

残念ながらEcmascriptでまだドラフト段階のためリリース版ブラウザの実装はまだのようです。

番外編

javasciptで演算子オーバーロードしたい

高速化とは全く関係ありませんがみなさんも一度はjsでも演算子オーバーロードしたいと思ったことがあるはずです。いや、そんなことはないですね。 少し気になったので調べてみたのですが結論としては、 【Javascript】演算子をオーバーロードしたい話 - Qiita にある通り普通のやり方では実現できずコードを構文解析して演算子オーバーロードしている箇所を書き換える必要があるようです。 そこで実際に実装してライブラリ化してみました!

GitHub - moritanian/minamike.js: このライブラリに過度な期待はしないでください

f:id:moritanian:20170926124329p:plain

このライブラリでCのstd::cout 風に記述するとこのようになります

const cout = "std::cout";
String.prototype.__operators__ = {
    "<<" : function(s1, s2){
        console.log(s2);
    }
  };
 
 
 cout << "Hello World!";

まあ、全く需要なさそうですが。

それではよきjsライフを!