アプリケーションの不調を発見し、チームで改善できた話

弥生 Advent Calendar 2020 8日目の記事です。

開発本部の id:mizukmb です。普段はMisocaのインフラの面倒を見たりしています。

Misocaの開発チームでは毎週SLOの状況を共有する時間をとっています。SLOを設定した話は別記事で紹介しています。

tech.misoca.jp

先日、開発チームに共有したところ普段よりもレスポンスタイムが悪化してることがわかりました。これ以上の悪化はサービスに大きな悪影響を及ぼしてしまう事も懸念されました。

そこで、年末年始をトラブルなく過ごせるように開発チームでボトルネックを見つける会を開き、原因の特定から実際に改善し効果が表われたことを確認できました。

問題発見

Misocaには時期によってアクセス数の波があります。月末・月初はアクセス数が普段と比べて多くなり、レスポンスタイムもそれに伴って増加しやすい傾向にあります。

下図を見てもらうとわかりますが、9, 10月は連続してレスポンスタイムの95, 99パーセンタイル値が増加しています。さらに、9月は四半期締めの時期で特にアクセス数が多い月なのですが、それよりも10月の方がレスポンスタイムが長くなってしまっています。この時点では原因は特定できてなかった状態で、早急に手を打つ必要がありました。

f:id:mizukmb:20201207164639p:plain
Redashで可視化しているレスポンスタイムのグラフ。濃い赤が95、薄い青が99パーセンタイルのレスポンスタイム値。9月と比べて10月の方が大きくスパイクしていることがわかる

しかし、開発チームメンバーは各自プロジェクトのタスクを抱えており、どのくらいのタスクが発生するかわからないパフォーマンス改善を各自が率先して引き受けるのは心情的に難しい状態でした。

そこで、過去に開発者ブログでも紹介した「お気持ち会」の要領で有志で集まり、最低限下記を明らかにすることにしました。

  • レスポンスタイム悪化のボトルネック特定
    • フロントエンド、アプリケーション、バックエンド、ネットワーク等様々な観点から見つける
  • ボトルネックを解消する方針の策定

上記までを会のゴールとして、実際に解消する作業は別途行うこととしました。

f:id:mizukmb:20201204140203p:plain
開発チームでどうするか話し合って、有志でボトルネックを見つける会を開くことが決まった

お気持ち会の記事はこちら。

tech.misoca.jp

ボトルネックを見つける会

後日、ボトルネックを見つける会を開きました。当日はRedashで可視化しているレスポンスタイムのグラフやSkylightでRailsのアクション毎のレスポンスタイムやイベントシーケンスを確認しながら、ボトルネックとなっている処理をみんなで探しました。結果として、いくつかボトルネックと思われる処理の特定ができ、さらにはそれに対する具体的な改善策まで話し合って決めることができました。

f:id:mizukmb:20201207143141p:plain
当日の議事録。当日は多くの人に参加してもらったことで一般的な高速化のテクニックからドメイン固有の知見を活かしてボトルネックと思われる箇所を複数発見できた

最終的に、この見つける会ではボトルネックを思われる箇所をいくつかリストアップし、その中から特に大きな効果を期待できそうな改善策を決めて修正するというところまで決めて終わることができました。

コードの修正〜リリース後の効果測定

見つける会では今後の作業者のアサインまでは決められませんでしたが、修正箇所の実装に詳しいメンバーが積極的にタスクを拾い上げてくれたおかげで、会を開いたその日に修正のプルリクエストが出来あがるという驚きの仕事の速さでした。

f:id:mizukmb:20201204150658p:plain
頼れる開発チームメンバー

f:id:mizukmb:20201207143032p:plain
本当に2時間後にプルリクエストを作っていた

リリース直後から効果は表れていて、効果の高いところだと普段よりも95パーセンタイル値で40%程高速になっていました。

f:id:mizukmb:20201207174846p:plain
Skylightのレスポンスタイムグラフ。薄い赤が95パーセンタイル値。リリース直後からグラフが全体的に下がっていて改善されていることがわかる

山場だったアクセス数が増加する期間も大きくスパイクすることがなくなり、パフォーマンスの改善効果があったことを確認できました。

f:id:mizukmb:20201207182411p:plain
Redashで可視化しているレスポンスタイムのグラフ。濃い赤が95、薄い青が99パーセンタイルのレスポンスタイム値

まとめ

アプリケーションのレスポンスタイム悪化から、実際に開発チームで改善するまでの流れを紹介しました。

ボトルネックの発見方法については、過去にISUCON 1 というコンテストに参加していた個人的な経験が活きたかなあと思いました。

また、こうした緊急度の高い問題に対してきちんと開発チームとして問題の提起から解決、結果の測定まで手を動かした事も良かったですし、それをしっかり実行できるMisocaの開発チームは強いチームだなあと改めて実感しました。

採用

チームで問題解決に取り組むことができるMisocaに興味のある方からの応募を待っています!


  1. 与えられたアプリケーションの高速化を競うコンテスト http://isucon.net/

Serverless Framework で DocBase の日報を Slack に流す Bot を作った話

この記事は弥生 Advent Calendar 2020の1日目の記事です。

こんにちは。@KawamataRyoです。
最近嬉しかったことは、6 年ぶりにリアルの新刊を読めたことです。

さて、Misoca開発チーム では DocBase に日報を書くのですが、みんなの日報を読むために毎回ブラウザで DocBase を開くのが地味に面倒でした。 なので、日報の内容を Slack に流す Bot を作ってみました。
この記事ではその実装方法を紹介します。

🍿 何を作った?

DocBase に日報を投稿すると、こんな感じにフォーマットされたメッセージを Slack の専用チャネルに通知する Bot です。DocBase 上でのコメントの投稿にも対応しています。

f:id:ba068082:20201130135321p:plain

一応 DocBase 自体で Slack 連携に対応しているのですが、以下のように投稿の全文が表示されず、毎回 DocBase を見に行く手間が発生するので今回は独自で作りました。

f:id:ba068082:20201126152042p:plain

help.docbase.io

🛠 実装方法

サクッと作りたかったので、DocBase からの Webhook を受ける用の AWS Lambda を作り、レスポンスを Slack 投稿用に整形したうえで Slack の Incomming Webhook に投げる方式としました。
構成はこんなイメージです。

f:id:ba068082:20201201083416p:plain

次項から簡単に実装を説明します。

1. Slack Appの作成

最初に Slack API から Slack App を作成して Incoming Webhooks を設定をします。

https://api.slack.com/apps

f:id:ba068082:20201126151540p:plain

ここで発行した Incoming Webhook の URL を次項で作る Lambda の送信用URLとして使います。

2. Serverless Framework で Webhook 応答用の Lambda を作成

AWS の Lambda の実装です。
Lambda は Serverless Framework を使って開発しました。
Serverless Framework は Lambda や cloud functions の開発環境構築・デプロイを楽に実現するためのライブラリです。

www.serverless.com

今回は以下コマンドで、TypeScript の Lambda 環境を作りました。

$ npx sls create -t aws-nodejs-typescript -n docbase-nippou-notifier -p docbase-nippou-notifier

これだけで指定のパスに Lambda の関連コードが生成されます。
あとは生成されたディレクトリに移動してコードを編集します。

今回は以下のような関数を作りました。
DocBase の Webhook からの POST リクエストを受け取り、Slack の Incoming Webhook に投げるだけです。

import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';
import { DocBaseWebhookPayload } from "./lib/types";
import { createPostBlock } from "./lib/createPostBlock";
import { createCommentBlock } from "./lib/createCommentBlock";
import fetch from 'node-fetch';

// DocBaseからのイベントを受け取り、整形したデータをSlackにPOSTする
export const webhook: APIGatewayProxyHandler = async (event, _context) => {
  try {
    const payload = JSON.parse(event.body) as DocBaseWebhookPayload;

    if (isTargetTeamPost(payload)) {
      await postToSlack(payload);
    }
  } catch (error) {
    console.error(error);
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'ok',
    }, null, 2),
  };
}

// 対象チームの投稿かどうかの判定
const isTargetTeamPost = (payload: DocBaseWebhookPayload) => {
  return payload.post.tags.some((t) => t.name === process.env.TEAM_TAG_NAME);
}

// Slack Incoming WebhookへのPOST
const postToSlack = async (payload: DocBaseWebhookPayload) => {
  const messageBody =
      "comment" in payload
          ? createCommentBlock(payload)
          : createPostBlock(payload);

  const options = {
    method: "POST",
    headers: { "Content-type": "application/json" },
    body: JSON.stringify(messageBody),
  };
  await fetch(process.env.SLACK_WEBHOOK_URL, options)
}

Slack への投稿の整形は以下で行っています。
レイアウト指定の JSON は、Slack Block Kit Builder を使って構築するのがおすすめです。

import { DocBaseWebhookPayload } from "./types";

export function createPostBlock(payload: DocBaseWebhookPayload) {
  const post = payload.post;

  return {
    blocks: [
      {
        type: "header",
        text: {
          type: "plain_text",
          text: `:pencil2: 日報 posted by ${post.user.name}`,
        },
      },
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: `<${post.url}|${post.title}>`,
        },
      },
      {
        type: "divider",
      },
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: post.body,
        },
        accessory: {
          type: "image",
          image_url: post.user.profile_image_url,
          alt_text: "user thumbnail",
        },
      },
      {
        type: "divider",
      },
    ],
  };
}

あとは、serverless.tsを編集して Lambda、API Gateway、環境変数、デプロイ先を設定して以下コマンドを実行すれば指定の Lambda にデプロイされ、API Gatewayまで作成されます。

$ npx sls deploy

Serverless Framework 便利!

3. DocBase の Webhook 設定

最後に DocBase の Webhook の設定を行います。 以下記事の通りに設定すればOKです。設定するURLは Serverless Framework でデプロイした Lambda のエンドポイントとなります。 これで完成です 🎉

help.docbase.io

終わりに

Lambda はちょっとした Bot を作るのにも使いやすいですね。
Misoca の slack には他にも Lambda で作られた Bot が多数生息しています。

tech.misoca.jp

今後も業務・個人で色々作っていきたいなと思いました。

🎺 宣伝

Misoca 開発チームでは日常の些細なことでも技術で解決していくエンジニアを募集しています。

Thinreports に SectionReport フォーマット機能を追加する pull request を作成した

こんにちは、Misoca開発チームの日高(@hidakatsuya)です。

以前、Thinreports の SectionReport フォーマット機能を公開したという記事を書きました。

tech.misoca.jp

この度、この機能を pull request 及び issueとして、Thinreports コミュニティに提案しましたので、その内容について紹介します。また、SectionReport フォーマットでは何ができるのかを知ってもらうために、いくつかの特徴もご紹介します。

SectionReport フォーマット機能のコミュニティへの提案

提案の概要

github.com

機能の概要や使い方、SectionReport フォーマット機能の今後の開発方針などを説明しています。SectionReport フォーマットという機能がどういうもので、どのような背景があり、どのような方針で実装されているかなど、この issue を読むことでそれらを把握することができます。

内容は概ね次の通りです。

  • コミュニティで提案されている仕様 をベースに SectionReport フォーマットを実装した
  • 未実装の仕様や既知の課題が残っているが、それらについてはコミュニティと一緒に開発していきたい
  • SectionReport フォーマットの仕様の説明として、いくつかのサンプルコード (Rubyコード、テンプレート、出力結果PDF) を作成したので参考にして欲しい
  • SectionReport フォーマットの使い方として Hello World を説明
  • 私たちが実装した SectionReport フォーマットは未完成で問題もあるが、今後はコミュニティと一緒に開発を進めたい。その提案として、一旦現状で取り込み、仕様の議論とタスクの整備を行って、広く開発に参加できる環境を作って進めてはどうだろう

実装の詳細 (pull request)

Thinreports の Generator (rubygem) と Editor (テンプレートデザイナ) の実装は、それぞれの pull request として作成しています。

github.com

github.com

pull request の説明では、使い方や実装ステータス (未実装、独自実装、既知の問題) について詳しく記載しています。

ぜひご意見ください

私たちが提案した SectionReport フォーマットは、未完成でいくつかの問題もあります。SectionReport フォーマットの機能としての仕様はもちろん、今後の開発の進め方など、広くディスカッションした上で、より良い形で開発を進めたいと考えています。

issue の内容への質問や意見、実際に動かしてみた感想や質問、コードに対する指摘などなど、ぜひご意見をいただけると嬉しいです。

SectionReport フォーマットで何ができるのか

現行の Thinreports のフォーマット (以降、現行のフォーマットと呼ぶ) との比較として、SectionReport フォーマットの特徴を二つ紹介します。

そもそも、SectionReport フォーマットって何?という方は、まず Thinreports コミュニティで提案されている仕様 をご覧ください。

ヘッダーやフッター、明細行を任意の数だけ定義し、組み合わせて出力することができる

現行のフォーマットでは、ヘッダーやフッターといった概念自体がありません。近い機能として「リスト」というツールがありますが、ヘッダーやフッター、明細行は一つだけしか定義することができません。

SectionReport フォーマットでは、次のように、いずれも任意の数だけ定義することができ、名前(ID)によって、それらを組み合わせることができます。

例えば、次のようなテンプレートを用意します。

f:id:hidakatsuya:20201012180259p:plain
テンプレート (Thinreports Editor)

次のコードで PDF を生成します。

report_params = {
  type: :section,
  layout_file: 'example1.tlf',
  params: {
    groups: [
      {
        headers: {
          header1: { items: { text_block1: 'タイトル' } },
          header2: { display: false }
        },
        details: [
          { id: :detail_a, items: { text_block1: '明細1行目' } },
          { id: :detail_a, items: { text_block1: '明細2行目' } },
          { id: :detail_b, items: { text_block1: '明細3行目' } },
          { id: :detail_b, items: { text_block1: '明細4行目' } },
          { id: :detail_a, items: { text_block1: '明細5行目' } }
        ],
        footers: {
          footer1: { display: false }
        }
      }
    ]
  }
}

File.binwrite('example1.pdf', Thinreports.generate(report_params))

出力される PDF は次のようになります。

f:id:hidakatsuya:20201012174550p:plain
出力結果PDF

パラメータによって、テンプレートに定義されたヘッダーを非表示にしたり (display: false)、複数の明細の定義を組み合わせて出力するといったことが可能です。

自動的に領域の高さが伸縮する

現行のフォーマットは、設定した用紙をキャンバスとして、テキストや図形などを配置してレイアウトを作成します。そのため、出力する PDF の高さも用紙の高さで固定され、テキストや図形などの描画位置やサイズも固定です。そのため、テキストの内容によって、図形の位置を下にずらしたり、領域の高さを動的に変更することが困難です。

SectionReport フォーマットでは、ヘッダーやフッター、明細行の高さの動的伸縮をサポートしています。また、動的伸縮によって、図形などの描画位置も動的に追従することが可能です。次の例をご覧ください。

次のようなテンプレートを用意します。

f:id:hidakatsuya:20201012175515p:plain
テンプレート (Thinreports Editor)

次のコードで PDF を生成します。

report_params = {
  type: :section,
  layout_file: 'example2.tlf',
  params: {
    groups: [
      {
        headers: {
          header1: { items: { title: '長いタイトル ' * 15 } },
        },
        details: [
          { id: :detail_a, items: { detail_text: '短い明細' } },
          { id: :detail_a, items: { detail_text: '長い明細 ' * 18 } },
          { id: :detail_a, items: { detail_text: '短い明細' } }
        ]
      }
    ]
  }
}
File.binwrite('example2.pdf', Thinreports.generate(report_params))

出力される結果は次のようになります。

f:id:hidakatsuya:20201012175703p:plain
出力結果PDF

定義した領域を超えるテキストをセットした場合でも、ヘッダー1 の領域の高さが自動的に拡張し、ヘッダー2 や明細A がその下に続いて正しく描画されます。また、明細Aの二行目では、拡張した領域に合わせて四角形の高さが拡張していることがわかると思います。

最後に

興味のある方はぜひ SectionReport フォーマットで遊んでみてください。

宣伝

Misoca 開発チームでは、積極的に OSS に貢献していきたいエンジニアを募集しています。

www.wantedly.com

Railsの複数DB機能で負荷を分散する

こんにちは。弥生で Misoca を開発している小坂と申します。インターネットには kosappi という名前で存在しています。

前回ご紹介した みんなのコンピュータサイエンス は読んでいただけたでしょうか?

9月末で事業年度が終わる会社は多いかと思います。みなさんは無事に10月を迎えることはできましたか?私は有給休暇の日数が付与されて、とても良い気分です 🏝

今回は、Rails の複数 DB 機能を利用して9月末の高負荷を乗り切った話を紹介いたします。

🔥 月末の高負荷

Misoca は請求書作成ソフトということもあり、月末にアクセスが増加します。

ユーザの増加や、機能が充実したことにより、DB への負荷も増加しています。8月末の負荷は DB の限界に近い値でした。 特に、文書の一覧や検索などの参照系のクエリの比重が高く、機能の充実によってクエリ自体も重いものになっており、問題になっています。

9月末は事業年度が終わるユーザも多く、8月末よりも負荷が高くなり、このままでは DB の処理能力の限界を超えてしまう事態が予想されました。

DB への負荷を改善する方法としては、下記のようなものがあると思います。

  • 重いクエリを見直して軽くする(SQLの改善)
  • DB インスタンスの強化(スケールアップ)
  • DB インスタンスを増やして負荷を分散させる(スケールアウト)

今回は参照系のクエリが問題になっていることもあるので、読み込み専用の DB インスタンス(リードレプリカ)を増やして、一部の重いクエリをそちらに向けることにしました。

🛠 Active Record による複数の DB 利用

Misoca は Ruby on Rails の上で開発されています。

Rails は 6 にバージョンアップした際に、複数の DB 利用をサポートしました。Rails 5 でも、追加の gem を使えば実現できたのですが、Rails 6 からは標準機能として提供されます。

参考 : Active Record で複数のデータベース利用

今回はこの機能を利用して、一部のクエリをリードレプリカに向けました。

一部のクエリをリードレプリカに向ける

まず DB を追加します。

事前に AWS の RDS で、普段使っているインスタンスをレプリケーションするインスタンスを作りました。 レプリケーションの遅延が発生しますが、これは 1ms 以内と示されていたので、今回は問題にならないと判断しています。

メインのDBを primary、リードレプリカを replica という名前にしています。 Rails が DB を判断するために、レプリカとして利用する DB には replica: true を書く必要があります。

production:
  primary: &primary
    <<: *default
    host: <%= ENV['PRIMARY_HOST']  %>
  replica:
    <<: *primary
    host: <%= ENV['REPLICA_HOST']  %>
    replica: true

そして全てのモデルで上記の DB が利用できるようにします。 今回はロールが writing の場合は primary を、 reading の場合は replica をそれぞれ利用します。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to database: { writing: :primary, reading: :replica }
end

最後に、向き先を変えたいクエリを実行している箇所を、下記のようにブロックに含めればOKです。このブロックの外では必ず role: :writing でクエリが実行されます。 role 以外にも database を直接指定することもできますが、こちらは非推奨となっているので気をつけてください。

ActiveRecord::Base.connected_to(role: :reading) do
    # リードレプリカに向けたいクエリを実行するコード
end

今回は手動でロールを変更しましたが、自動的に切り替えることも可能です。 書き込むクエリの場合は primary、読み込みクエリの場合は replica を使う、といった振り分けを書くことができます。

参考 : Active Record で複数のデータベース利用#コネクションの自動切り替えを有効にする

📉 結果

以上の対策を施した上で、無事に9月末を乗り越えることができました。

DB の負荷はどうなっていたんでしょうか?

primary DB の IOPS 1 を見てみましょう。オレンジが READ、ブルーが WRITE の IOPS です。

リードレプリカの利用開始時期(9/15ごろ)から、READ の IOPS が下がっていることがわかります。 8月末と9月末を比較すると、かなり負荷を減らすことができました。

今回は9月末の高負荷を、Rails の複数 DB 機能で乗り切ることができました。 今後は、2台に増えた DB のスペックを見直したり、他のクエリをリードレプリカに向けることを検討する予定です。

🎺 宣伝

Misoca 開発チームでは、Rails の新機能を使って課題を解決したいエンジニアを募集しています。


  1. Input/Output Per Second の略です。1秒あたりの入出力の多さを示しています。

みんなのコンピュータサイエンス読書会を完走しました

2週連続でこんにちは、弥生のMisoca開発チームの黒曜(@kokuyouwind)です。

前回記事のはてブがやたら伸びていて、なんでかと思ったら@mugi_unoのツイートがバズっていました。

これがインフルエンサーか…!

📕 みんなのコンピュータサイエンス読書会

以前の記事で書きましたが、Misoca開発チームでは有志で集まりみんなのコンピュータサイエンスの読書会を開催していました。

tech.misoca.jp

みんなのコンピュータサイエンス

みんなのコンピュータサイエンス

こちらの読書会ですが、2020年8月28日を持って無事完走しました!🎉🎉🎉

参加者の皆さん、お疲れさまでした!!!

⏱ 読了にかかった時間

初回が2019年12月6日だったため、9ヶ月ほどかけて読み切ったことになります。

週に1回の予定でしたが、休みだったり参加者の都合が合わなかったりといった理由で中止になる週も多かったため、実際には月2回ほどの実施だったと思います。少し多めに見積もっても20回20時間程度で読みきった計算になりますね。

本の厚さから考えると、かなり時間をかけたほうだと思います。とはいえアルゴリズムや計算量の式をきちんと追いかけたり、実践と結びつけた議論を行ったりなど丁寧に読み解いたため、まぁ妥当な期間ではないでしょうか。

なにはともあれ、長期に渡る読書会をきちんと完走できて安心しました。

💬 参加者の感想

f:id:kokuyouwind:20200903114924p:plain:w40 @suer

本については一貫してできるだけコンピュータサイエンス初学者にとっつきやすい説明がされていたと思います。 日々の開発で何気なく扱っている各種事項にこういう背景があるのか、というのが概観できました。

この本単体ではコンピュータサイエンスの学習としては足りないですが、 この本から興味のある分野をピックアップして他の書籍をあたるなどして深堀りしていくとよさそうです。

勉強会の形式については読んだあとに議論する形式だったので、議論の中で本には書かれていない 発展的な内容に言及されることもあっておもしろかったですね。 特にDBのインデックスをどうつければいいのかという話はこの本には書かれていなかったですが、 自社プロダクトに絡めてそのあたりの話ができたのでとても理解が進みました。


f:id:kokuyouwind:20200903131027p:plain:w40 @thara

コンピュータサイエンスの全体像を概観できる、「みんなの」という修飾に見合うレベル感だった。 検索用に英語の単語も併記されているのが丁寧で、この本を出発点に興味ある分野を掘り下げやすくなっているのが良い。 反面、すでにコンピュータサイエンスを修めている人に取っては物足りなさを感じたかもしれない。

単なる輪読会で終始せずに、議論したり、時にはコード書いたりしたのが良かった。 簡単な数式の読みを勘違いして意味がわからなかったのをその場で気軽に質問したが、これも心理的安全性のなせる技だなぁと実感した。


f:id:kokuyouwind:20200903131329p:plain:w40 @RKTM

あんまり参加できなかったが、本の記述以外のことも持ち寄って話せるのは、独学ではできない価値のある体験でした。

広く、程よく深い(程よく浅い)という内容で、良い復習になりました。


f:id:kokuyouwind:20200903131456p:plain:w40 @mizukmb

学生の頃に勉強した内容を復習するような、コンピューターサイエンスについて広くわかりやすく解説された本でした。

輪読会についても、この本には書かれてなかった技術や歴史などについて参加者同士で議論することができ、個人的にとても実りのある会になりました。


f:id:kokuyouwind:20200903131600j:plain:w40 @KawamataRyo

コンピューターサイエンスの基礎知識ゼロの状態で輪読会に参加していたのですが、毎回難解なトピック(自分には)を、皆でmiroを使った図示やサンプルコードを用い噛み砕いて説明してくれたので大分理解が進みました。 この輪読会という形式でなければ、自分は読了することは出来なかったと思います。

本書の狙い通りコンピューターサイエンスの全体像を把握したことで、より詳細なトピックに興味を持ちました。 今後は個別のトピックについて自分なりに勉強していこうと思います!!


f:id:kokuyouwind:20190930142248p:plain:w40 @kokuyouwind

「自分が読みたいから」という理由大半で始めた読書会でしたが、蓋を開けてみると単に輪読するだけではなく深堀りした議論を行うことができ、ひとりで読むよりも多くの収穫を得ることができました。 狙いのひとつであったコンピュータサイエンス知識のベースラインを揃えるという点でも、平易ながら広い範囲を抑えた書籍でちょうどよい題材だったと思います。

またリモートワークでコミュニケーションが薄くなりがちな状況において、こうした読書会はコミュニケーションの場としても機能するという気付きがありました。 知識をインプットするだけではなくコミュニケーションを増やすという目的も兼ねて、今後はまた別の本を題材に読書会を開催していきたいと思います。

あらためて、皆さんご参加ありがとうございました!!!

📢 宣伝

Misoca開発チームでは読書会で地力を強化したいエンジニアを募集しています!

VSCodeでDraw.ioをLive Shareしたら最高の体験だった

こんにちは、弥生のMisoca開発チームの黒曜(@kokuyouwind)です。

Kaigi on Railsの登壇が決まったのでよろしくおねがいします。弥生社員としては初登壇になります。

前回の記事が濃厚だったため、今回はライトなTips記事です。よろしくおねがいします。

tech.misoca.jp

👯‍♀️ VSCode LiveShareを使ったリモートペアプログラミング

皆さん、ペア作業してますか?

ペアプログラミングはドライバー・オペレータの交代など具体的なノウハウも多く、このブログでも以前に紹介記事を書きました。

tech.misoca.jp

上記の記事ではリモートでのペアプログラミングについてscreenhero*1を使っていると書きましたが、現在ではVisual Studio CodeLive Share機能がよく使われています。

この機能は画面を共有するのではなくコードの編集結果のみを共有するため、以下のような利点があります。

  • 各自の設定・キーバインドでエディタを利用できる
  • 画面共有と比べて軽い

なお音声はZoomを使って繋ぐことが多いです。最近ではLive Share Audioがプレビュー公開されているため、こちらを使えばVisual Studio Codeのみでペアプロが完結できるようになるかもしれません。*2

✏️ draw.io を使ったペア設計

Misoca開発チームでは他にも、クリティカルな本番作業をペアオペレーションで行ったり、難易度の高いプルリクエストのレビューをモブレビューで行ったりなど、様々な作業がペア及びモブで行われることが多くあります。

今回は設計作業でVisual Studio CodeのDraw.io拡張を使ってみたところ、非常にスムーズなペア作業ができた話を紹介します。

f:id:kokuyouwind:20200831103341p:plain

Draw.io拡張を入れると、上記のようにVisual Studio Code内にDraw.ioのエディタがそのまま表示されます。(この図は本番のECS化について検討しているアーキテクチャ図です)

この図はLive Shareセッションを繋げば各自が自由に編集できるため、例えば「図にElasticacheが足りないから足しておくね」と言いつつアイコンを追加したり、「S3はNATゲートウェイ挟んでアクセスするんだっけ?」と確認して繋ぎ変えたりといったことが柔軟に行えました。

もちろんこれらはクラウドサービスとしてのDraw.ioを利用しても行えるのですが、比較すると以下のような利点があると感じました。

  • 拡張機能自体がローカルにあるためか、クラウドと比較して挙動が軽く、相手の変更が遅延なく反映される
  • ローカルファイルとして保存するため、ソースコードと一緒にGit管理できる
  • 他のソースコードと行き来して、ペア設計とペアプロをシームレスに行える

🛫 今後やりたいこと

上記のように利点を挙げましたが、今回のペア設計では既存のソースコードリポジトリと別の場所にファイルを置いているため、2番目と3番目のメリットは享受できていませんでした。

とても便利に使えることがわかったため、例えばペアプロの前のタイミングでクラス構成をざっと書いてからコーディングに入るなど、もっとライトに活用していけると良いなと思っています。

📢 宣伝

Misocaチームではペア設計を活用したいエンジニアを募集しています。

*1:現在ではSlackに統合され、Slack Callの画面共有機能になっています。

*2:筆者の環境でも試してみましたが、残念ながらうまく音声が繋がりませんでした。正式公開を待ってまた試したいと思います。

9年モノの Rails アプリで、古い Bootstrap を剥がして FLOCSS 化した話

9年モノの Rails アプリで、古い Bootstrap を剥がして FLOCSS 化した話

こんにちは、弥生 Misoca チームでマークアップをする方のデザイナー @kanizmb です。

今回、約1年をかけて古の Bootstrap の撤去および CSS 設計手法の導入(FLOCSS 化)をやり遂げたので、これらの変更をどのように進めていったかについてお話しします。

どういった状況だったか

Misoca ローンチは 2011年、当時最新であった Bootstrap 2.3.2 を用いて構築が始まりました。(*1)
当初は請求書の郵送に特化した非常にシンプルなサービスだったため、少しの上書きでスムーズに開発が進められ、Bootstrap のメリットを存分に生かせていたのだと思います。

Bootstrap2.3.2 を app.css で上書きしている図

しかし時は流れ、取引先管理、品目管理、外部サービスとの連携など、機能が増え続けるとどんどん綻びが出始めます。 設計方針もないままに野放図に差し込まれた CSS たちは、いつしか激しい詳細度バトルを繰り広げるようになります。

たとえば、コードで示すとこのような内容です。(コードはイメージです)

/* Bootstrap */
h2  { 
  font-size: 20px;
  margin: 20px 0;
 }

/* アプリ汎用スタイルを上書き */
.page-A,
.page-B,
.page-C,
.page-D {
    #main {
        h2 { 
          padding-bottom: 10px;
          border-bottom: 1px solid #eee;
        }
    }
}

/* 特定のページの一部を上書き */
.page-A {
    .team-member {
        h2 { 
          margin-top: 30px;
          padding-bottom: 0 !important;
          border: none !important;
        }
    }
}

/* 特定のコンポーネント用に上書き */
.modal {
    h2 {
      margin: 0 !important;
      padding-bottom: 20px !important;
    }
}

スコープが広い Bootstrap のスタイルを、全体用のスタイルで上書き、さらにそれらをページごとのスタイル・コンポーネントのスタイルで上書きしているため、 !important や冗長な指定が山盛りになっていました。

Bootstrap 2.3.2 を app.css, page.css, component.css の順で上書きしている図

新しくスタイルを追加するにはこれら既存のスタイルに打ち勝つ必要があり、さらに混沌を極めます。 こうなってしまうと容易に手が出せる状態ではありません。

これらの根本的な負債解消のために、デザイナー主導で CSS 改善プロジェクトを立ち上げて対応することにしました。

まずは相手を知る

嘆いていてもなにも始まらないので、まずは現存するファイル・スタイルの用途や影響範囲を調べていきます。

現状のディレクトリごとにスプレッドシートを作成し、既存の各ファイルがどこでどのように使われているか大まかに記入します。

各CSSファイルごとに何が書かれていて、どのファイル(テンプレート)で使われているかを記入しているスプレッドシート

各CSSファイルごとに何が書かれていて、どのファイル(テンプレート)で使われているかを記入しているスプレッドシート

調査の過程で過去の担当者からの叫び(FIXMEコメント)が多数発掘され、使命感が高まります。

FIXMEコメントのキャプチャ「アプリケーション全体的に使うメインのコンテンツを内包するコンポーネント/FIXME: 作りが非常に悪いので main というクラスとともに早急に見直すこと。/FIXME: header下のスタイルはコンポーネントとして切り出せるはずなので、うまく切り出したい。」

FIXMEコメントのキャプチャ「FIXME: 本来であれば各一覧でのアクション等の見た目は一貫性をもたせるべきだが、取引先だけ古いままなので特別に専用のコンポーネントとなっている」

FIXMEコメントのキャプチャ「各文書の明細のコンポーネント/FIXME: 各スタイルで table等を使っているため詳細度を上げて優先的にスタイルが当たるようにするためにエレメントを直で指定していたりするが、好ましくないので、全体的に見直し修正すること」

FLOCSS 化

改変後の CSS 設計手法は、FLOCSS を採用しました。 後発の手法で導入事例も多く、日本語のドキュメントもあり、今後のチーム開発でルールや作法の共有がスムーズに行えると判断したためです。

(FLOCSS の具体的な設計手法の解説は公式ドキュメントやそれ以外にたくさん記事があるため、ここでは省略します)

github.com

作業に入る前に CSS 改善担当者間で FLOCSS 提唱者である谷 拓樹さんの「柴犬でもわかるFLOCSS」を読み合わせて認識の統一をしたのち、以下の手順で進めました。

booth.pm

STEP1. FLOCSS ルールに沿ってディレクトリ分類

先のファイルごと用途調査を元に、既存のファイルを FLOCSS のルールに沿ったディレクトリに分類していきます。1つのファイルで分類がまたがるスタイルが混在するものもありましたが、ここでは細かな点は一旦無視しています。

この段階ではまだ微妙なバランスの上に成り立っているスタイルが多いため、ファイルの移動により読み込み順が変わって表示崩れが起こらないか、慎重に確認しながら少しづつ移動しました。

なお、1ファイルで用途が多岐に渡る巨大な汎用ファイルについては保留にして触らずに置いておきます。

STEP2. FLOCSS ルールに沿った命名変更とスタイルの整理

大まかな分類が終わったら、各ファイルごとに記述されているスタイルがどこでどのように使われているかをスプレッドシートに書き出し、新しい命名や分類を検討しました。

新しい命名・分類では以下の点に配慮しています。

  • HTML の構造に依存しすぎない
  • スコープが絞り込まれていない状態で要素セレクタを使用しない(foundation は除く)
  • BEM (MindBEMding) を使用し、名前から影響範囲や役割が想像できるようにする
  • 今後の HTML 構造の変更や機能の追加に耐えられるよう、詳細度をなるべく低く抑える

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

/* before */
body.team  {
    .member-list {
        ul { ... }
    }
}

/* after */
.p-team-member__list { ... }

命名変更とあわせて他と共通化できる部分は統合し、重複するもの、似ているけど微妙に異なるもの、不要になっていたスタイルもバッサリと整理・削除しました。

この時、見た目が似ているからといって何も考えずに統合すると後から困る可能性があるため、その要素の性質や今後のデザイン方針を踏まえた変更でなければなりません。 このあたりはデザイナーが直接担当していることで、シームレスに判断しながら作業を進められました。

ユーティリティスタイルの併用

変更前のスタイルは要素セレクタが多用されるなどでスコープが広いスタイルが多く、整理の際にこちらを立てるとあちらが立たずといった場面がとても多くありました。

このような谷を埋めるためにマージンやフォントサイズ、色や配置指定のためのユーティリティスタイルを用意しました。
わずかな差はこのユーティリティスタイルで吸収しながら、コツコツ整理していきます。

ユーティリティーディレクトリのファイル一覧のキャプチャ、clearfix, margin, cursor, flex, float, display, text など用のファイルが並んでいる

自前の CSS コンポーネントへの置き換え

CSS 改善と並行して、ボタンやタブ、フォーム・ページングなどを Bootstrap のものから、自前のコンポーネントスタイルに置き換えるプロジェクトも動いていました。

Bootstrapのコンポーネントと自前のデザインコンポーネント一覧 ビフォーアフター

この作業によって、アプリで使用されていた Bootstrap の CSS コンポーネントはすべて自前のスタイルに置き換えられました。 こちらの取り組みについて、詳しくは以下の記事にあります。

tech.misoca.jp

Bootstrap 撤廃

FLOCSS 化と自前のコンポーネントへの置き換えが終わった後、Bootstrap 用の CSS はほとんど用済みになっていました。 不要なものは削除し、どうしても必要なものは FLOCSS 分類したディレクトリに移し変えてすべての関連ファイルを削除します。

なお、JS 部分は別途フロントエンドチームが全て削除してくれています!

tech.misoca.jp

CSS 改善を支える仕組み

CSS 改善プロジェクトは非エンジニア2名によるプロジェクトだったため、リソース不足や技術的な壁にぶつかる懸念もありましたが、実際のところほとんど支障なく進行出来ました。

主な理由は以下の3点です。

高速・高品質コードレビュー

CSS のレビューはなかなか手をつけづらく滞りがちなものですが、Misoca ではレビュー待ち数を増やさないよう所属プロジェクトを問わず協力し合ってレビューし合う体制があるため、CSS 改善の PR も例外なくスムーズにレビューが通っていました。 この際、単純な見た目の違いはもちろん特別な操作を行った時にだけ表示される部分や、他の箇所との整合性までしっかり見てもらえます。

レビューや巻き戻しをしやすくするために PR を小さめに分けていた都合で後続の作業に影響が出やすい状況でしたが、全く困ることがありませんでした。

遊軍チームのサポート

デザイナーがリファクタリングを担当する事で、リファクタリングに伴うデザイン変更の細かな判断や今後の変更を前提とした設計がスムーズになる一方、動的な UI の変更や複雑な spec 修正が絡む変更はどうしてもやりきれない部分が出てきます。

こんな時、気軽に声をかけられるのがこぼれタスクを専門に扱う「遊軍」チームです。
「テスト通らない助けて〜」「やります!」と、気負わず相談できる環境にはとても助けられました。

tech.misoca.jp

最後の砦 ビジュアルリグレッションテスト

影響範囲の広いスタイルの移動や削除・命名変更では思わぬ差分が出てしまうことがあります。 リリース前にこれらの差分をとらえてくれるのが、ビジュアルリグレッションテストです。 変更前後のスクリーンショットを比較して、前後で差分が出ていないか検証してくれます

tech.misoca.jp

アプリ全体で微妙なズレまで拾ってくれるので、きちんと意図通りに変更されているかをしっかり確かめてから安全にリリースができました。

ビジュアルリグレッションテスト、reg-suit のキャプチャ。表示差分が赤く表示されている。

ビジュアルリグレッションテスト、reg suit のキャプチャ。前後のスクリーンショットを並べて、表示が変わっていることが判断できる。

Bootstrap 撤廃 & FLOCSS 化 を終えて

FLOCSS化を終えたディレクトリのツリー。余計なファイルやディレクトリがなくなっている。

よかったこと

  • !important 地獄からの脱却
  • 設計・命名ルールが定まり、どこにどう書くかを判断する基準が出来た
  • 適用範囲が明確になり、変更や削除をあまり気を使わずに出来るようになった
  • component の再利用性が高まって、新しくスタイルを書く量が減った
  • HTML 側に書かれた class 名からスタイルがどのファイルに書かれているかすぐにわかり、grep して探さなくて良くなった
  • 令和になっても Boostrap 2.3.2 を使っている負い目から解放された

気になること

  • まだ判断に迷う部分がある
    • project に置くか、component に置くか
    • Modifer として扱うか、 .is-xxx のような補助的な名前にするか
  • BEM の Block 粒度のばらつき(特に project 内)
    • 大きいものだとページ(Rails の Controller)単位だけど、コレジャナイ感
  • BEM で命名が長大になってしまう場合の対処
    • Block の中で Element が入れ子になる場合、みんなどうしているんだろう…?

今後やってみたいこと

  • 現在部分的に導入している Scoped CSS を利用した Vue コンポーネント化範囲の拡張


まだまだ改善の余地はあると感じますが、ひとまず各スタイルが HTML の構造や他のスタイルと疎結合になり、今後新たな方針で進めるにしても舵を切りやすくなったと思います。

約1年をかけてコツコツ進めてきた CSS 改善がやっと形になって、感慨もひとしおです。(膨大すぎる作業を支えてくれたパートナースタッフの @riszw に感謝!)

宣伝

Misoca ではつらくない CSS 環境で開発を進めたいエンジニア・デザイナーを募集しています。

www.wantedly.com


  1. 正確に言うと 2系が出たのは 2012年1月なので、ローンチ初期の段階は適宜アップデートしていて、ある時期に固定されたものと思われます。(追記)Misoca創業者の証言によるとローンチ時は1系で、1〜2年後に2系にアップデートされたとのことです。