Railsフロントエンド設定をふつうにする

4月からForSchool事業部の石上です。以前は、主にStudyplusのウェブ版を担当していました。現在ForSchool事業部では、Studyplus for Schoolというプロダクトをエンジニア2人で開発しています。2人ともサーバーサイドとフロントエンドの両方を担当しています。

今回、Studyplus for Schoolのフロントエンド周りに必要なツールを入れるなどして、フロントエンドのコードをふつうに書いていくための下準備をしました。

背景

Studyplus for SchoolはRailsで作られたアプリケーションです。極力Railsのレールに乗るように作られています。

フロントエンドにはWebpacker、Babel、一部にReactをすでに利用していました。 多くの部分にはRailsのビューと、自前のベースJSコンポーネントを継承したコンポーネントが設置されています。

今後、アプリケーションのReact化と適切なコンポーネント分けを進めるために、下準備として型とLintとテストの導入に取り組みました。

私自身作業が遅いという問題はありますが、やはりこの辺のboilerplateを作ったことがない人であれば調べごとが多くなるのは避けられません。2日くらいでぺろっとやろうというよりは1週間以上はかけてやったほうがいいと思います。もしくは、一気に整えようとせずに徐々に環境を整えていくなりしたほうが良いです。今回は、ちゃんと時間をいただけて良かったです。

型があることで、

  • コンポーネントのpropsとstateが見やすくなる
  • エディタの補完を使えるようになる

などのメリットがあります。

JSに型を入れるツールとしては、TypeScriptとFlowが有名です。 今回はこの2つを比較して、結果としてTypeScriptを選択しました。

どちらもシンタックス的にそれほど大きな違いはありませんでしたが、使用方法や周辺環境に以下のような違いがありました。

Flowは静的型チェッカー、TypeScriptは言語

いずれもソースコードに型を書いていくのでJSへの変換が必要ですが、エラーの確認方法は違います。

Flowの場合、拡張子は.jsのままファイルの行頭に// @flowと書くことで型チェック対象とします。トランスパイル時のエラーにはなりません。型チェックするときはLintを走らせるように、flowコマンドを実行すればエラーが見れます。

一方、TypeScriptはJSのスーパーセットとはいえ、別言語です。拡張子は.tsにする慣習があり、ビルド時にコンパイルエラーが出ます。

DefinitelyTyped vs flow-typed

TypeScriptでコードを書いていて、なるべく外部ライブラリの型定義で困りたくはありません。なので、外部ライブラリの型定義ファイルを見つけられる可能性の高い方がいいです。

量だけで見ると、TypeScriptの型定義ファイルの置き場所であるDefinitelyTypedの方が多いです。今の所TypeScriptを使ったほうが困ることは少ないと推測しました。

なお、Studyplus for Schoolではreact-on-railsというライブラリを使っていて、こちらの型定義ファイルは存在しなかったので自前で書く必要がありました。他にも、いくつかの型定義ファイルは自分たちで書く必要が出てくるかもしれません。

Lint

TypeScriptのLintには、tslint が使えます。

tslint.jsontslint:recommendedを指定すると、TSLintのおすすめのルールが設定されます。

また、React用には、tslint-react というパッケージがtslintを開発しているpalantir社から公開されているので、それがそのまま使えます。

中身を見てみると、Reactのアンチパターン的な書き方は大体含まれているようです。

今回は tslint:recommendedtslint-react をベースに、カスタマイズしていく形にしました。

{
  "extends": ["tslint:recommended", "tslint-react"],
  "rules": {
    "arrow-parens": false,
    ...
  }
}

Lintがアプリケーションの改善につながるところ

Lint設定するにあたって、コードの書き方を定めて無駄な迷いをなくして生産性向上しようという意図がありました。Reactアプリケーションの場合はこれに加えて、書き方によってパフォーマンスに影響が出るようなところを見つけて改善することにも役立ちました。

たとえば以下のようなものです。

jsx-no-bind

Binds are forbidden in JSX attributes due to their rendering performance impact

このルールはtslint-reactのv2.6.0からのもので、以下のような書き方を禁止しています。

export Parent extends React.Component<Props, State> {
  onClickItem() {
    // ...
  }
  render() {
    return <Child onClick={this.onClickItem.bind(this)}>
  }
}

render時に毎回新しい関数をつくることになるので、パフォーマンスに影響があります。以下のように直したほうが良く、そうすることでLintも通るようになります。

export Parent extends React.Component<Props, State> {
-   onClickItem() { /*...*/ }
+   onClickItem = () => { /*...*/ }

  render() {
-   return <Child onClick={this.onClickItem.bind(this)}>
+   return <Child onClick={this.onClickItem}>
  }
}

Reactのドキュメントにも、注釈に書かれています。

Using Function.prototype.bind in render creates a new function each time the component renders, which may have performance implications (see below).

Passing Functions to Components - React

テスト

今回、Reactコンポーネントに対してユニットテストが行えるところまでを設定しておきました。

Reactコンポーネントでやりたいことは、ユーザーのアクションやイベントを受け取って状態を変えて、見た目に反映することです。なので、

  • イベントに対して意図した通りに状態が変わっていること
  • 入力に対して正しい見た目を出力していること

をテストすれば十分ではないでしょうか。これができれば、ライブラリは何でもいいと思います。

テストには、基本的なテストライブラリに加えて、Reactのコンポーネントを扱うためのライブラリが必要です。今回は、同様のライブラリの中でも設定が楽そうなJestEnzyme を選択しました。

Jest

Jestは、Facebookによって開発されている「ゼロ・コンフィギュレーションのテストプラットフォーム」です。楽に設定できるのが特徴なので、ここには実際どんな設定が必要だったのかを簡単に書いておきます。

インストール

$ yarn add -D jest @types/jest ts-jest

JSのソースをテストする分にはjestさえ入れればテストを書き始められます。今回はTypeScriptなので、jestの型定義ファイルと、プリプロセッサのts-jestを入れました。

TypeScript固有の設定については、ts-jestのREADMEの通りにすれば大丈夫です。Jestの設定をpackage.jsonに書き足します。

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

これでテストが書けるようになりました。

Enzyme

Enzymeは、テストコードでReactコンポーネントを扱うために入れました。

インストール

$ yarn add -D enzyme enzyme-adapter-react-16

shallowmount でコンポーネントを初期化して、テストで行いたい操作ができます。

shallow では浅いレンダリングが行われます。最終的に出力されるDOM要素をすべて確認することはできませんが、そのコンポーネントの子のコンポーネントまでは確認することができます。

mount では子以下のコンポーネントも含めてすべてrenderするので、DOM要素を確認することができます。

ユニットテストとしてはなるべくmountは使わず、shallowを使う意識でテストを書いたほうが良いと思います。親のコンポーネントが子のコンポーネントが管理しているDOMを確認するより、子のコンポーネントのテストとして書いた方が、関心を分離できるからです。

// よい
const wrapper = shallow(<MyParentComponent hoge={hoge} />)
expect(wrapper.find("ChildComponent")).toHaveLength(1);

// よい
const wrapper = shallow(<MyChildComponent hoge={hoge} />);
expect(wrapper.find(".child-component__label")).text().toBe("ほげ")

// よくない
const wrapper = mount(<MyParentComponent hoge={hoge} />);
expect(wrapper.find(".child-component__label")).text().toBe("ほげ");

その他

Webpackerどうする問題

現在、Studyplus for SchoolではWebpackerを利用しています。しかしいくつか問題があり、これが正しい選択かはあまり自信がないため上では扱いませんでした。現状として、問題になった部分だけ設定を書き換えて使っています。たとえばUglifyJSがデフォルトの設定ではproductionでソースマップを吐いたり、同じくUglifyJSの設定が原因のIE11で起きる不具合を踏んだりなどしました。

とはいえ、やはりWebpackerを使うことで、Webpack設定に割く労力を節約できます。現段階で脱Webpackerは考えていません。

まとめ

今回、フロントエンド環境の下準備として、型とLintとテストの設定をしました。

今後は、以下のようなことをやっていきたいと考えています。

  • Reactで作られていないコンポーネントをReact化する
  • Railsのビュー(slim)に書かれた要素をフロントエンド側に持ってくる
  • Atomic Designによってコンポーネントを分ける
  • Storybookでコンポーネントごとの見た目確認を行えるようにする

ForSchool事業部では、フロントエンド以外にも、サーバーサイド、デザイン、カスタマーサポート、企画レベルでもそれぞれやりたいことはたくさんあります。手が足りてません。手伝ってくれるRailsエンジニアを募集しております。よろしくお願いします。

info.studyplus.co.jp

fastlaneでCode Signing StyleをAutomaticからManualに変更する

はじめに

Studyplus開発部のiOSエンジニアの id:kurotyann です。2015年に入社してもうすぐで3年になろうとしています。

今回の開発者ブログは、弊社のiOSアプリ「Studyplus」 のCI環境について紹介しながら、fastlaneでCode Signing Styleを変更する方法を説明します。

Studyplus iOSのCI環境

図で説明すると下記のような構成です。他にもツールを使用していますが、基本的な流れはこのような感じです。

CI環境の構成

Code Signing Styleについて

Code Signing Styleは、XcodeのプロジェクトのBuild Settingsから確認でき、AutomaticとManualの2種類を選択できます。Automaticの場合、Xcodeが適切なタイミングで開発に必要な証明書やProvisioning Profileの関係を解決してくれます。Manualの場合、開発者が任意の証明書やProvisioning Profileを指定することが可能になります。

XcodeのCode Signing Style

Code Signing StyleをAutomaticからManualに変更したい理由

Appleが推奨する設定は、Automaticです。しかし、AutomaticのままだとCI環境で意図したとおりに証明書やProvisioning Profileを参照してくれないことがあります。

またCIサービスが不調で動作しないとき、ローカルからfastlaneを起動したいことがたまにあります。そのときMacに複数の証明書やProvisioning Profileがあると、適切な証明書やProvisioning Profileを参照しないことが起きます。

このようなあらゆる事態に対応できるようにfastlaneで確実に任意の証明書やProvisioning Profileを指定させたい。Code Signing Styleのローカル環境とCI環境の切り替えをスムーズに管理したい、というのが今回のブログの主旨です。

ローカル環境(デフォルト設定)をAutomaticにしておき、fastlaneを使ってManualに変更するメリットは以下のようなものが考えられます。

  • fastlane matchで取得したProvisioning Profileを確実にManualで指定すると、ビルドエラーの可能性を減らせる
  • デフォルト設定はAutomaticなのでDebugで開発する場合、証明書を共有する必要がない
  • 外部から短期で開発に参加するエンジニアをApple Developerのmember権限でTeamに追加できる
    • デフォルト設定をManualにすると証明書の共有や権限の変更などが必要になる
    • adminは権限が大きすぎて短期で開発に参加する人の権限としてふさわしくはない

fastlaneでCode Signing Styleを変更する

必要な処理を書いたFastfileを書きます。

このFastfileは、Debugの証明書とProvisioning Profileをmatchで取得して、Code Signing StyleをManualに変更します。その後、Debugビルドでエクスポートしてcrashlyticsのbetaに送信します。

default_platform(:ios)

platform :ios do
  before_all do |lane, options|
    ENV["CRASHLYTICS_API_TOKEN"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ENV["CRASHLYTICS_BUILD_SECRET"] = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    ENV["FASTLANE_USER"] = "test@example.com"
    ENV["FASTLANE_PASSWORD"] = "hogehoge"
    ENV["MATCH_PASSWORD"] = "fugafuga"
    ENV["MYAPP_ID_DEBUG"] = "com.mycompany.myapp"
    ENV["MYAPP_EXTENSION_ID_DEBUG"] = "com.mycompany.myapp.myappextension"

    if is_ci?
      setup_circle_ci
    else
      cocoapods
    end
    carthage(command: "bootstrap", platform: "iOS", use_binaries: false)
  end

  #####################################################
  ###### Private Lane                             #####
  #####################################################

  # 証明書の取得
  private_lane :certificate_development do |options|
    match(
      app_identifier: [ENV["MYAPP_ID_DEBUG"],
                       ENV["MYAPP_EXTENSION_ID_DEBUG"]],
      type: "development",
      force_for_new_devices: true,
      force: options[:force]
    )
  end

  # TodayExtensionなどターゲットが複数ある場合は各々指定して変更する
  private_lane :change_code_signing_style do |options|
    automatic_code_signing(
      targets: "MyApp",
      code_sign_identity: options[:code_sign_identity],
      profile_name: options[:my_app_profile_name],
      use_automatic_signing: options[:use_automatic_signing]
    )
    automatic_code_signing(
      targets: "MyAppExtension",
      code_sign_identity: options[:code_sign_identity],
      profile_name: options[:my_app_extension_profile_name],
      use_automatic_signing: options[:use_automatic_signing]
    )
  end

  #####################################################
  ###### Public Lane                              #####
  #####################################################

  desc "Push a new beta Debug build to Crashlytics"
  lane :beta_debug do
    certificate_development(force: false)
    # Maunalに変更
    change_code_signing_style(
      code_sign_identity: "iPhone Developer: my account (12345ABCDE)",
      my_app_profile_name: "match Development com.mycompany.myapp",
      my_app_extension_profile_name: "match Development com.mycompany.myapp.myappextension",
      use_automatic_signing: false
    )
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      configuration: "Debug",
      include_bitcode: false,
      output_directory: "./Builds",
      output_name: "MyApp_Debug.ipa",
      export_method: "development"
    )
    # Automaticに戻す
    change_code_signing_style(
      code_sign_identity: "iPhone Developer",
      my_app_profile_name: "",
      my_app_extension_profile_name: "",
      use_automatic_signing: true
    )
    crashlytics(
      crashlytics_path: "./Pods/Crashlytics/iOS/Crashlytics.framework",
      notes: "fastlaneによる配布",
      notifications: true,
      api_token: ENV["CRASHLYTICS_API_TOKEN"],
      build_secret: ENV["CRASHLYTICS_BUILD_SECRET"],
      ipa_path: "./fastlane/builds/MyApp_Debug.ipa",
      notes: changelog_from_git_commits,
      groups: ["my-ios-team"]
    )
  end
end

Code Signing StyleをAutomaticからManualに変更する処理は、 change_disable_automatic_code_signingautomatic_code_signing です。automatic_code_signing にはいくつかaliasがありますが、今回は use_automatic_signing: でCode Signing Styleを変更する方法を選びました。

このFastfileはDebugですが、ReleaseやAdhocも基本的には指定する値が変わるだけです。crashlytics betaからTestFlightなど使用するツールの変更はあると思いますが、処理の順番や呼ぶメソッドに大きな変更はないと思います。

おわりに

現在、StudyplusのiOSエンジニアは id:kurotyann だけです。つまり、iOSの開発は私一人で行なっています。このような限られたリソースと時間の中で、実装と検証のサイクルをスムーズに回すにはCI環境が必要不可欠です。

このブログがCode Signing Styleの切り替えに悩んでいるエンジニアの助けになると嬉しいです。また一緒に開発してくれる仲間が弊社に増えるともっと嬉しいです。

参考資料

AWS IoT Enterprise Buttonを使ってSlack通知ボタンを作る

こんにちは。スタディプラスでインフラ周りを担当している id:rmanzoku です。 先日、国内での発売が開始されたAWS IoT Enterprise Buttonを使ってオフィスの小さな改善を行ったのでご紹介します。

f:id:rmanzoku:20180522171742p:plain

弊社の課題

弊社のオフィスは、ビルの4Fと6Fに分かれており、6Fは会議室となっております。 6Fは「なんとなく自分が最後だ」、という人が施錠しSlackにて報告していました。

f:id:rmanzoku:20180522171736p:plain

(採用面接がある日は遅くなりがち)

この報告は忘れやすいですし、毎日行うことから非常に手間です。 今回、この報告をIoTボタンを使って簡略化することにしました。

AWS IoT Enterprise Buttonとは

先日、国内で発売されたIoTボタンです。 以前、Amazon Dashボタンを見て、ハックを試みたエンジニアの方は多いはずです。

このIoTボタンでは、なんと、リンクしたAWS Lambdaを呼び出すことができます!

実装

今回、解決したい課題は「施錠したときの報告を簡略化したい」でした。 ですので、シンプルにSlackへ通知するボタンを実装することにしました。

アクティベートの方法は公式ブログが詳しいです。

サンプルアプリケーションによると、次のようなJSONがLambda eventとして渡されます。

{
    "deviceInfo": {
        "deviceId": "GXXXXXXXXXXXXXXX",
        "type": "button",
        "remainingLife": 98.7,
        "attributes": {
            "projectName": "Sample-Project",
            "projectRegion": "us-west-2",
            "placementName": "Room-1",
            "deviceTemplateName": "lightButton"
        }
    },
    "deviceEvent": {
        "buttonClicked": {
            "clickType": "SINGLE",
            "reportedTime": 1521159287205
        }
    },
    "placementInfo": {
        "projectName": "Sample-Project",
        "placementName": "Room-1",
        "attributes": {
            "key1": "value1"
        },
        "devices": {
            "lightButton":"GXXXXXXXXXXXXXXX"
        }
    }
}

この値のうち、placementInfo内は、ある程度自由に入力できます。 また、IoT 1-Clickのコンソールを見ると、 placementNameにIoTボタンのある場所、attributesにカスタマイズ情報を入力する思想ということが推察できます。

f:id:rmanzoku:20180522171746p:plain

画像のようにAttributeを設定し、 次のLambdaスクリプトを実行するようにします。 (弊社はRubyの強い会社ですが、手慣れているPythonです)

import os
import json
import logging
import urllib.request
import urllib.parse


logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    webhook_url = os.environ.get('WEBHOOK_URL')

    logger.info('Received event: ' + json.dumps(event))

    text = '''
    %s %s
    ''' % (
        event["placementInfo"]["placementName"],
        event["placementInfo"]["attributes"].get("msg")
    )

    body = {
        "link_names": 1,
        'username': event["placementInfo"]["attributes"].get("username", "AWS IoT"),
        'text': text,
        'icon_emoji': event["placementInfo"]["attributes"].get("icon_emoji", ":aws:")
    }

    encoded_post_data = urllib.parse.urlencode({"payload": body}).encode(encoding='ascii')
    urllib.request.urlopen(url=webhook_url, data=encoded_post_data)

内容は至ってシンプルなSlack Incoming webhookのスクリプトです。GitHub

WEBHOOK_URLはLambdaの環境変数から指定することにし、 SlackのusernameとiconはAttributeで渡すようにしました。

結果

IoTボタンを施錠記名簿のところに置くことで簡単にSlackへ施錠通知ができるようになりました。

f:id:rmanzoku:20180522171739p:plain

イケているオフィスや自宅などではある程度自動化が可能かもしれませんが、弊社オフィスのように自動化できない状況は多いと思います。 なにか1つだけでも改善することで、働きやすいオフィスに近づいていけると思います。

今回の例では、改善案を思いついて1時間くらいで実装が終わりました。 小さな改善でもすぐに実行して結果が出せるのはエンジニアの楽しみの1つだと思いました。

pt-query-digestによるパフォーマンス測定

筋トレっていいですよね。 💪

BIG3の総重量が280kg(5RM)になりました。サーバーサイドエンジニアの花井(id:hiroyuki-hanai)です。

弊チームではサービスのコアになるAPIをRailsとMySQLをベースに開発しております。 インフラにはAWSを利用しています。

スタディプラスを初期から支えているAPIなので、モノリシックな部分や性能のよろしくない部分が少なからず存在しております。

先日、突然DBサーバーのCPUがスパイクしてサービスにアクセスしづらくなる障害が発生しました。

今回はサービスに影響を与えてしまった原因をあぶり出すために実施した、pt-query-digestによる性能改善の取り組みを紹介します。

intro

RDSの一般クエリーログやスロークエリログでもよかったのですが、本番のRDSに負荷をかけず、サーバーの設定を変更しなくても実行できるような方法が求められました。 また、本番のサーバーはオートスケーリングによっていつのまにかいなくなってしまうことがあるので、ローカルにログを残すわけにはいきませんでした。

上記の事情から、EC2上でtcpdumpを取得してS3へ結果を流すようにしました。


注: long_query_time に01.以上の値を指定することで、1秒未満のスロークエリについても記録ができるようになるそうです。 参考 - Amazon RDS における MySQL の既知の問題と制限


what is pt-query-digest?

pt-query-digestはpercona toolkitの一つで、slow logなどからMySQLのクエリを解析して、見やすい統計情報を表示してくれます。

what we did?

tcpdumpをいったんS3へ送り、サービスとは切り離されたサーバー上でpt-query-digestによる解析を実行します。


Step1

こちらを参考に、ログを取りたいサーバーでtcp dumpを取得し、S3へ流します。

# tcpdump  -G60 -w - -W1 port 3306 | aws s3 cp - s3://example.com/slow-query/dump.cap

各オプションについては以下のような意図で指定しました。

tcpdump

  • -G60 : 本番環境で実行するので、他の通信へ影響がでないように/出ても軽微で済むように、60秒で様子見したい。
  • -w - : S3へ送るために、キャプチャ結果を標準出力に書き出します。
  • -W1 : サンプリング目的なので、とりあえず1ファイルに収まる範囲で見たい。

aws s3 cp

  • - : パイプの前半部分から流れてきた標準出力をそのまま受けます。

Step2

こちらを参考に、取得したtcpdumpをtxtファイルにして、pt-query-digestにかけます。

まず、S3から解析を行うサーバーへファイルを持ってきます。

$ aws s3 cp s3://example.com/slow-query/dump.cap ~/

pt-query-digestにかけます。

$ tcpdump -r dump.cap -s 65535 -x -n -q -tttt | pt-query-digest --type tcpdump >> dump.txt

オプションについては tcpdump

  • -x : パケットを16進数で表示します。
  • -n : アドレスやポート番号をそのまま出します。
  • -q : 限定したプロトコルだけの簡易的な表示にします。
  • -tttt : タイムスタンプを見やすい形(年-月-日 時:分:秒.ミリ秒)にします。指定しないと時間だけ出ます。

(このオプションはマニュアルの方で、tcpdumpを扱う時のおまじない的に指定して欲しい旨が書かれています。)


  • -r dump.cap : 読み込むファイルを指定します。
  • -s 65535 : CentOS 6のデフォルトサイズに従っておきます。0を指定すると全部になります。

output

こうして以下のような解析結果が得られます。

# Query 2: 55.40 QPS, 0.15x concurrency, ID 0xXXXX at byte 305728800
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2018-04-17 04:52:29.618996 to 04:53:29.005168
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          6    3290
# Exec time      6      9s     3ms    16ms     3ms     3ms   426us     3ms
# Rows affecte   0       0       0       0       0       0       0       0
# Query size     8 776.48k     235     243  241.68  234.30    0.00  234.30
# Warning coun   0       0       0       0       0       0       0       0
# String:
# Hosts        XX.XX.XX.XX
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms  ################################################################
#  10ms  #
# 100ms
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS LIKE 'xx_xxxs'\G
#    SHOW CREATE TABLE `xx_xxxs`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT  `xx_xxxs`.* FROM `xx_xxxs` WHERE `xx_xxxs`.`user_id` = xxx AND `xx_xxxs`.`xxxxx_id` = xxx ORDER BY created_at desc LIMIT 1\G

# Query 3: 51.35 QPS, 0.13x concurrency, ID 0x9DXX at byte 71527245
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: 2018-04-17 04:52:29.616751 to 04:53:28.978729
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          5    3048
# Exec time      5      7s     2ms     5ms     2ms     2ms    91us     2ms
# Rows affecte   0       0       0       0       0       0       0       0
# Query size     3 315.27k     104     106  105.92  102.22       0  102.22
# Warning coun   0       0       0       0       0       0       0       0
# String:
# Hosts        10.3.2.218
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms  ################################################################
#  10ms
# 100ms
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS LIKE 'xxxs'\G
#    SHOW CREATE TABLE `xxxs`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT  1 AS one FROM `xxxs` WHERE `xxxs`.`xxxxx_id` = xxxxx AND `xxxs`.`user_id` = xxxxx LIMIT 1\G

この結果から、上位20件のうち15%ほどが同じテーブルに対するアクセスであることが判明しました。 詳しく調査してみたところ、n+1問題が発見され、問題の解消をすることができました。

conclusion

今回は問題が起きてからその原因を特定するために利用しましたが、後日こうした情報をきちんと監視して、改善につなげていく必要があるという話になりました。 上記コマンドをcronで定期的に実行するなどしていく予定です。

スタディプラス開発者ブログ始めました

はじめに

はじめまして、学習管理プラットフォーム「Studyplus」を運営していますスタディプラス株式会社のCTOの島田です。

www.studyplus.jp

この度、弊社でも開発者ブログを始めることになりました。
最初の記事として、このブログを始めるにあたって考えた事を書きたいと思います。

目的

ブログの大きな目的としては採用活動への貢献があります。
その他にも以下のような事が目的として上げられると思っています。

  • スタディプラスという会社の認知度・理解度のUP
  • 求職者への期待値調整。実際に入社したがギャップがありガッカリするといったことがないようにする
  • 採用活動中以外での定期的な外部への発信
  • メンバーが発信を意識した業務への取り組み
    • 社内でのナレッジ共有
    • 技術的チャレンジの促進と技術力の向上

方向性

どんな内容の記事を投稿し、どういったブログにしていきたいかを考えました。 いきなりハードルの高いところを求めておらず、どれくらいの時間軸でどうなりたいかをメンバーと共有しました。

  • スタディプラスに関連することが望ましい。会社でのナレッジを社内外に共有・発信する
  • 他社ブログを指標にして投稿内容のイメージを持ち、段階的な成長を目指す。

例)

  • ブリ:C社、M社
  • ワラサ:S社、M社
  • イナダ:E社、S社
  • ワカシ:K社、A社
  • 魚卵:弊社

懸念・問題

弊社でもご多分に漏れず、以下のようなブログ運用の懸念事項がありました。

  • 記事執筆にリソース(人、時間)を割けないのではないか?
  • 記事執筆・公開のサイクルが回らなくなり、ブログが放置されるのでは?
  • 品質チェックは?
  • 書くネタがない場合は?

解決方法

懸念事項に対して解決を考えてから始めないと、ブログ公開自体が逆効果になるので、そこを解決する運用方法を決めました。

リソース問題

これは、言い換えると優先度の問題とも言えると思います。
多くのエンジニアの最優先事項はプロダクト開発です。そこに会社のためとはいえブログ執筆をなんの考課もなく実施することは負担を生むだけとなってしまいます。

ブログで会社のブランディングを向上させる取り組みは、エンジニア個人にとってもブランディグをあげる事にも繋がり、更にそこから優秀なメンバーの採用に繋がればエンジニアのスキル、生産性を向上させることに繋がるのではないかと思いました。

なので、ブログ投稿を各エンジニアの目標に設定し評価の対象にしました。
評価は段階的な評価軸を決めて、以下のような運用を導入しました。(以下は簡略化した概要)

  • A: 投稿が話題となる(n件シェアされる。ただし炎上はダメ)
  • B: 投稿する
  • C: 投稿しない

公開サイクル問題

投稿数をコントロールしやすくするため、各チームでのローテーションとしました。 スタディプラスでは、以下のようにチームが分かれています。

  • CTO室
  • サーバーグループ
  • アプリグループ
  • ForSchoolチーム

担当をチームとすることで個人が担当するよりも負担が軽減されるのと、各チーム間での情報共有に繋がると考えました。 そして、隔週でエンジニア全員が集まる、Engineering All Hands MTGで発表する事にしました。

品質問題

投稿前のレビューについては以下のような最低限のチェックをして、カジュアルに投稿ができるようにしようと思いました。

  • 著しく会社を毀損することがないか
  • 技術的に誤りがないか

ブログを始めたばかりという事もあり、まずは投稿する事とそれを継続する事に重点を置こうと思いました。
やってみなければ課題や問題がわからないので、適切に振り返りをして、失敗を恐れずにチャレンジして行こうと考えています。

ネタがない問題

  • たまにであれば緩い内容のものでも良い。開発に直接関係なくても中にいるエンジニアの人柄が分かる内容でもOK。
  • 弊社では情報共有にesaを利用しているのですが、そこに「ネタ帳」ページを作成しストックする事にしました。

最後に

ブログについては若干の見切り発車感は否めないですが、何事もやってみなければ分からず、かつ採用に通じる事は全てトライしていこうと思っています。

これから、本ブログでスタディプラスの魅力を発信していこうと思いますので、よろしくお願いします。
また、話を直接聞きたいという方もお待ちしております。

www.wantedly.com