Quantcast
Channel: Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)
Viewing all 223 articles
Browse latest View live

スマホゲーム開発最前線を支える技術

$
0
0

2015/05/19(火)に、レバレジーズさんが主催しているヒカラボで、以下3つの発表をしてきました。

RoRAWSで100,000Req/Minを処理する

定常的に 100,000Req/Min くらいを処理しているインフラを構築した時に考えていたこと、失敗したことです。 特別なことをやっているわけではなく、スケールアウトを考え、負荷テストで事前に計測し、過負荷な状態をすぐに検知できるようにするといった内容ですが、それを楽に構築するためにやったことを共有しました。

Node.js実践非同期処理

Node.jsで非同期処理を実装するとCallback Hellになりがちですが、PromiseやGenerator(+co)を使って保守性・可読性高く実装する手法について、実装を交え詳しく紹介しました。

ネイティブアプリ開発でコケるポイント対策

6ab7a363cb0cb6372551208122644ff0

ネイティブゲームのクライアント開発では、要件の曖昧さ、複雑さ、開発文化(リリース出来たら成功の文化)といった面で保守性の悪いコードが生まれやすいものです。それを人に依存することなく、仕組みでどのように解決するかといった内容を共有しました。

懇親会含め、とても楽しい時間でした!参加頂いた方々、主催のレバレジーズさん、どうもありがとうございました!


アカツキのエンジニアの活動の紹介

$
0
0

はじめに

アカツキにて内定者インターンをしているsachaosです。 今年の6月から内定者インターンとして働き始めてからまだ2ヶ月弱のアカツキ初心者ですが、この記事では、アカツキの中に入って初めて知ったアカツキのエンジニアの活動の一部を紹介させて頂きたいと思います。

エンジニア達

エンジニアミーティング

エンジニア全員が集まるエンジニアミーティングがアカツキでは隔週で行われています。 アカツキではチーム・プロダクト単位で最適な方法を考えるという思想故、会社内の他のチームがどのような技術を使っているのか?ということが見えなくなってしまいがちです。エンジニアミーティングはこのチーム単位での蓄積した技術・方法論を共有する為に生まれました。

ここでは、「事前登録サイト構築時にElastic Beanstalk使ってみた」というような技術の共有や、「社員を人工知能系の国際学会に派遣してみる制度があったほうがいいのでは?」というような新たな制度の提案など様々なことが話し合われており、CTOからインターンまで隔てなく意見を出す事ができる場となっております。

勉強会

アカツキ流開発でいうと、最高の方法を考えないのは悪です。最高の方法を探索し、生産性を高める為、エンジニアというものは常に勉強する必要があります。勉強は一人だけですることももちろん重要なのですが、既に知見のある人から教わる事・自分が教える事で理解を深めようとする事も重要です。

学ぶ機会を増やす為、アカツキでは様々な勉強会を行っています。Rspecの勉強会、インフラの勉強会などに始まり、オライリーのアンダースタンディングコンピュテーションを輪読する会もあります。アンダースタンディングコンピュテーションの読書会は業務とは直接関係するわけではないのですが、計算機科学を学ぶ事でエンジニアとしての深さを身につけることができる良い機会となっています。

これらの勉強会の出席はもちろん自由で、自分の異なる領域の勉強会にも参加することができます。最近ではLT会も始まり、ランダムで選ばれた人が自由な話題でライトニングトークを行っています。このLT会も、先日開催されたLITALICOの合同勉強会でLTの良さに気づいたYoshihiroさんが「LT会をやる場がないならば作ろう」と、先ほど紹介したエンジニアミーティングにて提案された事で始まりました。

制度

アカツキでは外部のカンファレンスに出席する事を奨励しており、ゲーム系のカンファレンスはもちろん、RubyConやLinuxConの様なビジネスに直結しないカンファレンスに業務として参加する事ができます。もちろん、参加費や出張費は会社から支給されます。アカツキがカンファレンスの出席を奨励している理由は、別の会社ではどういう技術を使用しているのか、今どういう技術が熱いのか等、内部の勉強会だけでは不足してしまう部分を補う事ができると考えているからです。

また、アカツキでは技術本などを社費で自由に購入することができるのですが、それ以外にも最近、エンジニア文庫というものが出来ました。これは、アカツキのエンジニアがオススメする必読の良書が集められたもので、例えば、メジャーなところだと「リーダブルコード」「体系的に学ぶ 安全なWebアプリケーションの作り方」「Webを支える技術」「ハッカーと画家」等が用意されています。数多くある技術書の中のどの本で勉強すれば良いのか迷ってしまう僕のような新人には有り難いものです。

文化

アカツキのエンジニアは共有すること・世に出す事を良しとするハッカー文化を大事にしており、会社でのオープンソース活動の支援の流れもできつつあります。プロダクトを作るだけではなく、オープンソースのソフトウェアを生み出す新しいチームを作ろうというような話が出ていたり、業務時間の2割をオープンソース活動の為に当てる事が出来るようにしようと言うような話が出ていたりします。

また、社内ではQiita Teamを利用して、社員間の技術の共有を盛んに行っております。例えば、「SQLアンチパターン」「マルチプラットフォームのためのOpenGL ES入門」等の技術書の読書メモや、自社プロダクト上で負荷テストを行った際に得られた知見等が共有されています。アカツキ流開発でも述べられているように、良いものは全エンジニアで共有ですね。

今後

アカツキでは今後も様々な活動や制度が始まる見込みです。例えば、新たに外部講師を招いたHaskell勉強会や、社内ハッカソン、国際学会派遣制度等が出来つつあります。これらの制度もトップダウンではなく、先ほど紹介したエンジニアミーティングで提案されているものです。これからもどんどん会社全体の技術力を向上させるため、また、エンジニアがもっとワクワクして働けるようにするため、良い制度・活動を作っていきます。

最後に

アカツキのエンジニアの活動の一部を紹介をさせていただきました。アカツキのエンジニアの活動・制度はまだ形成段階ですが、どんどん良い方向へと成長していっています。

僕は、会社の文化の成長フェーズに参加する事ができてワクワクしています。このような時期の環境で働いている機会を無駄にせず、どんどん会社を良くする様に貢献していきたいです。

アカツキは今夏、学生エンジニア向けインターンシップ「EMOTION」を開催します。 詳しくはこちら

UnityとOculusを始めて3週間で街を走り回るゲームを作った話

$
0
0

はじめに

アカツキは5周年を迎え、先日パーティーが開かれました。その時に展示物として披露したUnityとOculus Rift DK2を使ったゲームを作った話をしようと思います。

Image今回作ったものは隕石を避けながらコインを取って行ってゴールを目指す、レースゲームです。この記事では様々なアセットを組み合わせてUnityを使ってどのようにして短期間でレースゲームを作り上げたかを紹介したいと思います。

前提

開発し始めたときの私はこのような感じでした。

  • Unityは少しかじったことがある
  • Oculus開発は初
  • 一般的なプログラミングスキルは持っている

あと、平日は業務で忙しいため制作はもっぱら土日の開いた時間で やりました。よって実働時間は4、5日といったところです。

また、本投稿で紹介するソースコードはわかりやすくするためにUnity上必要な部分を省いているところもあるため、このままでは動作しません。あしからずご了承ください。

コンセプトメイキング

パーティのコンセプトが「祭」ということもあり、祭にちなんだアセットをつかってみようということでこのアセットを利用したゲームを作ることになりました。

ゼンリンが無料で公開しているJapanese Matsuri Cityというアセットです。 Image

この段階でのイメージでは

  • 街を歩き回る
  • コインとかを集めるドットイート系ゲーム
  • 制限時間内にどれくらいのコインを集められるかを競う

感じのゲームにしようかなと構想しました。

まずはJapanese Matsuri Cityの中を走り回るようにすることを目指しました。

試作1

Unityのバージョンは最新の5.0系を使いました。 UnityでOculusを使うのは非常に簡単です。 OculusのDeveloperページからOculus PC SDK Unity 4 Integration 0.6.0.0-betaをダウンロードしてきます。Unity 5でもそのまま利用することができます。

UnzipするとOculusUnityIntegration.unitypackageというファイルがあるので、そのファイルをUnityのプロジェクトにimportします。

importするとOVR/PrefabsのなかにOVRCameraとOVRPlayerControllerという2つのprefabがあります。これはそれぞれ

  • OVRCamera:Oculusから見る固定位置のカメラ(移動不可)
  • OVRPlayerController:Oculusから見るキャラクタ(コントローラーにより移動可)

です。今回は動きまわるアプリを作りたいのでOVRPlayerControllerを使います。 OVRPlayerControllerをSceneの中に配置します。そして、Japanese Matsuri Cityのオブジェクトに衝突判定を加えるために、Mesh Colliderを追加します。

Image

Oculus Riftをつなげて実行するとこんな感じで見えるようになりました。

Image

改良

試作1の段階では

  • 画面を回転させると酔う
  • すぐに画面の端に到達してしまって面白く無い

などの課題がありました。 これに対応するためにゲームの方向性を変える必要が出てきました。

酔いにくいコースを作る

VR酔いの主な原因は、現実世界にない不自然な移動によるものです。特に画面をコントローラーで回転させる操作はほんの数秒でも酔ってしまいます。そこで、Japanese Matsuri Cityのアセットをそのまま使うという計画を変更して、Oculusに適した下のようなまっすぐなコースを作成しました。コースの端に透明な壁を用意してプレーヤーが誤ってコースから出てしないよう細工をしています。

course

邪魔キャラを作る

何もなくただまっすぐのコースを走るのはつまらないので邪魔キャラを用意することにしました。邪魔キャラは今回天から落ちてくる隕石ということにしました。 下記のようなコードで隕石を出現させています。 intervalという変数で隕石が出現する周期を設定します。フレーム単位で呼ばれるUpdateメソッドで時刻を進めていき、周期に達したらInstantiateで隕石を作成します。隕石を作成する座標はプレーヤーの現在位置をもとにしています。

# pragma strict

var meteo : GameObject;
var interval : float = 0.1;
private var timer : float;
private var prevPlayerZ : float = 0.0f;

function Start () { timer = 0.0; }

function Update () {
    timer -= Time.deltaTime;

    if ( timer < 0.0 ) {
        var player : GameObject = GameObject.FindWithTag("Player");
        var velocityZ : float = player.transform.position.z - prevPlayerZ;
        var offsetX : float = Random.Range(0, 20) - 10;
        var offsetZ : float = Random.Range(0, 5) - 20 + velocityZ * 10;
        var position : Vector3 = Vector3(offsetX + player.transform.position.x, 20, offsetZ + player.transform.position.z);

        prevPlayerZ = player.transform.position.z;

        Instantiate(meteo, position, transform.rotation);
        Instantiate(meteo, position + Vector3(0, 0, 20), transform.rotation);
        Instantiate(meteo, position + Vector3(0, 0, 30), transform.rotation);

        timer = interval;
    }
} 

隕石はSphereを元に隕石を表現するマテリアルを追加しています。今回は火の玉っぽい感じにしてみました。

コインを作る

パワーアップアイテム、およびスコアアップのアイテムとしてコインを作りました。

単にスコアアップさせるだけでなく、プレーヤーをスピードアップさせる効果を実現しました。この操作はOVR Player ControllerのComponentの中のAccelarationという変数を操作することで実現できます。スピードアップする一方になるとゲームバランス的に易しすぎるため、隕石にあたった場合はスピードダウンさせる効果をつけます。Accelarationの値が0、もしくは負数になってしまうとゲームが成り立たなくなるため、Accelarationは初期値未満にはならないようにします。

このアプリはJavascriptを使って制作していましたが、OVR Player ControllerはC#のクラスだったため、GetComponentを使ってのアクセスはできませんでした。このため、Accelarationを操作するC#のクラスとメソッドを持つComponentを独自に用意して、Javascript側からはSendMessageを使ってアクセスするという方法を取りました。 (調べてみると他にも方法があるみたいです。) また、ゲームオーバーの時にコントローラー操作をできないようにするため、controllerのenabledをfalseに変更しています。

呼ぶ側

function Meteo() {
    var player : GameObject = GameObject.FindGameObjectWithTag("Player");
    player.SendMessage("Meteo");
}

function GetCoin() {
    var player : GameObject = GameObject.FindGameObjectWithTag("Player");
    player.SendMessage("GetCoin");
}

呼ばれる側

using UnityEngine;
using System.Collections;

public class PlayerEventListener : MonoBehaviour {

    private float initAcceleration;

    void GetCoin() {
        OVRPlayerController controller = GetComponent<OVRPlayerController> ();
        controller.Acceleration += 0.1f; // コインを取ったら加速
    }

    void Meteo() {
        OVRPlayerController controller = GetComponent<OVRPlayerController> ();

        if (controller.Acceleration > initAcceleration) controller.Acceleration -= 0.1f;
        // 隕石にあたったら減速
    }
}

制限時間をつける

ゲームらしくするために制限時間をつけます。このためには ゲーム進行中→ゲーム終了という状態遷移と時間を管理するオブジェクトが必要です。今回は「GameModel」という名前の空のGameObjectを追加して、そこで時間を管理させることにしました。状態遷移に関してはgameStateという変数を用意して扱います。

# pragma strict

var time : float = 60.0;
var readyTime : float = 5.0;
var timeObject : GameObject;
private var gameState : int = 0;
// 0 -> 待機状態
// 1 -> ゲーム中
// 2 -> ゲームオーバー

function Update () { 
    if(gameState == 0) {
    // ゲーム開始を待機
        readyTime -= Time.deltaTime;
        if (readyTime <= 0.0f){
            gameState = 1;
        }
    } else if(gameState == 1) {
        // ゲーム中
        time -= Time.deltaTime;

        if ( time <= 0.0f ) {
            time = 0.0f;
            GameOver(false);
        }
    }
}

function GameOver(isCleared : boolean) {
    gameState = 2;
    // ゲームオーバー
}

スコアを作る

スコアは隕石に触れると-100点、コインに触れると500点に設定しました。 ゴールすると5000+残り時間×500点を加算します。 スコア計算と制限時間は「GameModel」に集約しています。 触れた時にはAudioSourceを使って音声を鳴らします。

var score : int = 0;
public var meteoDownScore : int = 100;
public var coinUpScore : int = 500;

function ScoreUp (up : int) {
    if (gameState == 1) ChangeScore(up);
}

function ScoreDown (down : int) {
    if (gameState == 1) ChangeScore(-down);
}

private function ChangeScore(change : int) {
    score += change;
}

function Meteo() {
    var player : GameObject = GameObject.FindGameObjectWithTag("Player");
    player.SendMessage("Meteo"); ScoreDown(meteoDownScore);
    var src = GetComponent(AudioSource);

    src.clip = meteoClip;
    src.Play();
}

function GetCoin() {
    var player : GameObject = GameObject.FindGameObjectWithTag("Player");
    player.SendMessage("GetCoin");
    ScoreUp(coinUpScore);
    var src = GetComponent(AudioSource);

    src.clip = coinClip;
    src.Play();
}

function Goal() {
    var goalscore = 5000 + Mathf.Floor(500 * time);
    ScoreUp(goalscore);
    GameOver(true);
}

時間やスコアを表示する

スコアや時間をOculusの画面上に表示するにはOVRPlayerControllerからツリーをたどったところにあるCenterEyeAnchor/OVRTrackerBounds/Canvasの下にPanelを追加して、このPanelにTextMeshをもたせます。

スコア表示

画面上の位置は今のところ実機で表示させながら調整していくしかなさそうです。画面端に表示させると読めないので、なるべく中央に寄せておくとよいです。

ゴール演出を作る

最後にゴール演出を追加しました。 ゴールした時には「Congratulations!」の表示とゴール地点に表示される花火のオブジェクトで祝福します。

goal

振り返り

もう少し時間が取れたら

  • お邪魔キャラ(人間タイプ)をマップに登場させる
  • コントローラー操作をもう少しレース風に改良する
  • ライバルカーを登場させて競走する

といった要素を加えたかったなと思いました。

Unityの最新版の5.1ではOculusがアセットという形ではなく標準サポートされてさらに使いやすくなるようなので、今後試してみたいと思います。

次世代の組織のあり方「ホラクラシー」

$
0
0

Delivering Happinessを会社の目的として掲げるZapposが、新しい組織構造としてホラクラシーを採用して以来、IT界隈でこの考え方が急速に注目を集めるようになりました。 ただ、日本語の情報がとても少なく、ホラクラシーについての誤解が飛び交うことが多いような気がしたので、一度周辺の話題をまとめてみようと思いました。

ホラクラシーとは

ホラクラシー自体は、HolacracyOneという会社が展開している、組織の構造や文化を改善するための一連のシステムを指し示しています。 旧来のマネジメントで必ずといっていいほど発生する官僚主義や権限の集中化によるデモチベーション、無駄な会議、社内政治等の諸々の問題を解決する仕組みとして提案されています。

ホラクラシーでは、これまで当たり前であったピラミッド型の階層化された組織構造は使われません。 これまでの組織を船として例えれば、リーダー(船長)が問題解決や意思決定をし、その他の人達が命令に従って動くというモデルが当たり前でした。 ホラクラシーでは、組織を「生き物」として例え、メンバーは「細胞」として機能すると考えます。 生き物において細胞は脳による司令によって動くのではなく、自発的にまわりの細胞と協調しながら(自己組織化しながら)、必要な機能を果たします。 組織においても、一部のリーダーによる司令にメンバーが従うのではなく、セルフ・マネジメントや自己組織化をするメンバーの集まりが必要に応じて機能していく方が理にかなっていると考えられています。

ホラクラシー≠フラット

ホラクラシーの考え方があまりにも旧来の組織構造と異なっているため、近年よくみかける、リーダーがいない「フラット」な組織をすべてホラクラシーだととらえられがちですが、これは間違いです。 ホラクラシーは旧来のマネジメント方式に比べて、むしろ複雑な構造となっております。 というのも、人と人とのつながりが単なる上司・部下間のつながりで済まされる木構造ではなく、「サークル」とよばれる特定の機能を果たす集団内で複雑なネットワークができるからです。

成文主義

ホラクラシーの特徴は何かと聞かれれば、私は「成文主義」と応えます。 ホラクラシーのマネージャーからの権限委譲をする考え方はそこまで目新しいものではないかと思います。 HolacracyOneの事業で特徴的なのは、その考え方を多数の企業に再現可能な形で展開するために、「ホラクラシー憲法」を作ってしまったことであると思います。 この憲法に基いて顧客の企業に研修を展開することで、比較的大きな組織でも、ロバスト性を保ってホラクラシーの考え方を浸透することができるようです。 憲法を見ていただくとわかると思いますが、もはや法律かと思われるレベルの規程があり、ホラクラシーを真面目に導入すると、会議の進め方、発言権の決め方等をホラクラシーのルールに則り細かく管理する必要があります。 成文主義によって、ルールに従っていれば誰にでも導入ができる反面、そのルールを学ぶハードルがやや高いという印象を持っています。

より抽象的な組織論

ホラクラシーについて調べていく中で、組織の形態が進化していく過程と、次世代の組織がどの方向に向かっていくのかを詳細に研究しているFrederic Laloux氏の考え方に行き着きました。 同氏は、組織の進化の過程を以下のように分類しています。

  • Orange Organizations: 旧来のピラミッド型階層構造、「機械」としての組織、ほとんどのヒエラルキーが存在する企業
  • Green Organizations: 目的と意義のために高い自由度をもって働く組織、「家族」としての組織、 Starbucks, Southwest Airlines等
  • Teal Organizations: 「生物」としての組織、Holacracy, Buurtzorg等

Frederic Laloux氏によるとTeal Organizationsの3大ブレークスルーは以下です。

  • Self Management: セルフ・マネジメントをする
  • Wholeness: 「ありのままの自分」で仕事をしてもいい
  • Evolutionary Purpose: 組織の行く方向を誰かが仕切って決めるのではなく、組織が「どこに行きたがっているか」に耳を傾ける

「文化は戦略を食う」といった考え方や、ビジョナリー・カンパニーで強調される文化の重要性は常識となってきたような気がしますが、Teal Organizationのブレークスルーはその文化を測る尺度として新たな軸となるのではないでしょうか。

参考文献

  • Why Employeer Are Always a Bad Idea, Chuck Blakeman
  • Maverick, Ricardo Semler
  • Work Rules!, Laszlo Bock
  • Holacracy, Brian Robertson
  • Reinventing Organization, Frederic Laloux

関連動画

勉強会でスピーカーが押さえるべき5つの極意について考えてみた

$
0
0

はじめに

はじめまして。アカツキ2015年度入社の新卒1期生エンジニア、tomoです。私は技術力の底上げと経験のために、社内で毎週OpenGL ES勉強会を開催しています。この記事では、3ヶ月間勉強会を開いてみて感じた、勉強会のスピーカーが大切にしたい5つの極意についてお伝えしたいと思います。考えてみれば当たり前のことなのですが、ついつい忘れがちになってしまうことなので、勉強会を開催したことがある方もそうでない方も参考にしていただければと思います。OpenGL ESに関しては別途記事にするので少々お待ちください。

five_secret_of_study_session

スライド

以前、本テーマで発表したスライドを掲載します。5つの極意を簡単にではありますがまとめています。 5つの極意についての詳細や裏話は記事をご覧いただけると幸いです。

勉強会のスピーカーが大切にしたい5つの極意

1. 勉強会の目的、ゴールを決める

勉強会には、必ず目的とゴールがあります。目的とは勉強会で何が理解できるのか、ゴールとは勉強会が終わった後にリスナーはどういう状態になっているか、です。この2つが設定されていないと、リスナーは何が学べるんだろう?と首を傾げながら話を聞くことになってしまいます。それを防止するために、勉強会の冒頭で、目的とゴールをリスナーに提示することが大切です。

2. 独り善がりな勉強会は開いてはダメ

勉強会のリスナーは、学びたいという意志のもと、貴重な時間を割いてその勉強会に参加しています。ですので、スピーカーの使命はリスナーが求める学びを提供することです。スピーカーがアウトプットしたいこと、リスナーがインプットしたいことが一致して初めて良い勉強会と言えます。勉強会を開く前に、参加者のニーズを本気で考えて資料を作成することが大切です。発表中も、その時々で質問タイムを設けると良いと思います。重要なのは、参加者に思いやりを持つことです。

3. 本をただただ解説してはダメ

参考書の内容をもとに開く勉強会も珍しくありません。そういった勉強会で重要なのは、本をただただ解説してはいけないことです。本を読むだけの勉強会でしたら、1人でもできます。勉強会では、本の内容をいかに分かりやすく噛み砕いてリスナーに届けられるかどうかが鍵です。例えば、「本ではxxと書いていますが、これはつまりyyということです。」などと要約したりしましょう。リスナーに「この勉強会分かりやすかったなぁ」と思わせることが重要です。

4. 「次はもっと良い勉強会にする!」ことを心がける

1回限りの勉強会でも輪講でも重要な事は、次はもっと良い勉強会にする、という心構えです。私は勉強会が終わった後、参加者からアンケートを取っています。アンケートでは参加者に、勉強会全体の5段階評価や勉強会の課題点などを記入して頂いています。アンケートを取ることにより、勉強会の反省を行うことができ、勉強会の度にPDCAサイクルを回すことができます。アンケートという形式が良いかどうかはその時々によりますが、大切なのは、勉強会のPDCAサイクルを回すことです。

5. 資料は社内共有する

勉強会は開いて終わり、ではありません。せっかく勉強会を開いたのですから、その内容は参加していない社内の人たちにも共有するべきです。資料を残して、社内にノウハウを蓄積することが重要です。その行為は、勉強会の内容を復習したいリスナーのためにもなります。自分の実績にもなります。公的な内容でしたら、SlideShareSpeakerDeckにアップするのも良いでしょう。

5つの極意の裏話

大それたことを冒頭でつらつらと述べましたが、中には、確かに言ってることは分かるけど、本当にこれで勉強会が良くなるのか?と思っている方もおられるかと思います。ですので、私の3ヶ月の体験談を織り交ぜながら、どのようにしてこの5つの極意に辿り着いたのかを書きます。

3ヶ月前、私は初めて社内で勉強会を開きました。このときの勉強会では正直5つの極意は何一つできていませんでした。目的もゴールも設定せず、本に書いてある内容をそれとなくまとめただけ、アンケートも取らない、社内共有もしない。ただただ独り善がりな勉強会でした。

「文章が多いよね。」

勉強会が終わった後、チームメンバーからもらったフィードバックの1つです。 確かに、そのときの資料は本の内容をスライドに写しただけだったのでそう言われても仕方ありませんでした。この散々な結果を物語っていたのは、2回目の勉強会の参加者数です。初回でしたのでチームメンバーも皆参加してくれていましたが、2回目以降の勉強会では参加者は半分になってしまいました。

これではダメだ、と私は勉強会について本気で考えるようになりました。まず実施したのはアンケートです。勉強会を良くしようにもフィードバックがないとすぐにはアクションが取れません。自分で自分にフィードバックをするという手段もありますが、より効率良く勉強会のPDCAサイクルを回すため、私はアンケートで参加者からフィードバックをもらうことにしました。そのアンケート結果が5つの極意のベースになりました。

  • ほんの少しでも良いので、何か自分なりの工夫が出来るともっと良くなる
  • その日に勉強する内容のざっくりまとめを最初に述べる
  • その勉強会が終わった後に、何がみんなわかっている状態になるかを述べる

上記はアンケート結果から抜粋してきたものです。これらは5つの極意の「勉強会の目的、ゴールを決める」、「本をただただ解説してはダメ」のベースになりました。

また、アンケートでは以下のような意見も寄せられます。

  • 「何々を渡すと、何をする関数」みたいな簡潔な説明があったら説明がもっと分かりやすくなって良いと思った
  • だんだんソースコードを追えなくなってきたので、ライブコーディングは続けて欲しい

上記の意見は、リスナーが求めていること、期待していることです。この意見を勉強会に取り入れることでリスナーの満足度は上がると思います。これは「独り善がりな勉強会は開いてはダメ」のベースになっています。

フィードバックから作ったベースを意識し、引き続き勉強会を実施しました。また、毎週勉強会を開く上で一番に意識したことは、PDCAサイクルを回すことです。5つの極意で言うと「次はもっと良い勉強会にする!」ことを心がけるですね。なので、フィードバックを受けたことは次の勉強会で必ず実践していました。具体的に言うと、勉強会の目的、目標を決めた方が良いというフィードバックの後は、次の勉強会でそれを設定したり、コードが長くなり処理が分かりにくいという指摘があれば、ライブコーディングを導入したりしました。

そして、PDCAを回し続けた結果、勉強会そのもののフィードバックではなく、技術的な部分のフィードバックを得られるようになりました。何より、「次の勉強会も楽しみにしています。」という意見をいただけていることも嬉しいです。

最後に

まとめると、勉強会の5つの極意は以下の通りです。

  1. 勉強会の目的、ゴールを決める
  2. 独り善がりな勉強会は開いてはダメ
  3. 本をただただ解説してはダメ
  4. 「次はもっと良い勉強会にする!」ことを心がける
  5. 資料は社内共有する

ただ、勉強会をやる上で前提として意識しないといけないことは、

「一歩踏み出す勇気を持つこと」

だと思います。この5つの極意についても、勉強会を開いてみなければ気付くことができませんでした。まだ勉強会を開いたことがない方に向けて、まずは分からないなりにも勉強会を開いてみる、その勇気が大切だと思います。

急いで学ぶElixir#01

$
0
0

Motivation

クラスメソッドさんがDevelopers.IOにとても良い記事を投稿されていたので、Elixir版も書きたくなりました。

Elixirを始める

最近のソーシャルゲームでは、ユーザー同士によるリアルタイムマルチプレイがトレンドになってきました。 アカツキが最近リリースしたメザマシフェスティバルも、Node.jsによってマルチプレイが実装されています。 個人的にリアルタイムゲームサーバーの他の実装例が気になったので、調べてみることにしました

Call of Dutyの実装言語であるErlangですが、このErlangVM上で動作するElixirという言語があります。 筆者はErlang関数型言語も知見が全く無いですが、学んだことをまとめていきたいと思います。

動作環境

今回使用した動作環境は以下のとおりです。

  • OS: MaxOS X 10.9.5
  • Elixir 1.0.5

Elixirとは

Elixirはオープンソースプログラミング言語で、 ErlangVM上で動作します。 主な特徴は以下のとおり。

  • ErlangVM上で動くので、Erlangの良い所(耐障害性、高可用性)が利用できる
  • Erlangの関数を呼び出すことができる
  • スケールしやすく、プロセス間の通信が楽
  • Fault-tolerance
  • 関数型言語
  • REPL(rubyで言う所のirb)
  • mix(ビルドツール)

Elixirのセットアップ

インストール

homebrewからインストールすることが出来ます

$ brew install elixir

サンプルを動かしましょう

せっかくなので今回はREPLを使ってみたいと思います。 iexコマンドでREPLを起動することが出来ます。(Ctrl+C 2回で停止)

$ iex

Hello Worldを実行するために、IO.puts/1を利用します。

iex(1)> IO.puts("Hello Elixir") Hello Elixir :ok

IO.puts/1は:okという値を返却しますが、これらについては次回以降に解説したいと思います。

また、Elixirは公式のドキュメントが豊富なので、気になった方はこちらを読み進めてみてください。

参考サイトなど

急いで学ぶElixir#02

$
0
0

Elixirの基本型

以下の基本型があります。 - integer : 1, 0x1F - float : 1.0 - boolean : true, false - atom (symbol) : :atom - string : "Elixir" - list : [1, 2, 3] - tuple : {1, 2, 3}

演算

iex> 1 + 2 3 iex> 5 * 5 25 iex> 10 / 2 5.0

Elixirでは/は必ずfloatを返します。 整数の商や余剰を得るには、divまたはremを使用します。

iex> div(10, 2) 5 iex> div 10, 2 5 iex> rem 10, 3 1

このように、Elixirでは関数を呼び出すのに括弧を省略できます。 2進数や8進数、16進数は以下のように使用でいます。

iex> 0b1010 10 iex> 0o777 511 iex> 0x1F 31

小数点を使用するとfloat型になり、指数形式を使用することもできます。

iex> 1.0 1.0 iex> 1.0e-10 1.0e-10

float型は64ビット精度です。 四捨五入にはround、切り捨てにはtruncを使用します。 iex> round 3.58 4 iex> trunc 3.58 3

Boolean型

truefalseを使用します。

iex> true true iex> true == false false

boolean型であるかどうかをチェックするために、is_boolean/1関数が用意されています。 (Elixirでは、関数を引数の数も含めて指定するときにスラッシュを使用します)

iex> is_boolean(true) true iex> is_boolean(1) false

同様に、is_integer/1is_float/1is_number/1も用意されています。

Atom

Atomはその名前そのものが値となる定数で、他の言語(Ruby等)ではシンボルと呼ばれます。

iex> :hello :hello iex> :hello == :world false

boolean型のtruefalseは実はAtomです。

iex> true == :true true iex> is_atom(false) true iex> is_boolean(:false) true

String型

Elixirではダブルクオーテーションを用いて文字列を表現します。 UTF-8エンコードされます。

iex> "エリクサー症候群""エリクサー症候群"

iex > a = 3 3 iex > "エリクサー症候群#{a}""エリクサー症候群3"

文字列中に改行を入れたり、エスケープシーケンスを使用することができます。

iex> "hello ...> world""hello\nworld" iex> "hello\nworld""hello\nworld"

IO.puts/1を使用して文字列を出力することができます。

iex> IO.puts "hello\nworld" hello world :ok

Elixirの文字列は内部ではバイト列のバイナリとして扱われます。

iex> is_binary("hellö") true

文字列の長さはString.length/1で得ることができます。

iex > String.length("エリクサー") 5

その他、UTF-8に対応したString.upcase/1が用意されてます。

iex> String.upcase("hellö") "HELLÖ"

無名関数

関数はfnendキーワードによって定義されます。

iex> add = fn a, b -> a + b end

Function<12.71889879/2 in :erl_eval.expr/5>

iex> is_function(add) true iex> is_function(add, 2) true iex> is_function(add, 1) false iex> add.(1, 2) 3

Elixirでは関数は"first class citizens"に分類されるので、integer型やstring型と同様に引数として関数に渡すことができます。 したがってis_function/1addを渡すと期待通りtrueを返します。 is_function/2の第二引数によってaddに対して定義されている引数の数を確認することができます。

無名関数を呼び出すためには、.(ドット)を使用する必要があります。

無名関数はクロージャーなので、 定義された時点でスコープに含まれる変数にアクセスすることができます。

iex> add_two = fn a -> add.(a, 2) end

Function<6.71889879/1 in :erl_eval.expr/5>

iex> add_two.(2) 4

関数内での変数への代入は、その外側の環境内の変数には影響しません。

iex> x = 42 42 iex> (fn -> x = 0 end).() 0 iex> x 42

リスト型

[]を使用してリストを定義します。リストの値はどんな型でも使用できます。 iex> [1, 2, true, 3] [1, 2, true, 3] iex> length [1, 2, 3] 3

++/2--/2演算子を使用することで、二つのリストを結合することができます。 iex> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6] iex> [1, true, 2, false, 3, true] -- [true, false] [1, 2, 3, true]hd/1及びtl/1(head, tail)を使用し、リストの先頭及び先頭以外の要素を取り出すことができます。

iex> list = [1,2,3] iex> hd(list) 1 iex> tl(list) [2, 3]

以下のようにリストを戻り値がシングルクォートで囲われた値になる場合があります。

iex> [11, 12, 13] '\v\f\r' iex> [104, 101, 108, 108, 111] 'hello'

ElixirはASCII数字のリストを認識したとき、それをcharのリストとして出力します。 シングルクォートとダブルクォートの文字列は別々の扱いとなります。

iex> 'hello' == "hello" false

Tuple型

Tupleは{}を使用して定義します。 リストと同様に、Tupleの値の型は問いません。

iex> {:ok, "hello"} {:ok, "hello"} iex> tuple_size {:ok, "hello"} 2

Tupleはメモリ上の隣接した位置に要素を格納します。 したがって、インデックスによる要素の取得及び要素数の取得を高速に実行することができます。

iex> tuple = {:ok, "hello"} {:ok, "hello"} iex> elem(tuple, 1) "hello" iex> tuple_size(tuple) 2

put_elem/3により特定の位置に要素を格納することも可能です。

iex> tuple = {:ok, "hello"} {:ok, "hello"} iex> put_elem(tuple, 1, "world") {:ok, "world"} iex> tuple {:ok, "hello"}

put_elem/3は新しいTupleを返しましたね。 tuple変数に格納した値は変更されませんでした。 これは、Elixirではデータ型はimmutableだからです。 この性質があるので、Elixirのコードは変数の値が勝手に変わってしまわないかという心配をする必要がありません。

また、immutableの性質により、並列処理において複数のエンティティが同じデータ構造を変えてしまう心配もしなくてよくなります。

ListかTupleか

ListとTupleの違いはなんでしょうか。

Listはメモリ上でリスト構造としてデータを格納します。 すなわち、各要素はその値と次の要素へのポインタを保持します。 要素と次の要素へのポインタのペアを"cons cell"と呼びます。

iex> list = [1|[2|[3|[]]]] [1, 2, 3]

したがって、リストの長さを計算するのは線形計算となります。 データを先頭に追加する分には、高速な処理となります。

iex> [0 | list] [0, 1, 2, 3]

一方でTupleは、メモリ上に並んで格納されます。 したがってTupleのサイズを取得したり、インデックスによって要素を取得するのは高速な演算となります。 ところが、要素を更新したり追加したりする処理は、Tupleの全データをメモリ上にコピーする必要があるために、 計算コストが高くなります。

このようなパフォーマンスの違いを踏まえ、それぞれのデータ構造を使い分けます。 Tupleがよく使われるケースとして、関数の戻り値が挙げられます。

iex> File.read("path/to/existing/file") {:ok, "... contents ..."} iex> File.read("path/to/unknown/file") {:error, :enoent}

ほとんどの場合、Elixirが用意した関数を使うことで適切なデータ構造を選ぶことになるでしょう。 例えばelem/2はTupleに対して用意されてますが、Listに対しては用意されてません。

iex> tuple = {:ok, "hello"} {:ok, "hello"} iex> elem(tuple, 1) "hello"

素数を数える場合、Elixirでは定数時間の演算に対してはsize、線形時間の場合はlengthという命名をします。

最後に、ElixirではPort, ReferencePIDといったデータ型がありますが、この記事では割愛します。

翻訳元

http://elixir-lang.org/getting-started/basic-types.html

新規事業で Elixir, Phoenix, React を使う

$
0
0

エンジニアリング・アドバイザーの noto です。先月末から新規事業チームのエンジニアリングについてもお手伝いすることになりました。

アカツキでは今年 (2015 年) の夏より、従来のゲーム事業の枠を超えて、教育、ヘルスケア、「働く」などの領域を対象とした新規事業の検討・開発を開始しています。(新規事業に関するプレスリリース / 新規事業公式ページ)

こういった領域では「作っては壊し、作っては壊し」のトライアル・アンド・エラーが繰り返されると想定されるため、技術の選択に失敗した場合でもリカバリできる可能性も高くなります。そのため、従来のゲーム事業に比べると新しい技術を取り入れるチャレンジがしやすいと考え、社内で使い慣れた

とは別の技術を導入し始めています。

サーバサイド

サーバサイドについては

にトライしています。Elixir は ErlangVMで動作するため、並行処理、分散処理、対障害性などに関する Erlangの持つ利点を享受できます。

また、PhoenixChannelsというリアルタイム通信の仕組みを持っています。アカツキ社内ではリアルタイム通信の仕組みが必要な場合は、Rails で実装した API サーバの他に Node.js を使うといったこともしていますが、Channels を使うと両方の役割を Phoenixで済ませるということも可能になり、ゲーム事業での活用に繋がることも期待されます。

ただ、なんといっても Elixir は「Ruby風」と言われる文法を持つことが Rubyエンジニアの多いアカツキにとっては大きな意味を持っています。Ruby (on Rails) プログラマーは Elixir のサンプルコードを見てもとっつきにくさを感じないでしょうし、同様に Phoenixのサンプルコードを見ても抵抗はないと思います。また、最悪使い続けるのが難しいような大きな落とし穴が見つかったとして、Ruby on Railsに「フォールバック」しやすいと考えています。

また、プロトタイプをさくっと作るという側面では、Backend as a Service である Parseの活用も選択肢に入ってきます。

フロントエンド

今、社内で進めようとしている開発対象は、スマートフォン向けアプリケーションを最初から作るというより、ブラウザ向けに開発するところから始めたほうがいいということで、規模や複雑さに応じて

  • シンプルなもの: jQuery
  • 複雑なもの、ある程度規模が大きいもの: React

というように使いわけしていこうと考えています。

また、従来 View レイヤーは個別のページをテンプレートで実装するというのが一般的でしたが、現在では個別のページで共通利用されるコンポーネント単位で実装する必要が出てきています。Bootstrap など開発者が使いやすい優れた HTML / CSS / JS のフレームワークもあり、それをベースにプロトタイプ開発を先行させ、デザイナーはそういった枠組みを活用しながらプロダクトに適した表現を適用するという順序になる可能性もあります。そういった意味では、エンジニアとデザイナーとの間の協調方法、ワークフローを再定義する必要があるタイミングなのかもしれません。こういった部分でもチャレンジが必要となっています。

新規事業での技術選定の考え方

ここまでがあくまでも現時点でのトライを説明したものになります。新規事業チームの中でも取り組むテーマが、教育、ヘルスケア、「働く」などと様々であり、そのチームの開発対象や参加するメンバーによっても最適な技術は変わってくると考えています。

Elixir (Phoenix) と Ruby (Ruby on Rails) のように行き来がしやすい技術は、開発者の心理抵抗を下げる、社内エンジニアのチーム間の流動性を担保するというメリットがあります。一方、社内にノウハウが蓄積されていない技術である場合、導入の調査・準備に時間がかかる、当初の開発生産性が従来の技術を利用する場合にくらべると低い状態で始まり、同じ程度になるまで多少の時間がかかるといったデメリットもあります。ただ、アカツキの新規事業チームでは企画・ビジネスサイドのメンバーもそういった必要な「コスト」を把握した上で、新しい技術を取り込むことにポジティブな姿勢なので、エンジニアチーム側もそういったメリット/デメリットを説明できれば柔軟に新しい技術を取り込んでいける環境になっています。今後も個々のチームごとに自主的に技術選定できるよう進めていく予定です。

仲間募集中!

アカツキ新規事業チームでは、Elixir, Phoenix, React など新しめの技術を実務で使いたいエンジニアを 5〜10 名規模で募集しています。正社員でも/業務委託でも、フルタイムでも/パートタイムでも幅広く門戸を開いています。新しい技術をスピーディーに取り入れられるかた、どの技術を導入すべきかいっしょに考えてくださるかただとうれしいです。興味を持っていただける場合、

http://aktsk.jp/recruit/category/new_service_engineer.html

あたりからご応募いただくか、 私 @noto、その他アカツキメンバーまでカジュアルにコンタクトいただければ幸いです。

あわせて読みたい

急いで学ぶElixir #01, #02


急いで学ぶElixir#3: 演算子編

$
0
0

Basic Operators

前回、Elixirは四則演算があることと、整数の商や余剰を得るためにdivremがあることを学びました。今回はElixirの基本的な演算子を、Rubyと少し比較しながら学んでいきましょう。

Elixirでは、++や--を配列に対しても使うことが出来ます。

iex> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]
iex> [1,2,3] -- [2]
[1,3]

文字列の連結は<>で可能です。(Rubyでは+だけで文字列を連結出来ますね)

iex> "foo"<> "bar""foobar"

Elixirは3つのbooleanオペレーターorandnotを提供しています。これらのオペレーターを使うためには、初項がboolean(true/false)でなければなりません。

iex> true and true
true
iex> false or is_atom(:example)
true

初項がbooleanでない場合は、例外が起きます。

iex> 1 and true
** (ArgumentError) argument error

Rubyでは例外が起きずに、数が来たらtrueと評価するので、ここはElixirがより厳密に型を扱っていることが良くわかりますね。

irb(main):001:0> 1 and true
=> true

orandは短絡オペレーターです。もし左側だけで結果を決定できないときのみ、右側も実行します。(例えばfalse andの場合は右側に何がきてもfalseなのでfalseとなります。)

iex> false and raise("This error will never be raised")
false

iex> true or raise("This error will never be raised")
true

ちなみにElixirにおけるandorは、Erlangandalsoorleseと同一です。

これらのbooleanオペレーターと異なり、Elixirでは||&&!はどんな型に対しても受け付けます。この場合、falsenil以外はtrueとして評価されます:

# or
iex> 1 || true
1
iex> false || 11
11

# and
iex> nil && 13
nil
iex> true && 17
17

# !
iex> !true
false
iex> !1
false
iex> !nil
true

おおざっぱにまとめると、以下の通りです。

  • booleanであることを期待 =>and, or, not
  • booleanでない引数がきて良い =>||, &&, !

Elixirでは比較演算子として、==!====!==< =>=<>を提供しています。

iex> 1 == 1
true
iex> 1 != 2
true
iex> 1 < 2
true

=====の違いは、整数と浮動小数を比較するときに、後者の方がより厳密です。

iex> 1 == 1.0
true
iex> 1 === 1.0
false

Rubyの場合、===はサブクラスで再定義されない限り==と同一なので、===そのものにこういった厳密性の意味はありません。

Elixirでは、2つのデータの型を比較可能です。(ここもRubyとはっきり異なるところです)

iex> 1 < :atom
true

異なるデータの型を比較可能な理由は実用主義によるものです。ソートアルゴリズムで異なるデータ型同士で順序付けについて心配する必要はありません。ソート順は以下のように定義されています:

number < atom < reference < functions < port < pid < tuple < maps < list < bitstring

正確にこの順番を覚える必要はありませんが、順番が存在することだけ知っているのは重要です。

次からは基本的な関数、型変換、ちょっとしたフロー制御について議論します。

元記事

http://elixir-lang.org/getting-started/basic-operators.html

resize2fsコマンドの先でカーネルは何をしているのか

$
0
0

背景

前回の記事で、resize2fsコマンドがどのように1秒未満での容量拡張を実現しているかを知るために、resize2fsコマンドのソースを調査しました。その結果、メタデータの一つであるGlobal Descriptor Tables(GDT)をカーネル内で更新しているからではないか、という示唆を得られました。今回は、実際にカーネルのコードを読んで、この示唆が正しかったことを見ていきたいと思います。

調査対象

今回も新しめのカーネルで調査しました。AmazonLinuxとは多少ソースが異なっているかもしれませんが、本質は大きく変わらないかと思います。

前回の復習

前回調べたときから約1年空いてしまいましたので、軽く前回の復習をします。

ext4ファイルシステムでは、ブロックグループという単位で複数のブロックをグループ化してデータを管理しています。ブロックグループの中にはGroup Descriptor Tables(GDT)というブロック領域があり、ここに全ブロックグループを管理するための情報であるグループディスクリプタを格納しています。全体のブロックグループ数が多ければ多いほど、GDT領域が大きくなります。前回、resize2fsコマンド経由でGDT領域を更新しているのではないか、という示唆を得ました。

ext4_bg

GDT領域で扱うサイズより大きなファイルサイズにオンラインで拡張したいときはどうすれば良いのでしょう?ext4ではこういうときのために、予約済のGDT領域(RGDT)をブロックグループの中に用意しています。オンラインでファイルシステムがリサイズ出来るのは、このRGDTを備えているからと言えます。実際、resize2fsコマンド内ではRGDTで管理しきれるファイルサイズにしようとしているかをチェックしていました。

resize2fsコマンドでは、カーネルファイルシステムのサイズを変更させるために、EXT4_IOC_RESIZE_FSリクエストのioctl(2)を発行していました。今回の記事ではこのioctl(2)の発行後に、カーネルは何をやっているのかを調べていきます。

リサイズリクエストのioctl(2)の処理内容

ioctl(2)のEXT4_IOC_RESIZE_FSリクエストに関するソースコードから読んでいきたいと思います。繰り返しますが、今回のゴールは容量拡張のメイン処理が何であるかを明らかにすることです。

まずEXT4_IOC_RESIZE_FSでgrepしてみると・・・fs/ext4/ioctl.cにありました。ここから読み進めてみましょう。

554      case EXT4_IOC_RESIZE_FS: {
555           ext4_fsblk_t n_blocks_count;
556           int err = 0, err2 = 0;
557           ext4_group_t o_group = EXT4_SB(sb)->s_groups_count;
558
559           if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
560               EXT4_FEATURE_RO_COMPAT_BIGALLOC)) {
561                ext4_msg(sb, KERN_ERR,
562                "Online resizing not (yet) supported with bigalloc");
563                return -EOPNOTSUPP;
564           }

前回、resize2fsでは-fを付ければbigalloc(ページサイズ以上のブロック単位をサポートする機能)でもresizeできるかのように見えましたが、最新バージョンのカーネルでもサポートされていないようです。not (yet)なので、今後に期待しましょう。

566           if (copy_from_user(&n_blocks_count, (__u64 __user *)arg,
567                                          sizeof(__u64))) {
568                return -EFAULT;
569           }
570
571           err = ext4_resize_begin(sb);
572           if (err)
573                return err;

ext4_resize_begin()では、現在リサイズしようとしているファイルシステムに対して同時にリサイズ命令しないように、メモリ内のスーパーブロック情報(ext4_sb_infoオブジェクト)にこのファイルシステムはresize中であるというフラグ(EXT4_RESIZE)を立てるという処理をします。

575           err = mnt_want_write_file(filp);
576           if (err)
577                goto resizefs_out;

mnt_want_write_file()では、通常パスでは2つの関数が呼ばれます。

1つ目はsb_start_write()です。この関数は他のプロセスによる書き込みを排除するために呼ばれます。具体的には、ファイルシステムを書き込みfreeze状態(SB_FREEZE_WRITE)とします。freeze状態とは、ファイルシステム用に用意されたロック機構の1つであり、一番緩いロック(=ある程度の競合を許すロック)です。freeze状態にはいくつかレベルが用意されています。レベルが低い順にフローズしていない状態(SB_UNFROZEN)、書き込みfreeze状態(SB_FREEZE_WRITE)、ページフォルトのfreeze状態(SB_FREEZE_PAGEFAULT)、内部のファイルシステム使用freeze状態(SB_FREEZE_FS)、完全なfreeze状態(SB_FREEZE_COMPLETE)です。sb_start_write()を使用する場合、freeze状態が解けるまでsleepして待ち続けます。

2つ目は__mnt_want_write_file()です。この関数はそのファイルシステムへwriteアクセスすることを記録するために呼ばれます。ただ、もしread onlyでファイルシステムがマウントされている場合はエラー(EROFS)となります。エラーとなったときは、書き込みfreeze状態を解除します。

579           err = ext4_resize_fs(sb, n_blocks_count);

いよいよ、今回のメインの関数である、ext4_resize_fs()について見ていきましょう。まず渡している引数を見ていきます。第一引数のsbとはresizeされるファイルシステムのsuperblockの構造体(super_block)です。superblockとは、ファイルシステム全体を管理しているブロックのことです。このsuper_block構造体は全てのファイルシステムで使われる一般的な形式となっており、ファイルシステム特有の情報(private情報)はs_fs_infoにvoid型のポインタとして格納されています。ext4_resize_fs()の第二引数n_block_countは、resize2fsコマンドから渡って来た追加分を含む新しいブロック数です。

ext4_resize_fs()

それでは実際に、ext4_resize_fs()で重要な処理に着目して、何をやっているか理解しましょう。

1896          o_blocks_count = ext4_blocks_count(es);
1897
1898          ext4_msg(sb, KERN_INFO, "resizing filesystem from %llu "
1899          "to %llu blocks", o_blocks_count, n_blocks_count);
1900
1901          if (n_blocks_count < o_blocks_count) {
1902                  /* On-line shrinking not supported */
1903                  ext4_warning(sb, "can't shrink FS - resize aborted");
1904                  return -EINVAL;
1905          }
1906
1907         if (n_blocks_count == o_blocks_count)
1908                 /* Nothing need to do */
1909                 return 0;

ここでは、現在のブロック数o_blocks_countをsuper blockから読み出し、新しいブロックサイズn_blocks_countと比較しています。onlineでリサイズする場合はshrinkがサポートされていないことがわかります。

1911         n_group = ext4_get_group_number(sb, n_blocks_count - 1);
1912         if (n_group > (0xFFFFFFFFUL / EXT4_INODES_PER_GROUP(sb))) {
1913                 ext4_warning(sb, "resize would cause inodes_count overflow");
1914                 return -EINVAL;
1915         }
1916         ext4_get_group_no_and_offset(sb, o_blocks_count - 1, &o_group, &offset);

次に、新しいブロックサイズのグループ数n_groupと現在のブロックサイズのグループ数o_group、グループ内のデータを取得しています。新しいグループ数がものすごく大きな値になっていたらここで-EINVALが返ります。

1918         n_desc_blocks = num_desc_blocks(sb, n_group + 1);
1919         o_desc_blocks = num_desc_blocks(sb, sbi->s_groups_count);

ここは、Group Descriptor Tableが何ブロック必要かを、新旧取得しています。

1923         if (EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RESIZE_INODE)) {
1924                 if (meta_bg) {
1925                         ext4_error(sb, "resize_inode and meta_bg enabled "
1926                         "simultaneously");
1927                         return -EINVAL;
1928                 }
1929                 if (n_desc_blocks > o_desc_blocks +
1930                     le16_to_cpu(es->s_reserved_gdt_blocks)) {
1931                         n_blocks_count_retry = n_blocks_count;
1932                         n_desc_blocks = o_desc_blocks +
1933                                 le16_to_cpu(es->s_reserved_gdt_blocks);
1934                         n_group = n_desc_blocks * EXT4_DESC_PER_BLOCK(sb);
1935                         n_blocks_count = n_group * EXT4_BLOCKS_PER_GROUP(sb);
1936                         n_group--; /* set to last group number */
1937                 }
1938
1939                 if (!resize_inode)
1940                         resize_inode = ext4_iget(sb, EXT4_RESIZE_INO);
1941                 if (IS_ERR(resize_inode)) {
1942                         ext4_warning(sb, "Error opening resize inode");
1943                         return PTR_ERR(resize_inode);
1944                 }
1945         }

resize2fsコマンドからオンラインresizeする場合は、resize_inode機能を必須とするので、実質このブロックは必ず通ることになります。

meta_bgについての細かい説明は前回の記事を参考にしていただきたいですが、meta_bgを使用しているときはresizeがサポートされていないことがわかります。

次のチェックは、今から拡張しようとしているファイルシステムを管理するためのGDTが既存のGDTとRGDTで収まりきれるかを確認しています。収まりきれなかった場合は、resize2fsコマンド内でこの拡張操作を禁止していましたが、カーネル内ではRGDTの最大限まで確保するような処理となるようです。

最後にresize_inodeを取得してきます。このinode番号EXT4_RESIZE_INOは7番となります。

1962         /* extend the last group */
1963         if (n_group == o_group)
1964                 add = n_blocks_count - o_blocks_count;
1965         else
1966                 add = EXT4_BLOCKS_PER_GROUP(sb) - (offset + 1);
1967         if (add > 0) {
1968                 err = ext4_group_extend_no_check(sb, o_blocks_count, add);
1969                 if (err)
1970                        goto out;
1971         }

追加ブロックがある場合は、ext4_group_extend_no_check()で、ext4_group_extend_no_check()のコメントを読むと最後のブロックグループに新しいブロック分を追加処理とのことです。 どうやらこの関数が私たちの目的の関数のようです。この関数を見ていきましょう。

ext4_group_extend_no_check()

ext4_group_extend_no_check()を読み進めると・・・ありました。

1670         ext4_blocks_count_set(es, o_blocks_count + add);
1671         ext4_free_blocks_count_set(es, ext4_free_blocks_count(es) + add);
1672         ext4_debug("freeing blocks %llu through %llu\n", o_blocks_count,
1673                         o_blocks_count + add);

super blockが管理しているblock数とfree block数を、super blockに更新しています。

1674         /* We add the blocks to the bitmap and set the group need init bit */
1675         err = ext4_group_add_blocks(handle, sb, o_blocks_count, add);

このext4_group_add_blocks関数はGDTを更新しているように思えますね。 実際のところはどうなっているでしょうか?

ext4_group_add_blocks()

ext4_group_add_blocks()を見ると・・・ありました!

4985         blk_free_count = blocks_freed + ext4_free_group_clusters(sb, desc);
4986         ext4_free_group_clusters_set(sb, desc, blk_free_count);

descはGDTの構造体のポインタで、blocks_freedが追加されるブロック数です。 確かに前回予想した、GDTの更新をしていることがわかります。

まとめ

resize2fsのカーネル処理は、以下のような処理をしていることがわかりました。 1. ユーザー空間からioctl(2)のEXT4_IOC_RESIZE_FSリクエストでresize2fsのカーネルの処理を実行 2. 同時リサイズを避けるために、ファイルシステムにリサイズ中であるフラグを立てる 3. super blockの全ブロック数と全フリーブロック数を更新 4. GDTを更新

オンラインresize2fsがなぜ高速で終わるのか?という疑問に対して、スーパーブロックのブロック数の管理領域とGDTを更新しているだけだからだ、というのがわかりました。

この記事には書きませんでしたが、実はext4_resize_fs()の処理の後に、lazy initializationという機能を使い、未初期化のinode tableをext4lazyinitカーネルスレッドに非同期に初期化させるということもします。少しでも高速化するように色々な工夫がなされていることがわかります。

いくつか読み飛ばした処理もあるので、もっと深く知りたい方は本記事で取り上げた処理以外の部分を読んでみてはいかがでしょうか。

Electronで社内ツール作ってみた

$
0
0

はじめに

アカツキにて内定者インターンをしているsachaosです。 この記事ではプロジェクトに配属され、少しの間アシスタントディレクター業を行っていた僕が、 エンジニアの端くれなりに社内ツールを作って業務プロセスの無駄な部分を自動化した話をします。

アシスタントディレクター業

アシスタントディレクター業として僕に任せられたのは、とあるゲーム内のお知らせを作成する業務でした。 お知らせとはゲームに追加された施策(イベント・ガチャ)の内容や、メンテナンスの時間等をユーザに伝えるものです。 お知らせはHTMLで書かれていて、タイトル、表示開始日時、表示終了日時などの値と共にデータベースに保存されています。 また、お知らせには画像も含まれていてアカツキではAWS S3に保存しています。

改善前の業務プロセス

お知らせの作成では以下のような業務プロセスが取られていました。

  1. HTMLを書く
  2. Sequel Pro(GUIのDBクライアント)を立ち上げてレコードに手動で入れる
  3. Cyberduck(GUIFTPクライアント)を立ち上げて画像をアップロード
  4. 実機で表示が正しいか確認。

このプロセスを複数回繰り返していくことでお知らせを作成していくのですが、 手動でレコードを挿入・更新したりする必要があり時間がかかる。 また、重いソフトウェアを立ち上げておかなければならないので、 なかなかストレスフルだという問題がありました。 なにより、レコードを直接弄るといろいろな人為的ミスも増えます。 レコードを弄るのは人間の仕事ではありません

お知らせ更新ツールの作成

上記のようなプロセスはなかなかつらいので、 CLIスクリプトを書き自動化していたのですが、 後任のお知らせ作成業務をするディレクターに作業を引き継ぐ必要がでてきましたので、 引き継ぎやすいようにGUIのツールを作成することにしました。

エンジニアかつミーハーである僕は、 今流行りのElectronを使用してGUIツールを作成することにしました。

Electron?

ElectronとはGitHub社が提供しているアプリケーションフレームワークです。 同じくGitHubが提供しているエディタのAtomやSlackのアプリケーション、Kobito for Windowsで使用されていることで有名です。 内部はChromium + node.jsとなっていてWebの技術(HTML, CSS, Javascript)で、スタンドアローンアプリケーションを作成することができます。 Mac, Windows, Linuxにクロスコンパイル可能となっているので、使用者の環境を考える必要がありません。

お知らせのをHTMLファイル一つで管理する

ツールの作成にあたり、お知らせに付随する情報(タイトル、表示開始日時、表示終了日時など)をHTMLファイル一つに管理した方が便利だと思いましたので、 metaタグとしてHTMLファイルの中に上記の情報を埋め込んで管理することにしました。

<meta name="title" content="超重要なお知らせ!!!" />
<meta name="start_at" content="2015-08-01 15:00" />
<meta name="end_at" content="2015-12-6 23:59" />
<!-- 以下お知らせの内容 -->

HTMLファイルが入力された時にパースしてmetaタグの中身を取り出し、その値を元にレコードを登録・更新します。

ドラッグ&ドロップでお知らせを更新

お知らせが書かれたHTMLと必要な画像を入れたフォルダをドラッグ&ドロップするだけで, HTMLはDBに保存し画像をAWS S3へアップロードするアプリケーションを作成しました。 こんな感じです。

画像

人間の仕事っぽいですね。

改善前の業務プロセスでは一回の更新作業で、 どうしても2分程度かかってしまっていましたが、 これにより3秒で更新することができるようになりました。

時間を短縮することで本来するべきお知らせの内容等に、 時間をかけることができるようになりました。

Electronどうだった?

メリット

Webの技術で簡単にスタンドアローンのアプリが作成できる。

ビューの作成もHTML、CSSで簡単に作成することができますし、 デバッグChromiumの開発者ツール上で行えます。 またnode.jsまわりの既存の資産を活用できるので、 簡単に強力なアプリケーションを作成することができます。

使用者の環境を整える必要がない。引き継ぎが楽。

スタンドアローンなので、環境構築・バージョン管理を意識させず 「このアプリをDropBoxから落としてきてー」というだけで アプリを使用させることができます。 また、クロスプラットフォーム(Mac, Linux, Windowsへ吐き出せる)なので、 使用者のOSを考える必要がなく、様々なOSが混在するような社内環境でも問題ありません。

デメリット

謎な挙動がある。

tmuxを通して立ち上げると文字入力できない等、 たまに謎な挙動に出くわしました。 しかし困ったことがあっても大体ググれば問題は解決できます。 解決できなければコントリビュートするチャンスです!

吐き出されたものが無駄に大容量。

結局Chromiumを内蔵しているので、 アプリケーションにした物はブラウザくらいのファイルの大きさになります。

まとめ

  • 社内GUIツールを作って業務改善しました。
  • Electron、GUIツールの作成におすすめです。

社内ツールを作るのはエンジニア的には楽しいですし、業務も改善することができます。 Electronを使用すれば簡単にこういったツールを作ることができるのでおすすめです。 もし、社内の業務でイケてない部分があれば、ツールの作成で解決することができるかもしれません。 ぜひ、試してみてはいかがでしょうか?

急いで覚えるElixir: 制御構文編

$
0
0

Elixirの基本制御構文

前回の記事から続いて、今回はElixirで利用する基本的な制御構文について学んでいきます。

if, unless/else

他のプログラミング言語で親しまれているif~elseは、Elixirでは以下のように記述します。

iex> if true do
...>   "みえる"
...> else
...>   "みえない"
...> end
"みえる"

ifをunlessに変えることで、条件を反転させることが出来ます。

case

case文は以下のように記述します。

iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "この条件にはマッチしない"
...>   {1, x, 3} ->
...>     "この条件にマッチして、この文のスコープ変数xに2が代入される"
...>   _ ->
...>     "_ はどの条件にもマッチする"
...> end
"この条件にマッチして、この文のスコープ変数xに2が代入される"

cond

case文は幾つかの値の中でマッチするのもを探しだすのに便利ですが、幾つかの条件の中で最初にマッチするものを探し出したい場合もあります。 このようなときは、cond文を使用します。

iex> cond do
...>   2 + 2 == 5 ->
...>     "ちがう"
...>   2 * 2 == 3 ->
...>     "これもちがう"
...>   1 + 1 == 2 ->
...>     "これだ!"
...> end
"これだ!"

上から評価されますが、最後までマッチされなかった時はエラーになります。

iex> cond do
...> 1 + 2 == 2 ->
...> "wow"
...> end
** (CondClauseError) no cond clause evaluated to a true value

do/end ブロック

Elixirもrubyのようにdo/endでブロックを定義することが出来ます。 do/endブロックで注意しないといけないのは、もっとも外側の関数の呼び出しに対してバインドされる点です。すなわち、以下の例は、

iex> is_number if true do
...>  1 + 2
...> end

以下のようにパースされることになります。

iex> is_number(if true) do
...>  1 + 2
...> end
** (RuntimeError) undefined function: if/1

また、ブロック内で定義された変数などは、ブロック外からはスコープ外になります。

raise

Elixirもrubyのようにraise句でエラーを発生させることが出来ます。 デフォルトはRuntimeErrorです。

iex> raise "oops"
** (RuntimeError) oops

エラーを独自に定義して、raiseの引数で指定することも出来ます。

iex> defmodule MyError do
iex>   defexception message: "default message"
iex> end
iex> raise MyError, message: "custom message"
** (MyError) custom message

rescue

エラーを補足するにはrescue句を利用します。

iex> try do
...>   raise "oops"
...> rescue
...>   e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}

throw/catch

Elixirで大域脱出を行うにはthrow/catchを利用します。 throwで値を投げ、catchで補足します。

iex> try do
...>   Enum.each -50..50, fn(x) ->
...>     if rem(x, 13) == 0, do: throw(x)
...>   end
...>   "Got nothing"
...> catch
...>   x -> "Got #{x}"
...> end
"Got -39"

after

after句で、rubyのensureのように、try句の終了直前に必ず実行する処理を記述することが出来ます。

iex> try do
...>   IO.puts "try!"
...>   raise   "raise!!"
...> after
...>   IO.puts "after!!!"
...> end
try!
after!!!
** (RuntimeError) raise!!

exit

すべてのElixirコードは互いに通信し合うプロセス上で動いています。 プロセスが「自然死」する場合(例えば未処理の例外が起きた時)、exitシグナルを送ります。 また、手動でexitシグナルを送ることでプロセスを殺すこともできます。

try do
  exit "急いで覚える"
catch
  :exit, _ -> "Elixir"
end
"Elixir"

exitは前述のtry/catchで補足することも出来ます。

まとめ

Elixirの基本的な制御構文を学ぶことが出来ました。 より詳しく知りたい方は、以下の公式ドキュメントからよりカッコイイ書き方を知ることが出来ます。(外部サイトへ飛びます。)

急いで覚えるElixir: Enumerable編

$
0
0

ElixirのEnumerable

前回の記事から続いて、今回はElixirで利用する基本的な制御構文について学んでいきます。

Keyword list

多くの関数型プログラム言語では、2要素のtupleによって関連付けられたデータ構造を表現します。 Elixirでは、最初の要素がAtomであるTupleのListのことをKeyword listと呼びます。

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true
iex> list[:a]
1

キーが重複している場合、先頭に格納された値が優先的に読まれます。

iex> new_list = [a: 0] ++ list
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0

Keyword listは以下の重要な性質を持ちます。

  • キーはAtomでなければならない
  • キーはユーザーが指定した順に並ぶ
  • キーが重複可能である

Map

Key value storeが必要なときは、迷わずMapを使うといいでしょう。 Mapは %{}構文により作成します。

iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil

Keyword list との違いは以下の点です。

  • MapはKeyの型を限定しない(Atomでなくてもいい)
  • 順序構造がない

Dict

Elixirでは、Keyword listとMapはいずれもDictionaryとして分類されます。 Keyword listとMapは一見rubyのHashのようですが、rubyのHashのように後から値を追加するために、[]を利用することは出来ません。 新たに値を追加するには、次のように、Dict.put/3を利用します。

iex> keyword = []
[]
iex> map = %{}
%{}
iex> Dict.put(keyword, :a, 1)
[a: 1]
iex> Dict.put(map, :a, 1)
%{a: 1}

Enumerable

ElixirではEnumerableがサポートされていて、ListとMapがEnumerableに該当します。

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]

Enumモジュールは、Enumerableの要素を変形・ソート・グループ化・フィルター・取得するための多数の関数を提供していて、Elixirで書かれたコードでよく登場するモジュールです。 Enumモジュールは多数のデータ型をサポートする設計で作られているため、そのAPIはデータ型に依存しない関数に限られています。 より具体的な操作のためには、そのデータ型に対応したモジュールを使用しないといけない場合もあります。例えば、Listの中の特定の位置に要素を挿入したい場合、 Listモジュールで定義されたList.insert_at/3関数を使用します。

パイプ演算子

上記の例の|>記号はパイプ演算を表しています。この演算では、Unix|演算子のように左辺を評価した後、右辺の第一引数として渡します。 パイプ演算子は複数の関数をまたがるデータの流れをわかりやすく記述するために用いられます。

iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000

パイプを使うと上のような煩雑な処理を次のように書き換えることが出来ます。

iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000

ここでEnumStreamという2つのモジュールが新たに出てきます。 その違いは、EnumはEagerに演算を行い、StreamはLazyな演算を行うことが出来ます。 例えば上の例では、StreamEnumモジュールに渡された時に演算が初めて実行されることになります。 Lazyに評価する事の利点は、例えばStreamを実際に演算しなくていい条件がある場合、その演算に利用する巨大なオブジェクトをはじめから保持する必要が無いことにあります。

Generator

ElixirではEnumerableに対してループ処理をしたり、別のListにMapするといった処理を頻繁に使います。 例えば、IntegerのListの各要素を2乗したMapは以下のように書くことができます。

iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]

n <- [1, 2, 3, 4]の部分をGeneratorと呼びます。 Generatorの右辺には、Enumerbleを指定することができます。 また、Generatorの左辺にパターンマッチを使うこともできます。 例えば、キーが:good:badのいずれかであるKeyword Listに対して、:goodの値のみの2乗を計算したい場合、以下のようにします。

iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]

まとめ

ElixirのEnumerableについて学ぶことが出来ました。 より詳しく知りたい方は、以下の公式ドキュメントからよりカッコイイ書き方を知ることが出来ます。(外部サイトへ飛びます。)

Rails4.2のコネクションプールの実装を理解する

$
0
0

tl;dr

Railsではコネクションプール数を設定していても、1スレッド辺り1コネクションしか持ちません。

発端

アカツキではRails + Unicorn + Nginx + MySQLの構成をAWSで運用しており、c3.4xlargeインスタンス上で1台辺り64のUnicornワーカープロセスが実行される設定になっています。

ソーシャルゲームでは時にたくさんのアプリケーションサーバを並列稼働される必要がでてきます。特に年末年始の時期は平時の2-3倍のトラフィックが予想され、アプリケーションサーバを最大100台で稼働させる必要がありました。

Railsのdatabase.ymlのpool設定は5だったので、単純に考えると最大 100台 * 64プロセス * 5接続 = 32,000個の接続が常時貼られるのでは?MySQLmax_connectionsの設定は大丈夫か?という議論があり、Railsのコネクションプールの実装をきちんと理解すべき!ということで、調査しました。

動きの確認

まずは実行してみます。

  • Unicornのworker_processesを2に、database.ymlのpoolを5にして動作させてみる -> コネクション数:2
  • Unicornのworker_processesを2に、database.ymlのpoolを1にして動作させてみる -> コネクション数:2
  • Unicornのworker_processesを4に、database.ymlのpoolを1にして動作させてみる -> コネクション数:4

どうやらpoolの設定にかかわらず、ワーカープロセスの数 = コネクション数となるようです。

ドキュメントの確認

さて、Railsのドキュメントといえば、RailsGuideです。 https://github.com/yasslab/railsguides.jpをクローンし、

git grep --name-only プール guides/source

を実行すると、以下がヒットします。

guides/source/ja/configuring.md
guides/source/ja/rails_on_rack.md

rails_on_rackActiveRecord::ConnectionAdapters::ConnectionManagementがコネクションプールを管理していることしか分かりません。 configuringを読むと、以下の記載があります。

Active Recordのデータベース接続はActiveRecord::ConnectionAdapters::ConnectionPoolによって管理されます。これは、接続数に限りのあるデータベース接続にアクセスする際のスレッド数と接続プールが同期するようにするものです。最大接続数はデフォルトで5ですが、database.ymlでカスタマイズ可能です。...snip... 接続プールはデフォルトではActive Recordで取り扱われるため、アプリケーションサーバーの動作は、ThinやmongrelUnicornなどどれであっても同じ振る舞いになります。最初はデータベース接続のプールは空で、必要に応じて追加接続が作成され、接続プールの上限に達するまで接続が追加されます。

これだけを読むと、database.ymlで設定された分のコネクションプールが「必要に応じて」確保され、各スレッドはそのコネクションプールを使う動きをしているようです。 実際の動きと、設定された分のコネクションプールが確保されるというドキュメントの記載内容に、違和感を感じます。 ここでの「必要に応じて」とは、どういう意味でしょうか?曖昧なので、より深く実装を確認する必要がありそうです。

実装の確認

API Documentation

ソースコードを読む前に、APIドキュメントを確認してみましょう。 RailsGuideには、ActiveRecord::ConnectionAdapters::ConnectionPoolによって管理されるとあるので、その部分をみてみます。

ActiveRecord::ConnectionAdapters::ConnectionPool

A connection pool synchronizes thread access to a limited number of database connections. The basic idea is that each thread checks out a database connection from the pool, uses that connection, and checks the connection back in. ConnectionPool is completely thread-safe, and will ensure that a connection cannot be used by two threads at the same time, as long as ConnectionPool's contract is correctly followed. It will also handle cases in which there are more threads than connections: if all connections have been checked out, and a thread tries to checkout a connection anyway, then ConnectionPool will wait until some other thread has checked in a connection.

Introductionを読むと、コネクションプールの実装は完全にスレッドセーフであり、各スレッドはコネクションプールから接続をチェックアウトして使う、という実装のようです。 スレッドセーフということは「ひとつの接続が、同タイミングで複数のスレッドにより使われることはない」ということなので、ワーカープロセスの数 = コネクション数となるのは分かる気がします。 しかし、リクエストのたびに接続をし、コネクションプールの最大設定値までプールする様に実装されているならば、その限りではありません。 Introduction以下を読み進めても、直接答えに結びつくような記載は見つかりません。ここまできたら、ソースコードを読むほうが早いでしょう。

ActiveRecord::Base

接続に関する内容については、sonotsさんが書かれた

や、kotaroitoさんが書かれた

等の記事を見たほうが早いかもしれませんが、ここでも読み進めていきます。

ドキュメント

ActiveRecord::Base APIドキュメントの、"Connection to multiple databases in different models"の項には、このように書かれています。

Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.

ということで、ActiveRecord::Base.establish_connectionが接続情報の作成、ActiveRecord::Base.connectionが接続の処理として見ていきます。

接続情報の作成

ActiveRecord::ConnectionHandling

以下、ActiveRecord::Base.establish_connectionの実装を見ると、connection_handler.establish_connectionを実行しています。

def establish_connection(spec = nil)
  spec ||= DEFAULT_ENV.call.to_sym
  resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
  spec = resolver.spec(spec)

  unless respond_to?(spec.adapter_method)
    raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
  end

  remove_connection
  connection_handler.establish_connection self, spec
end

connection_handlerは以下、ActiveRecord::Coreで実装されており、ConnectionAdapters::ConnectionHandlerのインスタンスをキャッシュしています。

def self.connection_handler
  ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
end

def self.connection_handler=(handler)
  ActiveRecord::RuntimeRegistry.connection_handler = handler
end

self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new

※ ちなみに、ActiveRecord::RuntimeRegistoryActiveSupport::PerThreadRegistryモジュールを利用しており、これはスレッドローカル(スレッドセーフなグローバル変数)値を安全にキャッシュするための機能です。クラス名をキーに含めることで、同じキーを別々のクラスで定義していても大丈夫な設計になっています。

ActiveRecord::ConnectionAdapters::ConnectionHandler#establish_connection

ConnectionAdapters::ConnectionHandler#establish_connectionの実装を見ると、以下の行でコネクションプールを生成しています。

owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)

owner_to_pool[owner.name]owner_to_poolThreadSafe::Cacheownerは実行元のモデルクラスなので、実行元モデル名ごとにConnectionAdapters::ConnectionPoolのインスタンスをキャッシュしています。 実装の先を追ってみましょう。

ActiveRecord::ConnectionAdapters::ConnectionPool

大枠を捉えながら、ConnectionPool内の処理を見てみます。

ActiveRecord::ConnectionAdapters::ConnectionPool::Queue

コネクションを格納するFIFOキューです。

ActiveRecord::ConnectionAdapters::ConnectionPool::Reaper

database.ymlファイルのreaping_frequencyに設定された秒数ごとに、reapを実行しています。

ActiveRecord::ConnectionAdapters::ConnectionPool#initialize

ConnectionPool.newされるときに実行される、initializeメソッドです。

def initialize(spec)
  super()

  @spec = spec

  @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
  @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
  @reaper.run

  # default max pool size to 5
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5

  # The cache of reserved connections mapped to threads
  @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)

  @connections = []
  @automatic_reconnect = true

  @available = Queue.new self
end

ここで分かることは、以下のとおりです。

  1. Reaperの(別スレッドでの)実行を開始している
  2. スレッドセーフなKey:Valueキャッシュである@reserved_connectionsを作っている
  3. コネクションのキューを作っている
  4. 設定にしたがって接続情報の初期化をしているだけで、実際に接続はされていない

ここではドキュメントの記載どおり実際の接続は行わず、接続の管理情報を初期化しているだけなので、特に気にすることは無さそうです。

接続の処理

ActiveRecord::ConnectionAdapters::ConnectionPool#connection

実際に接続されるときに実行される、connectionメソッドです。

def connection
  # this is correctly done double-checked locking
  # (ThreadSafe::Cache's lookups have volatile semantics)
  @reserved_connections[current_connection_id] || synchronize do
    @reserved_connections[current_connection_id] ||= checkout
  end
end
  • @reserved_connections[current_connection_id]にすでにキャッシュされていればその情報を返します
  • そうでなければsynchronize(mutexロック)した上で、checkoutにより貼られたDBとの接続情報を@reserved_connections[current_connection_id]に格納しています。

ここで着目するのは、@reserved_connectionsのキーに、current_connection_idを使っていることです。 current_connection_idの実装は以下のとおりです。

def current_connection_id #:nodoc:
  Base.connection_id ||= Thread.current.object_id
end

Thread.current.object_idを使っていますobject_idはスレッドを一意に識別するIDなので、同スレッド上で何度connectionを実行しようとも、同じ接続が使われます。

ここではじめて、1スレッド辺り1コネクションとなる動きが実装レベルで理解できました!

まとめ

  • ActiveRecord::Base.establish_connectionで接続管理情報が作成され、ActiveRecord::Base.connectionで接続が行われます
  • ActiveRecord::ConnectionAdapters::ConnectionHandler#establish_connectionではコネクションプールのインスタンスをモデル名ごとにキャッシュしていますが、これはそれほど重要ではありません
  • 実際の接続はActiveRecord::ConnectionAdapters::ConnectionPoolが持つ@reserved_connectionsにより、スレッドID単位にキャッシュされています
  • Railsではコネクションプール数を設定していても、1スレッドあたり1コネクションしか使いません。つまり、シングルスレッドのUnicornでは、1ワーカープロセス = 1コネクションとなります。

あとがき

Rails4.2のコネクションプールに関するソースコードを、APIドキュメントをきっかけにして追ってみました。 使っているフレームワークの動作を実装レベルで知ることは、未来の自分が書くソースコードの品質向上に直結すると思いますし、なにより様々な実装のアイデアを知ることは楽しいです。

そして、2016/01/16に Rails v5.0.0.beta1.1 が公開されましたね。 Rails5では @thedarkone さんのコミットによりコネクションプールの実装が大きく変わっており、Biasable queueなどのアイデアが追加され、コメントも増えて分かりやすくなりました。 この記事を見て、ちょっと興味が出てきた人はConnectionPoolのコミットログを読んでみると、面白い発見があるかもしれません。

Enjoy code reading!

5分で分かるRedis Clusterの構築方法

$
0
0

はじめに

Developpers Summit 2016で「大規模Redisサーバ縮小化の戦い」というテーマで発表してきました。

Redisのdumpファイルを取得して、それらをマージする方法や、Redis内で使用するdb数を増やせば、接続数も増えていく、といった話をしました。 特にAWS上でRedisを運用する場合、ElastiCacheの接続数上限は変更できないことは見落としがちなポイントなので、サーバを何十台もスケールアウトする人たちにとって役に立つノウハウが共有できたのではないでしょうか。

当日はネタスライドを山程仕込んで 会場は大爆笑だったのですがslideshareではネタスライドは割愛しております。

今回のお話

デブサミのスライドでは、ほとんどの話が縮小についての話だったので、今回はRedisの信頼性について考えてみたいと思います。

元々、Redisの縮小計画は

の2つの目的がありました。

サーバ台数を64台から8台に縮小して、その後、冗長構成(16台)にする計画を立てていました。64台の状態でも冗長化はできたのですが、冗長構成で128台運用はいくらなんでもコスト的に厳しかったため、泣く泣くシングル構成を許容して64台運用をしていました。これらを縮小化することで、冗長化に踏み出せました。

Redisの信頼性向上施策といえば

さて、Redisの信頼性向上施策と言えば、以下の設定が思い付きます。

マルチAZ

AWSのElastiCacheでRedisを運用していると、一番最初に思いつく信頼性向上の施策はマルチAZではないでしょうか。 version2.8.6以降でマルチAZにすることで、プライマリノードの自動フェイルオーバーという恩恵が受けられます。今回、Redisを縮小できたおかげで、我々も各RedisノードにマルチAZでスレーブを構築しました。

AOF(Append-Only File)

こちらも信頼性向上の施策の一つで、キャッシュデータを変更するすべてのコマンドを AOFファイルに書き込みます。DBの更新ログみたいなものです。こちらは自サーバ内に書き込みを行うため、物理障害に対応できません。

Redis Cluster

Redisは2015/4にver3がリリースされて、クラスタリングをネイティブでサポートしております。もうすぐリリースされて1年が経過するところですが、AWSのElastiCacheはRedisのver3以降がまだ対応していないため、我々のシステムでもまだ導入しておりません。とは言え、そろそろAWSでRedisのver3が対応してもおかしくはありません。

ちょうどいい機会だったので、クラスタリングについて触ってみました。

ドキュメントの確認

Redis ClusterのドキュメントはRedis Cluster Specificationです。 このドキュメントの中で最も試してみたい機能が Replica Migrationです。ドキュメントを一部抜粋すると、以下のような記載があります。

Replica Migration

  • A, B, Cのmasterに、A1, B1, C1, C2というslaveが構築されている状態(Cだけslaveが2台)があるとします。
  • Aが死ぬと、A1がmasterに昇格し、C2がA1のslaveとして移動します。
  • その後、またしてもA1が死にます。
  • C2がA1に代わってmasterに昇格し、クラスタは継続された状態のまま運用できます。

なんと、惚れるじゃありませんか!せっかくなので試してみましょう。

試してみた

Redis cluster tutorialを参考に、クラスタを構築してみました。 EC2上で7つのRedisを起動し、各portを7000〜7006で設定しております。 これらの7ノードでclusterを組み、7000, 7001, 7002をmaster, それ以外をslaveとしております。

Redis起動

redisのversionは以下のとおりです。

$ redis-server -v
Redis server v=3.0.7 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=5ece30e075eed8d2
$ redis-cli -v
redis-cli 3.0.7

ディレクトリを作成して

$ mkdir cluster-test
$ cd cluster-test
$ mkdir 7000 7001 7002 7003 7004 7005 7006

ディレクトリに/etc/redis.confをコピーしてきて、以下の設定に変更します。

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
loglevel debug
logfile /home/ec2-user/cluster-test/7000/redis.log
cluster-config-file /home/ec2-user/cluster-test/7000/nodes-6379.conf

各redisを起動し、redis.logにエラーが出力されていないことを確認します。

$ sudo redis-server ./7000/redis.conf
$ sudo redis-server ./7001/redis.conf
$ sudo redis-server ./7002/redis.conf
$ sudo redis-server ./7003/redis.conf
$ sudo redis-server ./7004/redis.conf
$ sudo redis-server ./7005/redis.conf
$ sudo redis-server ./7006/redis.conf
$ ps aux | grep redis | grep -v grep
root 23180 0.0 0.4 142808 4312 ? Ssl 11:20 0:02 redis-server 127.0.0.1:7000 [cluster]
root 23189 0.0 0.4 142808 4308 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7001 [cluster]
root 23197 0.0 0.4 142808 4228 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7002 [cluster]
root 23202 0.0 0.4 142808 4184 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7003 [cluster]
root 23207 0.0 0.4 142808 4256 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7004 [cluster]
root 23212 0.0 0.4 142808 4148 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7005 [cluster]
root 23217 0.0 0.4 142808 4160 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7006 [cluster]
$

起動できました。

上記の設定でRedisを起動すると、nodes-6379.confが出力されるので、中身を確認すると、port7000〜7006の全てのRedisがmasterとして認識されております。

$ cat 7000/nodes-6379.conf
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
$

この時点ではクラスタ設定がONになっているだけで、各ノードはお互いを認識していません。

クラスタ構築

redis-tribを使用してクラスタを作成します。

$ ./redis-trib.rb create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002
>>> Creating cluster
>>> Performing hash slots allocation on 3 nodes...
Using 3 masters:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

これでクラスタが組まれ、以下の状態になりました。

$ cat 7000/nodes-6379.conf
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master - 1458646033493 1458646032693 3 connected 10923-16383
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458646032693 2 connected 5461-10922
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
vars currentEpoch 3 lastVoteEpoch 0

logを見てみると、クラスタの各ノードに生存確認の通信が頻繁に走っていることが分かります。

$ tail -f 7001/redis.log | grep Ping
23189:M 22 Mar 11:29:29.814 . Ping packet received: (nil)
23189:M 22 Mar 11:29:30.117 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:30.821 . Ping packet received: (nil)
23189:M 22 Mar 11:29:31.119 . Pinging node 6082772a27ae6465b9f3d26959e1de5991b41356
23189:M 22 Mar 11:29:31.818 . Ping packet received: (nil)
23189:M 22 Mar 11:29:32.120 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:32.825 . Ping packet received: (nil)
23189:M 22 Mar 11:29:33.123 . Pinging node 6082772a27ae6465b9f3d26959e1de5991b41356
23189:M 22 Mar 11:29:33.825 . Ping packet received: (nil)
23189:M 22 Mar 11:29:34.125 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:34.828 . Ping packet received: (nil)
23189:M 22 Mar 11:29:35.128 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf

slave構築

slaveもclusterに組み込みます。

$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7003 127.0.0.1:7000
>>> Adding node 127.0.0.1:7003 to cluster 127.0.0.1:7000
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
0 additional replica(s)
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
0 additional replica(s)
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:7003 to make it join the cluster.
Waiting for the cluster to join.
>>> Configure node as replica of 127.0.0.1:7000.
[OK] New node added correctly.

同じように設定していきます。

$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7003 127.0.0.1:7000
$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7004 127.0.0.1:7000
$ ./redis-trib.rb add-node --slave --master-id eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7005 127.0.0.1:7001
$ ./redis-trib.rb add-node --slave --master-id 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7006 127.0.0.1:7002

これで想定どおりのクラスタが組めました。

クラスタの状態を確認すると、以下のように見えます。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 slave 6082772a27ae6465b9f3d26959e1de5991b41356 0 1458647137056 3 connected
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458647136055 1 connected
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458647141066 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458647139061 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458647140064 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master - 0 1458647138058 3 connected 10923-16383
$
$ redis-cli -p 7000 cluster slots
1) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7003
   5) 1) "127.0.0.1"
      2) (integer) 7004
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7005
3) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7002
   4) 1) "127.0.0.1"
      2) (integer) 7006

壊してみる

やっと準備が整いました。Replica Migrationの機能を確認したいので、まず、7002のnodeをkillしてみます。

$ ps aux | grep redis | grep -v grep
root 23180 0.0 0.4 142808 4312 ? Ssl 11:20 0:02 redis-server 127.0.0.1:7000 [cluster]
root 23189 0.0 0.4 142808 4308 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7001 [cluster]
root 23197 0.0 0.4 142808 4228 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7002 [cluster]
root 23202 0.0 0.4 142808 4184 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7003 [cluster]
root 23207 0.0 0.4 142808 4256 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7004 [cluster]
root 23212 0.0 0.4 142808 4148 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7005 [cluster]
root 23217 0.0 0.4 142808 4160 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7006 [cluster]
$
$ sudo kill 23197
$

さて、クラスタの状態を確認してみると、7006が7002の代わりにmasterになっており、かつ、7003が7006のslaveになっていることが分かります。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 master - 0 1458648558314 4 connected 10923-16383
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 slave 004e547e24fbdede970cd95a8801fdef6c51ee3c 0 1458648555305 4 connected
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458648557311 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458648554305 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458648559316 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master,fail - 1458648522304 1458648519195 3 disconnected

すなわち、こういう状態になりました。

今度は、7006をkillしてみます。

$ sudo kill 23217

クラスタの状態を確認してみると、7003が代わりにmasterになっていることが分かります。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 master,fail - 1458648779623 1458648779022 4 disconnected
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 master - 0 1458648899466 5 connected 10923-16383
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458648896455 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458648894445 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458648898463 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master,fail - 1458648522304 1458648519195 3 disconnected
$
$ redis-cli -p 7000 cluster slots
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7003
2) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7004
3) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7005

想定通りの挙動をしていますね。

詳細なアルゴリズムはドキュメントに記載してあるため、興味がある方は一度見てみるのはいかがでしょうか。

所感

クラスタ再構成中のデータ更新は当然データ消失の可能性がありますし、今回のようなほとんどデータがない状態でもクラスタ再構成に数秒かかったため、実データサイズでの時間計測はやっておきたいところです。とは言え、システムの信頼性向上にとっては素晴らしい機能だと思います。

他にも、resharding機能など、ver3では、スケールアウトの仕組みなども整っており、試したい機能がいっぱいです!AWSがRedis ver3に早く対応することに期待ですね!


ドリコムさんと社会人交換留学をしました

$
0
0

こんにちは、初めまして。 最近ようやく花粉症も治まってきたエンジニアのシモムラです。 2016年3月にアカツキドリコムさんの間で社会人交換留学という取り組みを行いましたので、その内容を紹介します。

社会人交換留学について

社会人交換留学とは 、会社同士で社員を交換しあうという取り組みです。目的は会社同士の文化交流、そこから生まれる「気づき」をシェアすることにあります。 社員になると他社の開発現場を生で見る機会は殆どないので、参加者自身にとっても貴重な経験です。

ご存知の方もいると思いますが、社会人交換留学は去年ドリコムさんとピクシブさんの間で行われています。 その当時の記事については以下の通りです。これを読むと、大体の雰囲気はつかめるのではないでしょうか。

この時はサーバーサイドエンジニア(Ruby on Rails)でしたが、今回はクライアントサイドエンジニア(C++/Cocos2d-xとC#/Unity)で実施しました。使っている技術要素が違うため成果が残しづらいことも考えられましたが、それはお互いの文化を学ぶことや成長につなげることとは別であるとしてやってみることにしました。 アカツキから留学に行ったのが私、ドリコムから留学にいらっしゃったのがイシカワさんです。 イシカワさんはドリコム勤務6年ほどのベテランエンジニアで、物腰も柔らかなとても接しやすい方でした。

今回のきっかけ

交換留学のきっかけは2015年6月に行われたIVS CTO Nightで、ドリコム白石さんと弊社CTOの田中が知りあったのがきっかけでした。その後、去年の11月にドリコムのオフィスで「エンジニアリングあるある勉強会」という合同勉強会を開催したりして、お互いのエンジニアメンバーのつながりを強くしていきました。 そこからお互いのメンバー選定、協力会社への調整、NDA締結を行い、留学を実施することとなりました。

留学の準備

さて、留学期間はわずか5日間と非常に短期間です。無駄にできる時間はありません。 しかし、いきなり仕事に取り掛かれるとは限りません。ベテランエンジニア同士で実施した過去の交換留学でも、初日は環境構築に費やしてしまったそうです。 今回はその反省を踏まえ、本留学開始の前の週に準備日を設けました。準備日には顔合わせ・環境構築・その他留学中のメニューなどのすり合わせなどを行います。 こうした準備を経て、2月22日にアカツキからドリコムへ、3月14日にドリコムからアカツキへの留学を実施しました。

留学中にやったこと

留学中はチームメンバーとして実際の業務を行います。今回の留学ではBisqueチームと呼ばれる社内共通基盤の開発チームに参加させていただきました。Bisqueチームはドリコムでクライアントアプリケーションの開発に利用しているCocos2d-xの拡張や、それをベースとした内製フレームワークの開発を行っています。 今回私は持ち込みのネタも無かったため、ここBisqueチームから提案のあったデバッグ用の機能拡張を実装することにしました。

またBisqueチームではUpstream開発に取り組んでいます。Upstream開発というのは、オープンソースソフトウェアに対して企業が行ったソースコード修正を元のオープンソースプロジェクトに還流させるプロセスを言います。 メリットとしては還元されたコードのメンテナンスに手間がかからなくなること、また独自の派生を減らすことでバージョンアップが容易になることが挙げられます。 一方デメリットとしては要望が通るとは限らない、通るとしても時間がかかる、通るまでの"つなぎ"の管理の必要が生じるといったことが挙げられます。ここは基盤開発の経験を活かしてうまく解決しているそうです。 せっかくですので私の実装もUpstream開発の一環として本家へPullRequestを送りました。

PullRequestを出したのはCocos2d-xに搭載されたリモートコンソール機能の拡張です。このコンソールに、アプリケーションの状態をモニタリングして割り込み停止をかける"trap"というコマンドを追加しました。 trapコマンドはデバッガーの方がパフォーマンスのチェックをする際に利用することを想定しています。 ほんの些細な内容ではありますが、これマージされて誰かに利用してもらえたら嬉しいです。

また前回の交換留学では働く方々へのインタビューが有意義だったそうなので、今回もドリコムの各部署のメンバーを対象としたインタビューをさせていただきました。 あまり堅苦しい形にはしたくなかったのでインタビューの一部はランチと一緒に行うよう一工夫しました。実際カジュアルな雰囲気で情報交換ができたと思います。 加えて前回の反省を活かして事前にランチのアポを取っておいた事も功を奏し、多忙なリーダーの方々ともご一緒していただけました。

他にはドリコムの子会社であるグリモアさんを訪問させていただきました。 グリモアは少数精鋭でのゲーム制作を信条としています。ここで伺った話はとても興味深いものばかりでした。 そのひとつが、昨今のゲーム実況配信の盛況を踏まえ、自分たちで色々なゲームの実況配信を実際にやってみるという取り組みについてです。実況し易いゲームや実況を見て面白いゲームとはどんなものなのか研究し、その成果を実際のプロダクト開発に活かしているそうです。

アカツキでの受け入れについてはイシカワさんのエントリで詳しく紹介されています。後述の問題を除いては概ね予定通り進められました。 留学の日程がちょうど上場の記念日と被るなど、一つの会社の節目を体験していただけたと思います。

ドリコムスタイル

留学を無事終えて気づいたドリコムの文化が以下の2つです。

前提知識の均一化

ドリコムではアカツキと比べても非常に多くのクライアントサイドエンジニアが働いていますが、そのエンジニア間のコミュニケーションコストは非常に低く抑えられているようです。 これは前述のとおり基盤技術を全社的に統一している、つまり多くのプロジェクトが同じ内製フレームワークを利用していることによる恩恵です。あるプロジェクトで得たノウハウは、チームの壁を超え素早く共有することができます。 Bisqueチームが主催となっている、アップデートの告知や問題のヒアリングなど行う共有会もこれに貢献しているようです。 さらに、チーム間での人員交換にかかるコストが低いため、適切なタイミングで適切なリソースの再配分が可能になります。

資産を全社に還元するシステム

ドリコムで徹底的に共有がされているのは実際のコードについても同様です。RubyでいうGemのように、CocoaPodsを用いたクライアントコードのライブラリ化が非常に活発です。 この取り組みを支える柱となっているのがクライアントアーキテクトグループ(略称:クライアントアーキG)。クライアントアーキGは各プロダクトチームの成果を全社に還元する役目を担います。 クライアントアーキGはBisqueチームのように独立して開発を行うのではなく、メンバーが各プロジェクトの開発に実際に参加してライブラリ化のサポートを行います。 現場に密着した彼らの働きによって、便利なモジュールの共有と整備がなされています。特に実運用で使われているライブラリは信頼性も高く、再利用率が非常に高いそうです。

ここでいう共有・共通化は上からの押し付けではなく、個別のプロダクトチームの成果を現場から全体に還元するというのがポイントです。 アカツキでは「チームごとに最適な方法を探索」という考え方があり、プロジェクト単位で自由な技術採択を推進しています。そうすると、技術的なチャレンジがし易い反面、プロジェクトを超えてノウハウや資産を共有することが難しいとも感じています。 クライアントアーキGのように個別のプロダクトチームが主体となる資産化の試みは、今後アカツキでも試してみる価値があると思いました。

反省点

今回の交換留学での反省点も共有したいと思います。

イシカワさんを受け入れた際のアカツキの状況

今回の交換留学では、受け入れ先のチームと同じフロアで作業することが出来ないというトラブルが発生しました。 企画から留学開始まで3ヶ月ほど期間が空いたため、受け入れ予定チームの周りに新しい機密プロジェクトが配置されたことが原因です。このため当該フロアへの立ち入りを制限することとなり、やむをえずチームの島から離れた場所に席を構える事になりました。 防ぎようのない不測の事態だったのですが、次は企画からできるだけ早く留学を始めることでリスクを減らすことができるかもしれません。

クライアントサイドのエンジニアを交換する難しさ

当初の予想通りクライアントサイドの作業では短期間で成果を出すのが難しく、両者とも無難なデバッグ機能の実装でひとまず着地することになりました。クライアントアプリケーションではエンジニアだけで完結する作業が少ないことや、直接ユーザーの目に触れることから品質の担保が必要になることが理由として挙げられます。 他に出来る事があるとすればツール開発などによるワークフローの改善などが有効かもしれません。ヒアリングの過程でワークフローを把握できれば開発の文化にも触れられて一石二鳥です。 次回チャンスがあればそのあたりをチャレンジしてみるか、もう少し長い時間で取り組めるよう考えていきたいと思います。

最後に

社会人交換留学は前例のある取り組みではありますが、同業種の他社にインターンシップに行くような経験は当然なく当初は不安感もありました。加えて前例では優秀なエンジニアが留学を行っていたのも強いプレッシャーでした。 そのような気持ちで留学が始まったので、現場でイシカワさんをはじめドリコムの皆様に快く受け入れ・サポートをしていただけて本当に嬉しかったです。 またこの交換留学は現場で会った方々以外にも、様々な方々の尽力によって実施することができました。この企画に携わっていただいた全ての皆様に感謝いたします。ありがとうございました。

スクラムトレーニングで自律型チームを実現する

$
0
0

はじめに

アカツキでは開発にスクラムの要素を取り入れています。しかし、現状の開発のやり方に漠然とした不安がありました。その不安とは、例えば、もっと効率的になるんじゃないか、もっと良いやり方があるんじゃないか、このやり方はスケールしないのではないか、等といったことです。そこで第三者の目が必要だろうと、ryuzeeさんにアドバイスしていただくことになりました。

メンバーヒアリングからスクラムトレーニング実施へ

まず現状をryuzeeさんに知っていただく必要がありました。そこで、アカツキの全メンバーを対象に、開発上困っていることを何でも相談出来る、アジャイル開発お悩み相談会を実施しました。また、一つのプロジェクトの振り返り(KPT: Keep Problem Try)にも参加していただき、観察していただきました。様々なプロジェクトから約20名ぐらい個別相談させていただいたり、KPTに参加していただいたたりした結果、以下の課題が浮き彫りになりました。

  1. スクラムとは何かをほとんどのチームメンバーが理解しきれていない
  2. スクラムのプラクティスをなぜ実施するかを、スクラムマスターが説明しきれていない
  3. スクラムマスターとプロダクトオーナーが明確でない

上記を解決するためには、例えば、スクラムを理解した人が社内で教えてまわるといった方法があげられます。しかし、現状はその工数を中々捻出出来ないといった課題がありました。そこで、スクラムトレーニングを一つのプロジェクトで実施し、その成功体験・プラクティスを横展開していくのが良いのではないかと考え、ryuzeeさんにスクラムトレーニングを実施していただきました。 scrum_desk

スクラムトレーニングでやったこと

スクラムトレーニングでやった内容そのものは非常にシンプルなものでしたが、約3時間かけて以下の実習をじっくり行いました。トレーニング中は常時明るい雰囲気で、スクラムに関する質問が飛び交うなど、参加者全員が楽しんでいる様子だったのが印象的でした。

スクラム体験1(紙飛行機製作)

これは紙飛行機の製作を通じてスクラムの本質を理解しようという試みです。以下のように進めていきました。

  • 5~6人のチームに別れる
  • あるルールにそった紙飛行機をチーム対抗で製作
  • 紙飛行機の製作工程をスプリントに見立てる(プランニング->紙飛行機製作->振り返り)
  • スプリントを4回実施する
  • 紙飛行機製作を通じた振り返り

結果は、こちらの写真の通りですが、予測/結果が合っているチームがあれば、全然異なった結果になっているチームもあり、なかなか面白いですね。 score_boardscrum_paper_plane

スクラム基礎の座学

これはスクラムの基礎知識を学ぶ講義でした。大体のことはこちらのスクラムガイドを参考にしてください。

スクラム体験2(ボールタッチゲーム)

これは最後少し余った時間で実施しました。やったことは、参加者全員(約20名)の名前の順に、ボールを出来るだけ早く触れるアクティビティです。これもスクラムトレーニングなので、計画->実行->振り返りを1スプリントとして、3回実施しました。最初は5分かかったのですが、3回目の挑戦では3秒という短い時間でこのミッションを達成出来ました。これを読んでいる方はどうやったか想像出来るでしょうか?

スクラムトレーニングを受けたメンバーの気付き・学び

スクラムトレーニングを受けたメンバーに気付き・学びのアンケートを実施しました。そのアンケートの中で、特に多かった声をご紹介します。

暗黙の制約に縛られてはいけない

既に述べた通り、スクラムトレーニングの紙飛行機制作にはルールがありました。このルールを制約と捉えるのか、規約と捉えるのかで大きく違ってくるというエピソードがありました。ルールに沿った紙飛行機の生産量を比較したとき、チームAは10機以上制作出来るのに、チームBは1機しか製作出来ませんでした。この状態が何回か繰り返されたとき、ryuzeeさんが一言「他のチームの制作した紙飛行機を分析してはいけないというルールは無いですよ。」とアドバイスしました。全てのチームに共通していたことですが、チームで1から設計したものでないと紙飛行機製作とは言えない、と暗黙の制約を自分たちに課していたのです。その後、チームBはチームAの紙飛行機を徹底的に分析して、劇的に改善して最終的には7機製作(7倍の効果)していました。 普段の業務をする上でも、暗黙の制約を課していないかを常に考える必要がある、そうでないとイノベーションを起こす機会を失っているかもしれないという大きな気付きがありました。 scrum_wideview

計画・振り返りの重要性

紙飛行機製作のトレーニングで、スプリントを重ねる度にほとんどのチームで製作数が増加するという結果が出ました。具体的には最初は4機だったチームが、4回目のスプリントでは10機作れていました。1スプリントは5分でしたが、20分間ただ作り続けた場合と比較した場合、同じ結果が得られたかというとそれは厳しかったかなと思います。例えば、私のいたチームの場合は、

  1. 最初のスプリントで全員がどう作ればいいかわからない状態でスタートする(1機製作)
  2. 次のスプリントの計画で良い紙飛行機の製作方法を共有する(4機製作)
  3. さらにその次のスプリントで役割を明確にする(11機製作)

と、明らかに5分ごとに改善されていきました。これは、計画・振り返りを何度も繰り返すことで、個人主義的な開発からチーム主義的な開発へとシフトしていったからだと思います。ここから、自己改善のできるチームが目指すチームの姿だよね、という共通認識が出来ました。

スクラムトレーニングを受けてから変わったこと

一言で述べるならば、誰かに管理されて動くのではなく、自主的に動ける自律型チームになりつつあります。結果として、メンバーのプロダクトに対する責任感が高まり、マネージメント工数が劇的に下がりました。ここでは、変わったことを2つ紹介します。

自主的にカンバンを取り入れた

スクラムトレーニングにて、朝会がもしただの進捗報告になっているのであれば、カンバンを取り入れてみるのも一つの手であると、アドバイスをいただきました。後日、カンバンを取り入れようという声がメンバーから上がり、チーム全体でタスクを見える化するようになりました。最初は付箋紙をいちいち作るのは面倒だという声もありましたが、数日続けてみた結果、それまでただの進捗報告だった朝会が、お互いの進捗を確認しあって困っていることを共有する場になりました。

自主的に課題解決の場を設けるようになった

問題が発生した際に、チームで自発的に集まって振り返りや解決方法を考えるようになりました。これまではなんとなく放置されていた問題があったり、チーム全体で実施するKPTでだけ振り返りをしていたので、大きな変化ですね。

まとめ・今後の展望

スクラムトレーニングをメンバーが受講したことで、自律型チームの種はまけたかなと思っております。ただし、これからもより良いチームにするためには、常にチームはどうあるべきかを自分たちで考え、自分たちで良くしていくしか方法は無いと思っています。そのためには、誰かの力を借りずともスクラムトレーニングを自分たちで出来るぐらい、スクラムのことをより深く知る必要があると思っています。

まだまだ改善出来るところがあるということは、伸びしろがあるということなのでワクワクしますね!!

エンジニアチームを幸せにするたった1つの方法

$
0
0
あらゆる人間関係の衝突は、謙虚・尊敬・信頼の欠如によるものだ。 プログラマとして成功するには、最新の言語を覚えたり高速なコードを書いたりするだけではいけない。プログラマは常にチームで仕事をする。自分が思っている以上に、チームは個人の生産性や幸福に直接影響するのである。

これはTeamGeekに載っている、Googleのチームビルディングの考え方です。アカツキでも、この謙虚・尊敬・信頼(HRT)の価値を重んじ、HRTの文化を作ることで、チームの人間関係を円滑にしています。

しかし、エンジニアチームを 幸せにする方法はこれだけでは足りません。 では、エンジニアチームを 幸せにするために必要なことは何でしょうか。

そう、カレーを食べることです。

エンジニアがチームとして働くうえで、謙虚・尊敬・信頼の価値を重んじ、カレーを食べることでチームビルディングの成功につながります。

発端

アカツキでは2週間に1回、エンジニア社員全員であつまるMTGを開催しています。技術的なノウハウや悩みの共有、LTなどを中心に話を進めるのですが、エンジニアMTGについてこんな話が出てきました。

f:id:aktsk_hackers_lab:20161209183321j:plain

「エンジニアMTGは楽しむための場所である」 …なるほど!と思い、私がこんな投稿をしていみたところ、

f:id:aktsk_hackers_lab:20161209182832p:plain

意外にも、みんなとても前向きなリアクションをしてくれました。 すれ違いざまに「こまいさん、カレー作るんすか?」なんて言う人も出てきました。

この日から私の心は カレーを食べたいという気持ちでいっぱいになりました。 いや、正確にはカレーを食べたいという情熱が私の心を掴んで離さなかったのです。 カレーを食べたい。カレーを食べることでみんな幸せになれるのではないか? カレーをみんなと一緒に食べることで、幸せ溢れる世界を創り出したい。

…カレーについてありとあらゆることを考えました。 カレーについて深く考えぬいた私は、自らの枠(サーバーエンジニア)に囚われず、新たな領域(カレー)に挑戦し、かつ大きな成果(美味い)を残すために、目的の遂行・達成「だけ」を純粋に求めるようになったのです。

問題提起

というわけで、次のエンジニアMTGで議題に挙げ、弊社のCTO田中に提案してみました。

f:id:aktsk_hackers_lab:20161209183008j:plain

駒井:あの〜以前Slackで言ったんですけど、カレー食いたいっす(ドキドキ) 田中:あ、いいっすよ

はい、決まりました。 ……なんと軽いのでしょうか。

というわけで、エンジニアMTGの前にカレーを仕込み、MTG後にみんなでカレーを食べることになりました。

この日は、ちょうどドリコムさんと社会人交換留学をしているタイミングだったため、MTGの様子はドリコム石川さんの交換留学の記事にも載っています。

こだわり

アカツキでは、ストレングスファインダーという、自分の強みを分析する有名なツールで社員全員の強みを明確化しています。私の強みの1つは「最上志向」です。最上志向の私は、最高のカレーを作らずにはいられませんでした。

世界で最も美味しいカレーとはいったい何でしょうか。そう、マッサマンカレーです。

だが、美味しいだけでは「最上」とは言えません。アカツキ社員は食箋弁当という砂糖をいっさい使わない健康弁当を週に3回も食べるほど、健康へのこだわりも尋常ではないのです。

f:id:aktsk_hackers_lab:20161209182951p:plain

アカツキらしさを出すカレーにするには、美味しさだけでなく、健康の追求も必要なので、野菜たっぷり、かつ、肉の旨味が凝縮された、エンジニアのための最高のマッサマンカレーを作ることに決めました。

作ってみた

見てください、このあふれんばかりの具材。 「料理」は自然を素材にし、人間の一番原始的な本能を充たしながら、その技術をほとんど芸術的な領域にまで高めます。

分量も、1人2杯は食べれるであろう量です。

f:id:aktsk_hackers_lab:20161209183031j:plain

ミーティング当日

さて、仕込みが終わり、ミーティングの時間になりました。 あとは電気コンロにカレーの鍋を置けば、食べる準備は万端です。 macにサトウのごはんという、とてもシュールな絵が出来上がっています。

f:id:aktsk_hackers_lab:20161209183122j:plain

だがここはミーティング。技術的なことについてまずは本気で議論します。

f:id:aktsk_hackers_lab:20161209183104j:plain

心なしか、 エンジニアの出席率が上がっているではありませんか!!

試食

そして、技術的な議論が一通り終わった後、ついに試食です。 みんながカレーに群がってきました。

そのお味は………

う、うまい…………!!!、、うますぎるっ………!!!

f:id:aktsk_hackers_lab:20161209183047j:plain

部屋中「旨い」という言葉だけが小一時間響きわたっていました。あまりの美味しさにCTO田中が「人生で一番うまいカレーだった」と言っていました。(本当です)

効果

カレーを食べることで、単純にお腹が満たされるだけでなく、コミュニケーションが活発になり、エンジニア同士の交流に繋がったり、モチベーションアップにも繋がるなど、様々な副次的効果がありました。単純にイベントとして面白いですしね。

個人的に最も良かったことは、謙虚・尊敬・信頼(HRT)という、目に見えない価値の促進剤になったことです。このように、参加した全ての人に感謝されました。この感謝の言葉だけで私のお腹はいっぱいです。

f:id:aktsk_hackers_lab:20161209183234j:plainf:id:aktsk_hackers_lab:20161209183241j:plainf:id:aktsk_hackers_lab:20161209183248j:plain

アカツキのビジョンは「感情報酬社会を作ること」。ひとつ、新たに感情の総量がアップした瞬間です。カレーを食べることで、チームへの愛、アカツキへの愛、そしてカレーへの愛がよりいっそう深まったのです。

さいごに

冒頭でTeamGeekの言葉を借りましたが、チームビルディングの上でひとつ足りないものがあります。付け加えるとするならば、こうなるでしょう。

あらゆる人間関係の衝突は、謙虚・尊敬・信頼・カレーの欠如によるものだ。 プログラマとして成功するには、最新の言語を覚えたり高速なコードを書いたりするだけではいけない。プログラマは常にチームで仕事をする。自分が思っている以上に、チームは個人の生産性や幸福に直接影響するのである。

おいしいカレーを食べることで、チームのモチベーションもアップし、チームビルディングがうまくいき、そしてお腹まで満たすことができます。

f:id:aktsk_hackers_lab:20161209183403j:plain

次回やるなら、炊飯器を持ち込んで、MTG前に炊飯ボタンを押して、MTG後に炊きあがった米を食べたいです。MTGでカレーを食べるイベントは定期的に開催したいですね!

Happy Hacking

アカツキを支える大規模ゲーム開発プロセス

$
0
0

この記事は Akatsuki Advent Calendar 2016の6日目です。

はじめに

こんにちは、アカツキでチーム開発マネージメントをしているゆのん(id:yunon_phys)です。 アカツキではアジャイル開発手法の一つであるスクラムを取り入れて、ソーシャルゲームの開発を小さなチームで取り組んできました。近年では、高品質かつ多くの機能を持ったゲームを短いスパンでリリースし続けることが市場として求められてきており、開発が年々大規模化してきています。このため、ユーザー価値を最大化しつつより開発がスケールするように、小規模チームから大規模チーム用に開発プロセスを拡張する必要性が出てきました。そこでこの記事では、アカツキの大規模チーム開発プロセスの取り組みを紹介します。

f:id:yunon_phys:20161205214347j:plain

大規模開発における課題

開発規模が大きくなると、小さな規模で発生しなかった問題がいくつか出てきます。様々な問題のうち、根本原因はコミュニケーションによるものだと仮定し、以下で課題整理をしていきます。

課題1. コミュニケーションコスト増大

まず何よりもコミュニケーションコストが単純に増大するという問題があります。n人がコミュニケーションを十分にとるためには、n(n-1)のコミュニケーションチャネル、つまりn2のオーダーのコミュニケーションチャネルが必要になり、コミュニケーションコストが爆増します。しかしさすがにこの課題を放置してコミュニケーションを個々に任せてやっていると開発が全く進まなくなったり、情報共有の密度が薄まったりするので、情報共有の仕組みを工夫しなければなりません。

課題2. 大人数参加型会議の効率の悪さ

課題1を解決するため、良くある手としては、一斉に情報共有する場として大人数参加型の会議を設置することです。しかし、大人数参加型会議で全員の意見を聞くわけにはいかなかくなるので、どうしても発言するメンバーに偏りが出てしまいます。そうなると会議への当事者意識も薄いメンバーが増えていき、結果として、情報共有に濃淡がうまれてしまいます。(ここでの問題は、会議への当事者意識を持てないメンバーではなく、そういう形式を取らざるをえない状態になっていることです。)

課題3. 開発時間の確保 ->残業

全員参加型の会議を設置してもまだ情報共有が足りないので、じゃあそれを補うように・・・と、さらに会議を設置するようになります。そうすると、貴重な開発時間がどんどん奪われて開発スピードが低下するので、それを補うように残業が増え、どんどん開発の余裕が無くなっていきます。

課題4. コミュニケーションハブがSPOFに

じゃあ会議が増えないでもなんとかなるように・・・と、一部のメンバーにコミュニケーションハブ役になってもらって情報を集約させて、その人を介してコミュニケーションを取るようにしよう、という手を取る場合があります。しかしこれの問題は、ハブ役は常に情報収集・拡散をするため、ハブ役が忙しくなりすぎたり、ハブ役が不在のときに混乱が生まれたりする、ということにあります。ハブ役を設置する場合は、ハブ役が忙しくなりすぎないように、一極集中になりすぎない工夫をしなければならず、ぐぬぬ・・・となります。

f:id:yunon_phys:20161205213702j:plain

アカツキで実施した解決方法

大規模開発において4つの課題をあげましたが、本質的には課題1を何とかすれば全ての問題は解決します。しかし、やりたいことのために人数を減らすわけにもいかないので、アカツキでは、スクラムをベースに組織面の改善と会議体の工夫で解決をはかりました。

職能横断型チームを複数結成

ゲーム開発はクライアントエンジニア、サーバーエンジニア、プランナー、デザイナー、QA、デバッガーなど、多くの職種と密に連携する必要があります。しかし、これらのメンバーを全て1つのチームにするとどうしても人数が多くなりすぎる(課題1)という問題が出てしまうので、エンジニアチーム、プランナーチームのように各職種のコンポーネントチームを作るのが良くある対策かと思います。しかし、このチーム作りの問題は、大きな問題があります。それは、ある機能を開発しようとした際に、仕様共有会のメンバー選定、開発担当選定などの職種間の調整役が必要になり、上記の課題4と同じ状態になってしまうのです。さらに、その調整役がいないと職種間の相談がし辛い状態になってしまい、調整役がどんどん忙しくなり(以下略)

そこで、アカツキではそういった調整役を立てるのではなく、チームで1つの機能の開発を完結出来るように職能横断型チーム(フィーチャーチーム)を複数結成しました。これにより、誰が何をやっている状態かチーム内で完結出来るようになったので、余分なコミュニケーションハブ役を立てる必要が無くなりました。

ただフィーチャーチームのデメリットとしては、職種内の横のつながりのコミュニケーションが薄れ、スキル向上が図りにくくなってしまう危険性があることです。そこで、チーム単位ではなく職種単位で島を作り、席配置をすることで、スペシャリストのスキルを伝搬しやすくしました。また、定期的な職種内の情報共有会を促し、そこで出てきた課題をバックログリファインメントに持ち込み、非機能や開発・運用効率化施策も開発に組み込めるようにプロセスを工夫しました。

スプリントをチーム間で同期

開発サイクルがチーム毎に異なってしまうと、プロダクトオーナー(PO)がチーム毎にプロダクトバックログをチューニングしなければならないため非現実的だと考えました。また、全メンバーに共有すべきことが常にある一方で、全メンバーが集まるのはなかなか高コストなので、出来る限り全メンバーが集まる機会を少なくしたいという思いがありました。

そこで、スプリントをチーム間で同期することで、これらの問題を解消しました。具体的には、スクラムの各セレモニーを以下のようにしました。

スプリントプランニング

全メンバーを招集してスプリントプランニングを実施するようにしました。スプリントプランニングは2パートあります。

まず、情報共有に数分使います。更に、チームにアサインされていない機能がある場合は、ここでどのチームが実施するかその場で決めます。

次に、全体共有を終えたら、チーム毎に集まります。後は通常通りタスク割り振り、見積りをチーム内で実施すればOKです。

スプリントレビュー

他のチームが何をやったかをわかるように、チームメンバー全員が同じ時間、同じ場所で行うようにしました。大規模開発とはやや違うコンテキストですが、プロダクトに関わるメンバーに全員に集まってもらい、そのスプリントのチームの成果を見えるようにもしていました。

スプリントレトロスペクティブ

まずチーム毎に通常通り振り返りを実施します。次に、タイムボックスの最後の15分を使って全チームメンバーが集まり、振り返り内容を共有するようにしました。これにより、他のチームでどういう問題が起きているか、共通に解決出来る問題が無いかをチームメンバー全員がわかるようにしました。

バックログリファインメント

バックログリファインメントだけ、やや込み入った話になるのでメンバーを厳選しました。具体的には、PO、各職種の代表とチーム代表が集まり、非機能や開発・運用効率化施策を開発に組み込めるように話す場としました。また、プロダクト全体に関わる課題の共有や、不具合の修正担当チーム決めなども行うようにしました。

その他

他にも、POは忙しくなりすぎるのでプロダクトバックログの優先度だけジャッジする、チームメンバーは基本固定化する(チーム間を行き来しない)など、取り入れました。

f:id:yunon_phys:20161205214814j:plain

大規模開発プロセスを取り入れてどうだった?

じゃあ、実際に上記の開発プロセスを入れてどうだったのか?というのが気になるところだと思います。

最初は職能横断型チームに対してやはり戸惑いの声が多く、XXXの機能は他チームのAさんがいないと出来ない、YYYの不具合修正に他チームのBさんの力が必要・・・といった不安の声が多くありました。しかし、1ヶ月も続けると、ちょっと手が空いた人が自発的に他の人のサポートに入る、チームランチなどチーム活性化施策を自分たちで行う、カンバンをチームで独自に改良する、など目に見えてチームの結束力が高まり、チーム力が増していきました。

最終的には、上記の開発プロセスを導入した私が出る幕が無くなり、ゲーム開発プロセスのマネージャー不在の組織を実現出来ました。

最後に

実は先日認定LeSS実践者研修に参加したのですが、ほぼほぼLeSSと同じやり方でびっくりしました。もちろん、完全なLeSSではないですし、まだ改良の余地がありますが、後はチームメンバーが自分たちの力でなんとかやっていくことでしょう。

今後もユーザー価値最大化のために、アカツキでは最高のチーム作りをしていきます!

アカツキエンジニア・ロングインタビュー: 湯前慶大 (ディベロップメント・ディレクター / エンジニアリング・マネージャ)

$
0
0

アカツキ応援団、エンジニアリング・アドバイザーの能登です。

アカツキという会社は、そこに集まっている人たちがいちばんの特徴になっている、という僕の最初の印象、4年半前に感じた印象は今でも変わっていない。実際社内の何人かに会ってもらうと「予想以上にいい人たちが集まっていて、いい会社なんだなと思いました」と言ってもらうことが多い。ただ、そういう中にいる人たちの人物像って、どうにも会ってもらうまで伝わらないので、それだとウェブではうまく伝わらないということになってしまう。自分としてはそこにずっと歯がゆさを感じていたんだけど、もしかしたら長めのインタビュー記事を載せることで、そこが解消されるんじゃないか、と思って、自分でインタビューして公開してみることにしました。どれくらい意図通り人物像が伝わるかわからないですが、何事もまずはひとつやってみないとね、と思って。

アカツキエンジニアで一番最初に紹介したいのは誰かなと考えた時、速攻で頭に浮かんだのが今回取り上げた湯前さん (id:yunon_phys) でした。

f:id:notolab:20170205155842j:plain

湯前さんは前職で Linuxカーネルの改善をやっていて、今は社内で最もスクラムに詳しい男であり、かつ CTO や僕と一緒にアカツキのエンジニアチームを良くしていくマネジメントの役割も担っている。エンジニアチームの主役はマネージャでは決してないけど、こういう人がマネージャをやっているからこそ、エンジニアにとって働きやすい環境・チームができている、というのは伝わるんじゃないかなと思ってます。

ということで、以下、聞き手・構成ともに能登が担当してお送りします。

Linuxカーネル改善を仕事にしていたにもかかわらず、アカツキへ転職した背景

―――― それでは、まずアカツキに来る前にどういうお仕事をしていたか、聞かせてください。

前職では大手電機メーカの研究所にいて、主にインフラ事業向けのシステムに使われるサーバの OS の開発をしていました。そのサーバ OS は当初 Linuxを独自改造していたんですけど、そういった OS をメンテナンスし続けるのにけっこうコストがかかっていました。そこで、メンテナンスコストの削減のために、自分たちの変更を Linuxカーネルに取り込んでもらうアップストリーム活動を会社としてすることになりました。自分は正にそのアップストリーム活動を担当していました。

―――― カーネルにオリジナルの変更点を入れる、オープンソースに関わるっていうのは、ソフトウェアエンジニアにとっては大変さも感じるだろうけど、おもしろさややりがいも大きい業務だと思うのですが、なぜそこから転職を考えるようになったんですか?

毎回数年かけて開発して、そこからさらに何年も保守してというのを繰り返していて、実際に僕が退職したのは今から 3 年くらい前なんですけど、その時までやっていた開発対象ってどうもまだ世に出ていないらしいんです。それくらいスパンが長い業務になっていて、自分がやったことに対して素早く世の中からフィードバックがあるかというとそういうものではなかったというのがあります。そういう状況に悶々としていたところに、ドイツのおっちゃんからメールが来て、自分が Linuxカーネルメーリングリストに投げたパッチを取り込んで動かしてみたら、すごくうまくいったよというコメントが返ってきたんです。それがすごくうれしくて、もっと自分がやったことに対して世の中からフィードバックが来るような仕事がいいなと感じて、そういう仕事に変えようかなとふと思ったというのがきっかけです。

―――― そういうフィードバックが素早く来る、という点では、ウェブやスマフォゲーム系の会社なら、2, 3 年かかるということはあまりないので、選択肢は広がると思うけど、その中でもアカツキがいいなと思ったのはどういうところなんですか?

アカツキが他の企業とぜんぜん違うと思ったところがあって、エンジニア文化というのを打ち出していて、自分たちはこういう風に開発をしていくんだというのが『アカツキの開発に対する哲学』に明示されていますよね。あと、これはエンジニアについてだけではないのですが、アカツキは会社としても「こういう会社である」というのをコーポレートページで定義しているところがぜんぜん違っていて、しかもそこに自分自身が共感したというところが大きかったなと思います。普段から自分が考えていた、面倒なことをしないように最高の方法を考えるとか、自分たちでワクワクして働くというものが、そのまま書いてあったというのがすごくマッチしたと感じたところでした。

―――― 面倒なことは自動化するとか、自分たちが楽しまないと仕事っておもしろくないよね、っていうのは、前職の時からそういう方針が自分の中にあったということですか?

そうですね、たまに手動でやっていることもあったんですけど、手動でやっていると疲れてくるというのもあって、ちょっとこれスクリプト書いて自動化するかとか、できる限りやっていました。自動化すると手動でやっていた時より楽だし、手でやってミスする可能性を排除できるので結果クオリティが上がります。他にも個々の実行時間が短くなるので、ならこれを追加してみたらどうかとか、その時どういう挙動になるか確認するとか、10 台いっきにセットアップしちゃおうとか、いろいろ試しやすくなるのも大きいです。そのあたりが自分の経験として良かったので、自動化するとか、システム化するというのがすごく好きなんですよね。

「ワクワクして働く」の方も、自分が楽しいと思っていないとそこに情熱注げないと当時から思っていて、オープンソースの活動自体は自分でも情熱持って働けたので、そこは良かったと思っています。

インフルエンザで PM 不在、混乱したチームが…

―――― アカツキに入ってからけっこういろんな仕事をしてきていると思うんですけど、まずは今担当しているものの、ひとつ前のプロジェクトについて聞かせてください。

ひとつ前は、規模大きめの既存ゲームタイトル運営チームにいました。突然 1 週間くらい前に通達があって、プロジェクトマネージャ (アカツキではプロジェクトリーダがゲームタイトルごとの最終責任者で、プロジェクトマネージャは進捗や生産性、問題解決の部分に責任を持つ役割) として異動してくださいということになったわけですけど、移ってみるとまずチームの状態が思ったより良くなかったんです。隣の人が何をやっているか把握していないというか、「隣の人待ちなんですよね… (チラチラ、と横の人を見る)」みたいな。必要なコミュニケーションがとられていないし、形式的な朝会が行われていて、そちらもあまり意味を見出しにくい内容で。「ここにいる人たちはすごい人たちなのに全然この人たちを活かせてない!」と思いましたし、何より楽しそうじゃないなと気付いて。これは何とかしなきゃということで完全にチームを改善する方向で仕事を始めました。

まずは、明確にチームメンバーへのサポートが必要だなと思ったので、自分が整理する、ちゃんと決めていくというところに全力投入していました。スプリントのタスク内容を定義して、自分が「この人がこれをやる、この人がこれををやる」とタスクのアサインをして、かつ、会議など全部のファシリテーションをして、チーム全体のワークフローがスムーズに流れていくよう、まずは自分が頑張るというアプローチでした。ただ、それだけでは不具合もぜんぜん減らないし、ぎりぎりになってもまだやる、ハードワークでカバーするというところも変わらず、これじゃぜんぜんダメだなっていうのもあり、じゃあどうしたらいいんだろうと。そうやっていろいろ悩んでいた時に、自分がインフルエンザにかかって「1 週間会社に来られません」ってことになって… スプリント期間が 1 週間だったので、チームが大混乱になる未来が見えて、ああ開発が止まるなあ、と。

やっと治って出社したら、驚く光景が広がっていました。なんと、自分がインフルエンザになる前よりもチームがすごく良くなっていたんですよね (笑)。チームのメンバーが、じゃあ自分たちだけでどうすればいいんだろうと試行錯誤したみたいで、自主的にチームに分かれて、ワークショップ形式でワイワイ楽しそうにタスク割り振りをやっていました。このとき、自分がやっていたことは改良にはつながっていたけど、もっといい方向もあったんだということに気づきました。

今までのやり方を変えなければなあと思っていた時に、ちょうど認定スクラムマスター研修を受けることができたんですよね。

―――― 湯前さんのインフルエンザという「事故」みたいなものがあって、自分の思いもよらなかったプラスの方向にチームが変化して、それとスクラムマスターの研修っていうのは、割と並行したものだったんですか?

もともとだいぶ前に予約していたんですよね。まさかインフルエンザになった次の週に重なるなんて、予期してなかったことなんです。

「帰ってきたばかりで恐縮なんですけど、来週からいません」みたいな (笑)

―――― でも、その「自分たちで考えて進める」状況を強める結果になったということですね。

そうです。

―――― 自分がいない 2 週間でのチームの変化を体感して、スクラムマスター研修を期にスクラムを理論的にも学ぶようになったんですか?

元から本で勉強はしていたんですけど、真の意味で理解したのはそのスクラムマスター研修です。本を読んでいても、現実との比較をずっとしていて、こうは書いてあるけど現実では無理だよなと感じながらずっときていました。

スクラムマスターの研修に行った時に、そういう現実とのギャップについて講師にくりかえし質問しました。「実際こういう状況になっているんですけど、こういう場合どうするんですか?」と聞くと、「でもそれはあなたのプロジェクトがそういう状況なだけで、理想はこうだから」としか講師は教えてくれないんですよね。何度もそういう回答が来て、自分が理想の状態に持っていこうと努力していなかったということなんだと気づいて、反省しました。現実はこうだからというところに甘えていて、さらにもっとより良くするにはどうすればいいかというのを自分が考えていなかったという。

―――― 推測するに、インフルエンザで湯前さんがいないから苦肉の策で現場の人たちが実践し始めた「自分たちで考えて、自分たちでやってみよう」という姿勢が、実はスクラムとかアジャイルの世界で目指すべき姿だったということでしょうか?

まさにそうだったんです。

―――― これはすごい偶然ですよね (笑)

この機会は絶対逃してはいけないなと感じたんです。

じゃあ、自分が最初からプロジェクトにいなければそういう状況になったのかというとそうでもなかったような気がしていて、湯前が入る前にはそれでうまくいってなかったというのもあるんで。自分のやり方をまわりの人が見ていたから自分たちでできるようになったという面もあるでしょうし、かつ自分たちで学習するっていう下地がチームにできていたんだと思います。あくまでインフルエンザとスクラム研修は、きっかけにすぎなかったと。

それを期に、自分が仕事を割り振るという役割はやめて、できる限りチームに任せて、チームが困っている時にサポートすることに振り切るようにしました。結果そのチームは自分がいなくても回るという状態になったので、じゃあ湯前いなくなってもいいよね、ということで新しいプロジェクトに希望して移りました。

プログラムを書くか、マネジメントをするか、自問自答し続ける

―――― 湯前さんって、そういうスクラムマスターやルーティンじゃない問題解決に専念する役割がチームに必要なんだなというのを、先ほどの経験を通して学ばれている気がするんですけど、一時期エンジニアとして技術を突き詰めるか、マネジメントをやるかという迷いがある時期もあったと思います。マネジメントにしばらく専念しようかなと思ったのは、その経験から来ているんですか?

そこはもっと前なんですね。先ほど話したプロジェクトに入る 2 ヶ月前とか、3 ヶ月前とかだったんですけど、能登さんとも毎回 1 on 1 の時にそういう相談をしていて「ちゃんと自分のこと考えなさい」と言われていて。たしかに自分のことを考えられていなかったなと思って、毎日寝る前に 10 分だけそれを考えるというのを続けてたんですね。「自分が何をしたいのか」とか「自分は何が好きなんだろうか」と。

そうしたら、たしかに技術も好きだし、プログラム書くのもすごい好きなんだけど、自分にとってプログラムを書くことが目的なわけではなく、それを使ってプロダクトが良くなるということが好きなんだと。ということはプロダクトが良くなれば、プログラム書かなくてもいいんじゃないか、って結論に至ったんですよね。そこに落ち着いてからは、プロダクトを良くするためにどうすればいいかという視点で物事を考えるようになったので、エンジニアとしてコードを書いているか書いていないかというのは問題じゃなくなったし、将来的にプログラムを書き続けないといけないのか、というモヤモヤしていたところも、解消されました。

実際その当時のプロダクトチームで、自分がコードを書きながらチームリードをやっていると、ぜんぜん回っていかないという状態になっていて、それがすごく自分の中で嫌だったんですよね。自分はコードも書きたいし、チームを良くしたいのに、ぜんぜんプロダクトが良くなっていかない。自分がチームのボトルネックになってしまっているっていう…

―――― 自問自答を続けて、それで気づいた内発性というか、それをちゃんと仕事の改善に活かしているというのもすごいアカツキっぽいですよね。

確かにアカツキには多いですね。自問自答し続けるのも辛いので、つい逃げてしまうこともよくあるんですけど (笑)。壊れない程度に自分を見つめるバランス感って大事だな、って思います。

―――― チーム開発で気づいたことがあるとき、どうしているんですか?

これまでの経験上、自分が「やばい」って思っても致命的でなければしばらく黙っているのも意味があるなと思っています。そうしていると、まわりからも「これやばいんじゃないか」という声が出てくるんですよね。そうなったタイミングでちゃんと対処するとすごくうまく進むというか。自分だけしかその問題を感知していない状態だと、ひとりでいろいろやらなくちゃいけないのでたいへんなんですけど、自分以外の人も危険だと感じられた時ならまわりの人を巻き込んでできます。しかもそれを二度と起こさないためにどうすればいいかというところまで全員で考えて、チームとして「これはやめよう、なくしていこう」というところまで考えられるようになります。さらに、たまに自分が思っていたよりも更に良い方向に行くこともあって。そういう意味で、気がついていながらあえて見守るのも、チーム開発においては重要だなって思います。怪我をしてちょっと傷つくのを見ているのは辛いんですけどね。(笑)

新規ゲームタイトル開発ならでは楽しさと難しさ

―――― 今は希望していた新規ゲームタイトル開発チームにいますよね? そちらの方はどういう状況ですか?

このプロジェクトも以前の初期と似たコミュニケーション不全などありましたが、まずはエンジニアチームだけ改善してみて、それがちょっとずつうまくいき始めたら、他のプランナーやデザイナーも巻き込んで、という形でやってきました。まだ完全にスクラムの全部をやりきれているわけではないんですけど、チーム開発とは何かというのをみんなで考えながら、いろいろ改善してみているという段階です。

以前のチームより、みんなにまかせる裁量も大きくしているし、自分が無茶苦茶頑張ってなんとかするという方向ではなくて、「どうやったら良くなるんだろうね?」というのをみんなに問いかけながらやっています。とはいえ、以前のチームと同じ状態まで持っていくにはまだ半年くらいはかかるかなという気はしていますが。

f:id:notolab:20170205160704j:plain

―――― 「チーム開発とはなにかみんなで考えながら」のところや、改善内容についてもう少し詳しく教えてもらえますか?

朝会とかスプリント・レトロスペクティブ (=振り返り) の時とかに、これってどうやったら改善されるんですかね? って聞くようにしています。例えば、チームとして誰かが誰かを経由して伝えているような状況だと「それでいいんでしたっけ? 直接伝えたほうが速いんじゃないですか?」と聞いたり。ちょっと嫌そうな顔をされるときもあるのですが、めげずに背中押しをします。

あとはベロシティやバーンダウンチャートを毎日 Slack に投稿して、今こういう状態になっています、前回の Sprint からこう改善されました、これはチームのコミュニケーションが上手くいっているからですよね、というのを伝えることによって、「じゃあこういう状態がいいんだ」というのをみんなで理解するということもやっています。数値やグラフで見えるということはいいことかなと思っていて、感覚ももちろん大事だと思うんですけど、自分たちはこれだけ良くなったんだというのを定量的に見えるというのも大事かと。

ベロシティ測るのは、以前いたチームではちゃんとできてなかったことなんです。いまのチームは最初から自分がいろいろ知識を持った状態で入れましたし、プロセスがほとんど定義されていなくてまっさらな状態だったので、いくらでもいいプラクティスをやろうと思えばやれるので。

―――― 今担当しているプロジェクトは、新規ゲーム開発、ゼロからイチを作るということだと思うんですけど、それってゲーム業界の中では、運用中のタイトルを改善するよりぜんぜん難易度が高いって言われてますよね。そこに移ってみて、実際難易度が高いと感じますか?

新規タイトル開発はやっぱり難しいなと思いますね。すでに形があるものから改善するというのは見えやすいというか、わかりやすいんです。運用しているタイトルならみんなも自分でプレイしていますし、「ここの画面に行くときは以前はこうなってたけど、今後こうなります」というような説明は伝わりやすい。

新規だとそういう前提となるものがないので、先にコンセプトとか、ストーリーをしっかり作っておく必要があります。仕様についても、そういった前提を踏まえて「これは何のためにやるんだ」というのをちゃんとおさえながらやらないと、担当者間で認識の齟齬が出て、手戻りが発生したりします。

あとはやっぱり、スケジュールですかね。運用タイトルの場合は、すでにプロダクトが動いていてお金も稼いでいるので、最悪新しいバージョンが 1, 2 週間、あるいは 1 ヶ月遅れても、まずいはまずいですけど、大ダメージにはならないですよね。でも、新規のタイトルの場合は、本当にそこに出さなければ終わってしまうというか、1 ヶ月伸ばすとコストだけがかさんでいくことになるので。

―――― 見込んでいた売上がまるまる 1 ヶ月たたないとか。

そうなんですよね。

あと難しいのは、作ったことのない機能が多くなるので、予測がしづらくなるというのもあります。見積もりとかも相当ずれるというのもありますし、見積もりがそもそもしづらいというのもありますね。

アカツキでの成長

―――― ここまでの話で、アカツキでいろいろチャレンジしてきている内容が伝わると思うんですが、本人としてアカツキで成長できたなと思うポイントってありますか?

成長したことはたくさんあるなと思っていて、プロダクトに対する考え方とか、そもそもゲーム業界じゃなかったので、すべてが自分にとっての成長になったと思います。

いちばん成長したのは根本から考えるところかなと思っていて、自分がそのエンジニアなのか、マネージャなのかというところで悩んでいたところとか、あとはチームに開発とは? とか、いまの新規開発とは? とか、根本から考えるということを、ちゃんとやれるようになったというのは、すごく大きな成長かなと思います。

―――― なぜ根本から考えるようになったんですかね?

今日すでに話した内容にも出ていますが、根本や前提から考えないで表面上の議論をしていたりとか、表面上取りつくろったりして何かをやっていても最終的にうまくいかなかったりとか、その下にある爆弾が爆発して… というのを何回か経験しているというのも大きいと思います。あとはやっぱり自分の中でモヤモヤし続ける、すっきりしないまま進めてしまうっていう、そこが耐えられないからかもしれません。もうその根本から考えるということが重要だってことをわかった状態にある以上、根本から考えないでやることに抵抗があるというか。

―――― たしかに取りつくろうよりは、過去の自分達を否定することになっても、根本の問題解決をしようという傾向が、アカツキにあるような気がします。

アカツキでよく言われるキーワードだと、より良くなるために、グレートになるためにどうすればいいか、グレートな状態は何かをけっこう CEO の塩田さんたちが口にしているので。これはそもそも『ビジョナリー・カンパニー②飛躍の法則』(ジム・コリンズ 著)という本から来ている内容なんですけど、そういうところが大きかったのかもしれないですね。

現状に満足しないというか、どんなに売上が良かったり、最高益だったとしても、必ずプロブレムを見つけ出して、それを改善していくというのを会社全体で常にやり続けているという姿勢からきているのかもしれないですね。

スクラムマスターからエンジニアリング・マネージャへ領域を広げる

―――― 湯前さんはスクラムマスターとして、担当チームを良くするというのが業務の主軸にあって、それでチームにも会社にも貢献しているんだけど、今さらにエンジニアリング・マネージャとして、エンジニア全体のチームをどうするか、採用をどうするか、今後評価制度をどうするかというところにも関わろうとしているじゃないですか? ある意味すげー忙しいのにそこにチャレンジしようと思うモチベーションって何なんですか?

まず一つは、未知なことに挑戦したいからですね。スクラムマスターはやるべき業務が明確になっていて、何かチームにこういう課題があるとか、あとはいつのタイミングに発動させるかというのを見ながらやっているというのがあって、ある種自分の中では型が定まりつつあります。一方、それ以外の領域はまだぜんぜん見えていないところなので、もっと自分の知見を広げたいなというのがあって、他のことも挑戦してみたいと思っています。もっと広い視点に立った時に、どういうことなんだろうか、見えてくるものは変わるんだろうか、とか。

あとは、最近読んでいる本とかにも感化されているのかもしれないですけど、幸せな状態で働きたいんですよね。それは自分もそうなんですけど、いっしょに働いている人たちが幸せな状態で働きたいと思っていて、だからそこをちゃんと考えたいなというのもあるかもしれないですね。

―――― いま読んでいる本というのはどういうものですか?

ひとつは『Managing for Happiness』 (Jurgen Appelo 著)、 もう一つは 『Joy, Inc.』 (リチャード・シェリダン著) です。どちらも最近出た本で、タイトル通りなんですが、自分も周りの人も喜びを感じながら幸せに働くためにはどういうマネジメントができるか、どういう組織づくりができるか? について述べた本です。

―――― スクラムマスターとか、エンジニアリング・マネージャをやるにあたって、エンジニアのときの経験って活きると思います?

難しいですが、スクラムマスターって技術のことを基本的には指示しない立場なんですよね。しない立場なんですが、自動化の重要性とかもわかっていないといけないし、これまでの経験上、こういう技術を使ったほうがいいとか、こういうシステムを導入した方がいいとか、先にこれをやっておいた方がいいっていうのはあって。それをなんとなく、それとなーく (笑) 言うという点では活きています。

エンジニアリング・マネージャとしても、エンジニア特有の悩みをわかっていないとエンジニアにとって良い施策を打てないと思っています。先ほどお話したコードを書くのかマネージメントなのか、問題もありますし、技術の学習や情報共有をどうすればスムーズに行えるか、とか。こちらも気づきを与えるようなコミュニケーションを出来るだけ取るようにしています。

―――― 自分が決める役割ではないけど、気づいてもらう?

物事をより良くするためには、個々人の主体性が必要かなと思っています。

スクラムマスターとしての例をあげます。プロダクトオーナーはどんどん目に見えるビジネス価値を追い続けようとするので、目に見えない価値に工数を割くことに対してあまり理解されなかったりします。一方、開発メンバーはこの改善に数日もらえれば劇的に効率良くなる、というアイデアを持っている場合が多いのですが、中々それをプロダクトオーナーに説明しきれない場合があります。そのため、こういうことが大事ですよ、自動化とか、システム化とか、フロー化とか、リファクタリングとか、やり続けないといけないですよ、とプロダクトオーナーが認識し、理解するのをサポートします。一度理解して、あとは結果を出せば、プロダクトオーナーももっと改善出来ることは無いか、と主体的に考えるようになります。そういうサポートをする意味で今までの技術知識が活きているかなって思います。

―――― 開発メンバーがやりたいと思っていることをプロダクトオーナーに翻訳してあげる役割をするんですか?

場合によってはやりますけど、どちらかというと直接言うのをサポートしていますね。話したいって言ってるみたいですよ、って伝えてみたり。

プロダクトオーナーと開発チームの関係ってトップダウンになりやすいですし、壁を作りやすいので、その壁を取っ払う役割として働いたりしてます。もしもそういう提案をする場合は、自分とプロダクトオーナーのふたりだけで話すんではなくて、必ず言い出しっぺの人を連れてきて言ってもらいますね。そうやって、主役になるべき人にちゃんと主役になってもらうようにしています。

Viewing all 223 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>