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

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

Cloud Native Kansai #1 に参加しました

f:id:masayuki14:20190201190107j:plain

こんにちは。アンバサダーの id:masayuki14 です。先日CloudNativeKansai#1に参加してきました。

cnjp.connpass.com

CloudNativeJPコミュニティにより主催されていて、全国でミートアップイベントが開催されています。イベント名の通り関西では今回がはじめての開催でしたが、2時間半の間に7つの発表が詰め込まれている内容の濃いイベントでした。

全体的にKubanetesを取り巻く周辺の環境を紹介する内容が多く AWS, GCP, Azure といったマネージドサービスの利用環境が充実してきたんだなという印象でした。

Talk1 Cloud Native Journey with Kubernetes

1つ目の発表で印象的だったのは、CloudNativeComputingFoundationからKubernetes利用までのロードマップが示されているという部分。 現状はいくつかのDockerコンテナを用途に合わせて使っている程度なのでstep1くらいにいるけど、徐々にk8sを使うところまで持っていきたい。 経験者が語るように、はじめからマネージドサービスを使ってみようと思います。

Talk2 Amazon EKS Quick Dive

残念ながらKubernetesをまださわったことがないので、内容があまり入ってこなかったんですが、EKSを使うにあたりチュートリアルが用意されているとのことなので、その時が来たらお世話になりたい所存です。

Amazon EKS Workshop :: Amazon EKS Workshop

Talk3 AKS をいちばん楽に始める方法

CI/CDにはAzureで紹介されたDevOpsを使ってみようと思いました。個人利用だと無料だし。 GitHubがMicrosoftに買収されたので今後このあたりが連携しやすくなることに期待。 全力で思考停止していきたい。

Talk4 地方におけるCloud Native

情報のキャッチアップが大変というのは業界の常ですね。いろいろな情報に触れられることで、できそうなことの多さとできることの少なさのギャップに、うまく折り合いをつけていかないといけませんね。

個人的ピークはカブトムシのMRIでした。貴重な映像だった。

Talk5 GitOpsでKubernetesのYAML管理

GitOpsというワードは初めて知りました。いろいろあるんですね。 そしてここでもマネージドサービスを使ったほうがいいという知見を得ました。kubectl コマンド使うもんじゃないみたい。

Talk6 Kubernetesで機械学習基盤(GPU)を構築している話

マイクロサービスには気をつけようと思います。

発表中にcomets 発表スライド上にコメントが流せるサービスというサービスを使ってスライドにコメントが飛び交ってたので全然集中できないっていう。

Talk7 Get started on Cloud Native with Rancher !!

Rancherというプロダクトの紹介で初めて知りました。とても便利そう。

全体の感想

いろいろなサービスや技術や背景の思想がありますが、新しいものを使えばそれでいいというわけでもなく、目の前にあるチームや組織や文化やエンジニアのスキルなどの環境にあわせていかなあかんなぁと思いました。

個人的に今年の目標として Docker/Kubernetes の利用を掲げているので、1年かけて取り組んでいきたいと思っています。ちょうど個人的に作りはじめたプロダクトがあるのでこれと絡めて使っていけるとサイコーな感じです。

イベントのクロージングで第二回のイベントが公開されました。興味がある方はおはやめにどうぞ。

cnjp.connpass.com

その他のまとめなど

togetterまとめが作成されています。

togetter.com

chocopurin.hatenablog.com

他にも紹介しているブログを見つけたらリンクを追加しようと思います。

3D空間で「野球っぽい挙動」を表現するために学んだこと

はじめまして!アルバイトのkouhiraです!

いきなりですが、僕は三度の飯よりも野球が好きだと自負しています。三度の飯もかなり好きな方なので、野球に対するはそんじゃそこらの人には負けない自信があります。

そしてもちろん、プログラミングも大好きです。こちらはまだまだ勉強することの多い身ですが、毎日楽しくスプーキーズでもコードを書いています。

野球オタクがプログラミングという趣味を持った場合、当然「自分で野球ゲームを表現してみたい!」という目標を持ちますよね?(持ちませんか?)

僕もご多分に漏れずそんな目標を抱いていて、その足がかりとして、「Minecraft」というゲーム内で野球をするプラグイン(Steamゲーにありがちな「MOD」の亜種みたいなものだと思ってください)を趣味で作成しています。

そして先日、社内勉強会で発表する機会があり、僕はそのプラグイン作成時に学んだ知識について発表しました。(スプーキーズの社内勉強会は業務内容と微塵も関係なくても温かく聞いてもらえます!)今回は、その時に発表させていただいた内容を紹介させていただこうと思います!

前提としている環境はかなり特殊ですが、環境に依存しない内容に絞って紹介するつもりですので、もし役に立ちそうな知識があれば、スポーツゲーム作成などの際の参考にしていただければ嬉しいです!

※物理の知識について書いた箇所がありますが、書いている僕は高校物理すら習っていない根っからの文系です…なにか誤りなどありましたら申し訳ありません…。

前提

  • SpigotというMinecraftの公式サーバーソフトウェア上が動作環境。
  • MinecraftのJava Editionで動作するものなので、開発言語はJava。
  • イベント駆動(ゲーム中にこういうエンティティがこういう挙動を行ったとき、といったものに対応してリスナーを書いていく形式)で概ね書く。
  • イベントとは無関係に定期的に実行したい動作はRunnableというものを定義し、実行間隔などを指定して呼び出す形式。
  • 基本的なプレイヤーの動きはMinecraftにデフォルトで搭載されているものが利用でき、野球の根幹を成す「投げる」という動作は雪玉というアイテムを利用している。
  • 物体の移動は「速度」として三次元ベクトルを付与する形。
    • その瞬間(0.05秒=1tick)の(xの移動量,yの移動量,zの移動量)というのがここでの「ベクトル」(yが上下方向)

投球

  • 野球における投球を満足できる程度に再現しようとすると、球速や軌道をある程度プレイヤーの思うように操れる必要がある
  • つまり、変化球を実装しなくてはならない
  • 投球を行った時に起動し、以降毎tick実行されるRunnableの中で投じられた雪玉の運動のベクトルを少しづつ操ってやるようにした
  • プレイヤーがどの球種を投げようとしているかを指定するのは、Minecraftにデフォルトで存在する「アイテムに名前を付ける」という機能を用い、「この名前ならば毎tickこれだけの変化」というのを定義しておき、それぞれのプレイヤーが投球前に名付けを行う形にしている
import org.bukkit.entity.Projectile;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

public class BallMovingTask extends BukkitRunnable {
    private Vector move;
    private Projectile ball;
    public BallMovingTask(Projectile ball, Vector move) {
        this.ball = ball;
        this.move = move;
    }
    @Override
    public void run() {
        // ボールの速度を取得し
        Vector velocity = ball.getVelocity();
        // 変化のベクトルを加えて
        velocity.add(move);
        // ボールの速度を上書き
        ball.setVelocity(velocity);
    }

}

問題

  • このままだと、「真上方向にストレート(上方向に変化するボールとして定義したとする)を投げた時」などに(特に)挙動がおかしくなる
  • 上に上に変化し続けて、ただなかなか落ちてこないというだけのボールになってしまう
  • 投球だけなら真上方向に投げるのがレアケースなのでそこまで問題にならないが、現実の野球でフライが伸びたり切れたりするのを考えると、この「発射後の軌道の変化」は打球にも適用したい
  • となると、真上のフライはそこそこありうる打球なのでよくない

    解決策

  • そもそも期待する挙動はなにか?
  • バックスピンで上方向に上がる打球というと現実でいうとキャッチャーフライ
  • http:// https://youtu.be/sdRoiiI8KeQ?t=5

  • 上記動画を見ると、必ずキャッチャーはキャッチャーフライ捕球時に後ろ(バックネット方向)を見て捕球している

  • 体の前に飛ぶ打球なのにわざわざ後ろを向いて捕球するのは、キャッチャーフライが後ろに向かって上がった後、前に戻ってきながら落ちてくる打球だから(キャッチャー経験者は小学生の時期に習います)
  • ゲーム内でもこの挙動を再現したい
  • つまり「ℓ(リットル)」の字のような軌道になってほしい

現実の物理

  • そもそも回転する球の軌道が変化するのは、「マグヌス効果」というものによるらしい
  • バックスピンなら、上部の空気がより早く後ろに流される→上の方の空気が薄くなってそちらに引っ張られるという原理(※自分のレベルでの理解)
  • 引っ張られる力は運動のベクトルと回転のベクトル表現の「外積」に比例する
  • 向きを回転軸(反時計回りになるように取る)、大きさを回転の大きさとして定義されるのが「回転のベクトル表現」
  • あるベクトルともう一つの他のベクトルに対し、そのそれぞれに垂直なベクトルが「外積
  • 同じ方向のベクトル同士の外積は零ベクトルになる
    • 進行方向と回転軸が一致する縦のスライダーと、回転数がそもそも少ないフォークボールはともに重力によって「落ちる」球になる
  • ちなみに変化量はその瞬間の球速や回転量に依存するらしい
  • だいたいボールの直径を73mm、大気の状態が標準状態として、1Tickあたり1/8 * π * π * 1.205 * 0.073^3 * 球速 * 一秒あたりの回転数くらい(参考にしたスライド)になる。
import org.bukkit.entity.Projectile;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

public class BallMovingTask extends BukkitRunnable {
    // 回転のベクトル表現(ややこしいので大きさは適当)
    private Vector spinVector;
    private Projectile ball;
    // 1秒あたり何回転するか
    private int rps;
    public BallMovingTask(Projectile ball, Vector spinVector, int rps) {
        this.ball = ball;
        this.spinVector = spinVector;
        this.rps = rps;
    }
    @Override
    public void run() {
        //バウンドしたり打たれてボールが死んだらタスクをキャンセルする
        if(ball.isDead()){
            this.cancel();
        }
        // ボールの運動
        Vector velocity = ball.getVelocity();
        if(spinVector.length() != 0){
            // 運動と回転の外積
            Vector actualMove = velocity.getCrossProduct(spinVector);
            if(actualMove.length() != 0){
                // 外積を一度長さ1のベクトルに直してから正しい大きさにする
                actualMove.normalize().multiply(1/8 * Math.PI * Math.PI * 1.205 * Math.pow(73/1000, 3) * velocity.length() * rps);
            }
            // 運動のベクトルに加える
            velocity.add(actualMove);
        }
        ball.setVelocity(velocity);
    }

}
  • 実際にやってみると、

javaw_20181212_000139F.gif (30.3 MB)

  • できた。

    打撃

  • 打撃を再現するためには、
    • 多様な打球が飛ぶ
    • タイミングが問われる
    • 振るコースや高さの調整が要求される
  • といった要素が必要だと考えられる
  • 一番簡単なのは、何らかの操作があった時に「この範囲ならバットに当たる」という箱を作って判定する形
  • 箱の表面で当たり判定を行うのではなく、箱の中にボールが入っているような位置関係だったら「当たった」ということにする
    • コースや高さは目線操作で設定
    • クリックのタイミングが問われる
  • バットに当たった後どういう打球になるかは、その箱の中心と当たった瞬間のボールとの位置関係から考えればいい
    • ボールの上を振るとゴロに、下を振るとフライに
    • 振り遅れると打球が後ろに飛ぶ
    • 概ね感覚に合うのでは?
  • 打球の強さはその二点の距離が大きいほど弱く、小さいほど強くなるようにする
    • 「芯」に当たればいい打球、外すと弱い打球
    • これにはn-距離を使うといい感じだった(1~0の変域で連続的な数値になるので)

問題

  • 「打球の回転」を定義しようとすると問題が発生する
    • 基本的にはバットとボールの位置関係が回転にも影響するのでそれを使いたい
    • 箱の中心からボールの位置までのベクトルをとり、それとボールの運動のベクトルとの外積をとれば逆に「その方向に切れていく回転」のベクトル表現が得られるはず
    • しかし今回の場合運動のベクトルとそれがまったく一致してしまい、結果が零ベクトルになる
  • 全く同じベクトルでさえなければ零にはならないため、
  • 「箱の中での位置関係」以外に打球の方向を左右するものが必要
  • 普通に考えれば「スイング軌道」(バット自体の動き)

    スイング軌道実装の困難

  • 「これが正しい」というのが存在しない
  • 選手ごとに全然違うのはもちろん、理論的にこれが正解というのもわからない
  • あれば球児は悩まない(愚痴)
  • 理論武装が出来ないので、「多くの人にとって納得できる」のをでっちあげる必要がある

    野球界の迷信を取り入れる

  • 正解が存在しないので、「それっぽく見える」やつを使いたい
  • 野球界には古くから「理想のスイング軌道」についての迷信がある
    • 「最短距離(直線)」
  • しかしこれはもはやほとんどの人が信じていない、古い考え方とされている(※僕の主観です
    • 「野球 最短距離」とかで検索しても否定的な記述が目立つ
  • ここ5年くらい(※主観)で多くの人に信じられるようになりつつある新たな迷信(※主観)がある
    • 「最速降下曲線」というもの
  • 「任意の2点間を結ぶ全ての曲線のうちで、曲線上に軌道を束縛された物体に対して重力 (に代表される保存力) のみが作用する仮定の下、物体が速度0でポテンシャルが高い方の点を出発してからもう一方の点に達するまでの所要時間がもっとも短いような曲線」らしい。
    • 要するに「ある地点からある地点まで何かを転がすときに一番早く転がる軌道」
    • https://www.youtube.com/watch?v=L2_d7MPeJks
    • こういう感じらしい
    • 「構えたところからインパクトまでがこの軌道だったら一番早いはず」という理論で紹介されていることが多い
    • お昼のワイドショーでも「大谷翔平のスイング軌道がこうだ」と取り上げられたらしい
  • 実際はスイング時は重力以外も関係する(というか人間が振り回す力が主)なので…?
  • が、「多くの人が納得してくれる」は達成できそう

    実装

  • 詳しい説明は理解できていないが、最速降下曲線の式は、Minecraftのワールド上で描こうとすると(x=θ-sinθ, y=1-cosθ)という感じになるらしい
  • プレイヤーの目のところを出発地点とし、θの値を0-πまで増加させて描いてみるとこんな感じ 2018-06-01_17.01.18.png (286.2 kB)
  • これをスイング時のバットの縦の動きと考える
  • そのまま横向きに回転させる
public static Location getBatPosition(Location eye, double roll , int rollDirection){
    // 目の位置
    Location eyeLoc = eye.clone();
    // 与えられた角度の分だけ横回転させる
    eyeLoc.setYaw(eyeLoc.getYaw() - (float)(90 * rollDirection) - Math.toDegrees(roll));
    // 回転させた方向にまっすぐ伸びるベクトルを得る
    Vector push = eyeLoc.getDirection().setY(0).normalize();
    double theta = Math.abs(roll * 2);
    // 最速降下曲線のx座標を求める式
    double x = push.normalize().getX() * (theta - Math.sin(theta));
    // 最速降下曲線のy座標を求める式
    double y = -(1 - Math.cos(theta));
    // 最速降下曲線のx座標を求める式
    double z = push.normalize().getZ() * (theta - Math.sin(theta));
    // 元の目の位置にそれぞれ得られた値を足した座標を得る
    return eye.clone().add(x,y,z);
}
  • こうなる 2018-06-01_18.12.51.png (184.7 kB)
  • この軌跡をどこか任意の点で微分して得られたベクトルを「バットのスイングによって与えられる運動」として加える
  • 自分は一番手っ取り早いので近くの2点を選んでその2点の差をとって使っている
  • 基本は一番低くなっているところとそのすぐ近くのもう一点
  • ※この軌跡をまるごとスイングとして使うのではなく、あくまで「バットに当たった瞬間のバットの動き」としてこの軌跡の一部分を用いる
  • 箱の中心からボールまでのベクトルと違うベクトルになるため、打球の回転のベクトルを得ることができるようになった

    副作用

  • 基本的には最速降下曲線の一番「底」の部分を使っているが、「任意の点」を選んでスイングのベクトルを得ることができるようになった
  • 一番底の部分を微分するとY方向の傾きは小さくなるはず
    • いわゆる「レベルスイング」=標準的なスイング軌道と考えることができる
  • 底以外の部分を使うことで打者の打球傾向をある程度変えられる
    • 底に至るより前のところを使えばゴロが多い「ダウンスイング」に
    • 後の方を使えば「アッパースイング」になる
    • パワプロで言う「弾道」のような要素として使える
    • ちなみに調整を行わずさっきの関数をそのまま用いると横回転の度合いも変わってくるため、「フライの打ちやすさと引っ張りの多さ」「ゴロの打ちやすさと逆方向への打球の多さ」が相関してしまうが、
    • それも統計上正しいようなのでよしとしている。

      参考資料

  • Spigot公式JavaDoc
  • 『野球の投手が投じる様々な変化球の特徴 ~移動速度,回転速度,回転軸の向きに着目して~』
  • ゴルフゲームでUnityの限界を突破する方法
  • Wikipedia「最速降下曲線」
  • 物理のかぎしっぽ「最速降下曲線」
  • BASEBALL GATE 「プルヒッティングのすすめ」

テクテクテック#7DB勉強会を行いました

f:id:masayuki14:20190117223704p:plain

アンバサダーの id:masayuki14 です。先日京都オフィスでテクテクテックを開催しました。

spookies.connpass.com

今回はDBをテーマにした勉強会で、3つの発表と1つのLTがありました。

  • 君はWindow関数を知っているか by @masayuki14
  • 超素人がDynamoDB触ってみた by Otazoman
  • MySQL Spiderエンジンでゲーム運用してみて by @ikuro_nishizuka
  • RDSのレプリカが便利過ぎて涙が出た話 by @21ma

君はWindow関数を知っているか

私は「君はWindow関数を知っているか」というタイトルで発表を行いました。スライドはこちらです。

ウィンドウ関数はMySQL8.0で新機能として実装されたことで、主要なRDBMSでは利用可能な状況となりました。 SQLの機能としては新しいほうで、モダンなSQLとして今後さらに普及しそうです。 スプーキーズでは主にMySQLを利用していますが、これまでの5系では利用する機会がなくウィンドウ関数を知らないスタッフが多い状況だったので、今回これをとりあげました。

ネタとしてはまだまだ磨いていけそうなので、今後も他の勉強会でも発表しようと思います。オファーがあればどこでも話にいきますよ!

超素人がDynamoDB触ってみた

Otazomanさんによる発表です。 AWSのLambdaとDynamoDBを使ったアプリケーションの紹介で、いわゆるサーバーレスでの構成でした。 ぜんぜん超素人じゃないじゃん、って感じです。

MySQL Spiderエンジンでゲーム運用してみて

弊社 @ikuro_nishizukaによる発表です。 MySQLのSpiderエンジンを運用で使ってみて得た知見などを発表しました。情報としてはあまり出回っていない分野のことかと思います。 10年ほど前ですが私も利用したことがありわかりみのある内容でした。個人的にはなかなかつらい思いをしましたが、今となってはいい思い出です。 どのような技術でもそうですが適正のある用途で使わないといけないですね。

RDSのレプリカが便利過ぎて涙が出た話

弊社@21maによるLTです。RDS便利ですよね。もう自前でDBサーバー立ててレプリケーション設定して、ってのがバカバカしくなってしまいますね。

懇親会

懇親会ではビールとピザを片手にAWSについての話題でいろいろと盛り上がりました。 AWSのサービスも多岐にわたりキャッチアップするのも大変ですが、今年はAWSをもっと積極的に使っていこうということでスプーキーズでは盛り上がっています。 まだまだ人数の少ない勉強会イベントですが親しみやすい雰囲気で行えているのと思います。

もくもく会のお知らせ

今年から毎週木曜日にもくもく会を開催する運びとなりました。 「興味ある技術さわれて単純に楽しい」という気持ちを大事にしたい、という思いから開催することにしました。 言語やテーマなどは特に決めておりませんので自由に作業していただけます。 どなた様も参加いただけますので、スタッフ一同お待ちしています。 だいたい @21ma がReactNativeで何か作っていると思います。

spookies.connpass.com

はんなりPython#12でLTをしてきました

発表の様子

アンバサダーの id:masayuki14 です。先日はんなりPython#12を開催しLTをしてきました。
これからはアンバサダーの活動報告的にコミュニティ活動の記事をPOSTしていこうと思います。

hannari-python.connpass.com

今回はPyConでの登壇経験もある吉岡さんに発表を依頼し、残った時間でLTをいくつか実施しました。

吉岡さんの発表は、Q-Traderという強化学習のライブラリを使って株式売買の判定を実験したものでした。 発表の途中から質疑応答もあり、また発表後もたくさんの質問が飛び交い盛り上がりました。 個人的にはこの手の分野はまだま勉強不足なため、理解できる部分はとても少なかったのですがとても興味深い内容でした。 発表の終盤に「Wasserstein gun」というキーワードをゲットし調べましたが、正直なところ全くわかりませんでした。

その後、2人の方にLTをしてもらったところで閉会の10分前。

私のLTは、運営としてのこの1年のふりかえりをまとめた内容だったのでねじ込んで発表させてもらいました。 駆け足でしたが「1年続けられてよかった!みんなありがとう!」というメッセージが伝わっていたらいいなと思います。 スライドはこちら。

(追記)その後ブログを書きました。
はんなりPythonを1年間続けてきて思うこと - 主夫ときどきプログラマ

また、同じく運営の id:mazarimono さんのLTはできなくなってしまったのですが、ブログに書いてくれています。 機会を見てLTとして改めて聞きたいなぁという内容なので、勉強会やコミュニティに関わっている方はぜひご覧ください。

www.mazarimono.net

今年も残すところあと僅か。来年も同じような活動報告をできるよう、次の1年も細く長く続けていきたいと思います。

【ハンズオンあり】オンラインマイグレーションツール 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