ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

マルチAWSアカウント環境での構築をTerraformで行う際に待機する

おはこんばんちは

ユニファのインフラみてます、すずきです。

8月に入ってから猛暑と湿度で部屋から一歩も出たくない日々が続きますね。 涼しくなれ!

さて、Terraformでステータスが更新されるまで情報が取得できない、変更できないリソースなどは、Create後すぐに別のリソースで呼び出そうとするとエラーになることがあります。 ちょっと違うけど、ACMって検証が終わるまでELBやCloudFrontで呼び出せないので Resource: aws_acm_certificate_validation を利用して待機しますよね。

そういったことが起こりうるリソースで同じことをやってみようというものです。

続きを読む

緊急事態宣言下でのシステム運用振り返り

みなさんこんにちは。

ユニファでサーバーサイドエンジニアをしております田渕です。

新型コロナウイルスによる突然の生活の激変が起こってから、早くも半年近くが過ぎようとしています。 (居住している国、地域などにより、体感している期間は多少異なると思いますが。。。) 弊社でもこれまでに何度か、新型コロナウイルスにまつわる投稿をこのブログの中でしてきています。 経験したことのない状況の中、色々と試行錯誤をしながら進めてきたわけですが、今回は主としてここまでの「システム運用」について振り返ってみたいと思います。

前提

これを読んでくださっている方はおおよそご存知かと思いますが、ユニファは保育園、幼稚園、こども園様向けのプロダクト/サービスを提供しています。 したがって弊社では、新型コロナウイルスにおける緊急事態宣言のもとにおいても、平時と変わらない(あるいはそれ以上の)レベルでの安定したシステム稼働が必要でした。

どんな状況だったのか

勤務体制

何度かこちらのブログでも紹介している通り、ユニファのエンジニアは元々リモートワークが可能な勤務形態でした。 これまではリモートワークと言えばエンジニアがメインでしたが、緊急事態宣言に伴い、全社的に可能な限りリモートワークを行う体制に移行していました。

緊急事態宣言下における当社の業務に関するお知らせ

tech.unifa-e.com

エンジニアのリモートワーク状況については、下記の記事を参考にしてください。

tech.unifa-e.com

システムの利用状況

以前、弊社エンジニアの島田が、緊急事態宣言下での保育施設利用状況について、本ブログにてまとめてくれたことがありました。

tech.unifa-e.com

このブログの中でも書かれている通り、3月~5月は園児さんの欠席率が大きく上がり、保育園の稼働率も例年の同時期と比べると大きく下がっていました。

何が難しかったのか

ここまで聞くと、システム負荷が低いだけだし、そんなに問題はないのでは、……とお考えになるかと思います。 が、実は裏側では、日々バタバタしていました。

難しくしていた原因その1:いつもと違う利用のされ方

平常時と異なる状況なので、いつもとは少々異なる負荷の傾向が現れていました。 具体的な例では、先に紹介した保育施設の利用状況のデータをまとめたブログ中でもご紹介した「欠席コメント欄への記入率違い」などが挙げられます。

f:id:unifa_tech:20200603160824p:plain
[再掲]緊急事態宣言前後でのコメント記入率

緊急事態宣言が発令された2020年4月7日以降で、明らかに利用の仕方が変わっていることが分かると思います。 この様な事例が、幾つか見受けられました。

システム保守をしているサーバーサイドエンジニアならば多くの方がシステムの利用状況、負荷状況を日常的に気にされていることと思います。 通常、システム利用の予測というのは、これまでの利用データ、負荷状況、利用者数の増加予測などを元として行っていきます。 しかしながら、新型コロナウイルスに対しての様々な新しい対応が日々、各所で行われる中では、これまでの利用データがあまり参考にならず、かつ今後の利用者数の予測を行うことは非常に難しいことでした。 基本的にシステムというのは、ある一定の想定条件下で正常に動くように各所のパラメータが調整されているので、その条件を外れれば正常に動かない箇所も出てきます。 事前に考えられる事態に対して対策はしていましたが、やはり事前には予測していなかった状況に直面しました。 それらのうちの幾つかの要素を原因としたトラブルに見舞われる中、地道にデータを分析し、暫定対応、恒久対応を行っていくことで事態の収束を行いました。

難しくしていた原因その2:全社的なリモートワーク

エンジニアのリモートワークはこれまでも行って来ていましたが、全社的に大多数の社員がリモートワークになるという状況は初めてのことでした。 どの会社でも同じだと思いますが、トラブルの発生の折には社内の関連部署への連絡が必須となります。 平時であれば誰かしら出社していたので、速報を口頭で伝えるということもしていたのですが、全員がリモートなのでSlackでの連絡が全てのきっかけになります。 元々エンジニアにリモート勤務が認められていた環境でしたので、Slackでの各所との話し合いは定着していたのですが、「口頭で速報を伝える」という手段を封じられるという状況はありませんでした。 もちろん、一番初めのきっかけ以降はリモートの会議体制に移行し、話し合いを行えますが、第一報はSlackです。 このことは、意外に緊急時に於いて、情報の速報性を妨げました。 というのも基本的にSlackは送ったからと言って必ずしもその場で読んでもらえるものでもないからです。

対策を考えてみる

緊急事態宣言が今後再び発令されるかは分かりませんが、少なくとも暫くの間はリモートでの勤務を継続することにはなるでしょう。 となれば、上記の困ったことを放っておく訳にはいきません。

いつもと違う利用状況に対して

必要となるのは、下記の要素だと考えています。

  • 問題が発生した際の検知の早さ
  • 影響の最小化に向けた取り組み
  • 適切な暫定対応を行うための早期の決断

このうち、「問題が発生した際の検知の早さ」に関しては、ログやシステム監視の検知条件の見直しなどを実施し、平時と異なる利用があった場合に検知が可能となるように改善を行いました。

「影響の最小化に向けた取り組み」については今回利用した暫定対策の手順等をまとめ、次回の類似事象発生時には即座に適用できるようにしました。 また、問題の原因分析、システムの状況がより分かりやすくなるように、検知ツール類の設定の見直しも行っています。 が、これに関しては実際は発生した事象ごとに必要な情報も異なってくるため、実際のところは発生ベースでの対応となってしまいます。

残る「 適切な暫定対応を行うための早期の決断」については、次項で詳細を記載する体制の整備の中で承認ルートの見直しを行っています。

リモート主体勤務時のトラブル対応体制の整備

トラブルが発生した際、「どこのチャンネルで」「どうやって」一報を流すのかについて、改めて整備と認識合わせを行いました。 これまでも基本的に一報を流すことはSlack上で行ってきましたが、自身のチーム内や直接的な関係者しかいないチャンネルを利用していることもあるなど、その方法が統一されていませんでした。 今回のことを機に

  • 一報を出す場所、メンション先、報告内容
  • システム利用者への連絡方法
  • トラブルによる緊急リリースが必要となった場合の承認ルート

の再整備を実施しています。

まとめ

今回は、今年2月以降で経験したことについて、備忘の意味も込めて記載してみました。 全く整備や準備が出来ていなかったという訳ではなかったのですが、非常時に於いてはほんの少し詰めの足りなかった部分が綻びとなり、顕著に形となって現れてくるものなのだなと感じています。 当たり前のことしか書かれていないのですが、当たり前のことが出来てないと非常時に困るんだ!ということを痛感した期間でもありました。 幸い、「もっとここが出来ていたらな。」という内容なので、今後も続くこの状況でもより安定してサービス提供を行えるように試行錯誤を繰り返して行こうと思っています。

自分たち自身も平常時と環境が異なる中、平常時以上のサービスレベルを保つと言うことはそれなりに苦労もありますが、一方で利用者の方から見て「動いていないと困るサービス」になれているのだ、と言うことが実感できた期間でもありました。 そのことは純粋に、有難いことだなと感じています。

ユニファでは現在も積極的に仲間を募集しています! ご興味のある方は、お気軽にお声がけください。

unifa-e.com

SORACOM API を Sandbox 環境でテストする

皆様こんにちは、ユニファの赤沼です。

弊社では「ルクミー午睡チェック」というプロダクトで SORACOM Air Sim を使用しています。通常はセルラーモデルの iPad mini での通信用途に使っていて、API の活用はまだまだ十分にはできていないのですが、最近 Ruby のスクリプトから API を利用する機会があり、Sandbox 環境でのテストも行ったので書いてみます。

SORACOM のサービスは API が充実

まず改めてのおさらいですが、 SORACOM のサービスは IoT 用途ということもあり、 API が充実しています。サービスの数もかなり増えており、コンソールも使いやすいのですが、API でも大半のことはできるようになっているのではないでしょうか。ドキュメントもしっかり提供されています。

dev.soracom.io

全ての API についてリファレンスも提供されています。リクエスト時のパラメータの内容やレスポンスの各項目についてはもう少し説明があると良いなとは思いますが、実際に認証と各APIの実行までリファレンスページから行うことができるようになっているので、実装に組み込む前に実際のレスポンスなどが確認しやすくなっています。

dev.soracom.io

テスト用の Sandbox 環境

実際に動かして試せる API Reference はとても便利なのですが、一つ気を付けないといけないのは、 API Reference のページから実行した内容は、実際の環境に対して実行されるということです。情報取得系の API であれば問題ないのですが、何か変更を加えたり、Sim の解約等まで行えてしまいますので、軽い気持ちで試したりするとあとで大変なことになります。また、実装に組み込む際にも開発段階ではテストが必要になると思いますが、そういう時のために SORACOM には Sandbox 環境が用意されています。Sandbox 環境で実行した内容は本番環境には影響しませんので、解約処理等の危険な処理も試すことができます。

dev.soracom.io

本番アカウントも必要

Sandbox環境は本番環境とは別環境ではありますが、本番環境にもアカウントを持っている必要はあります。そのため本番環境のコンソールから SAM(SORACOM Access Management)ユーザを作って認証キーを生成しておく必要があります。SAMユーザがいて認証キーさえあれば良いので、権限は何も与えなくてもOKです。間違って本番環境に対して実行してしまっても影響がないように、テスト用に何も権限のない SAMユーザを作っておくのが良いかと思います。

Sandbox 環境での準備: オペレータ作成

Sandbox環境にはオペレータは用意されておらず、またGUIもないので、まずはオペレータを下記APIで作成する必要があります。

sandboxInitializeOperator API https://dev.soracom.io/jp/docs/api_sandbox/#!/Operator/sandboxInitializeOperator

Sandbox専用の API もリファレンスページから実行できますが、今回は Ruby で下記のようなコードを書いて実行しました。 init メソッドの中で実際に API にリクエストを投げています。ちなみに使用するメールアドレスは過去に使ったものと重複するとエラーになりますので、作成時には新たなメールアドレスを使用する必要があります。これを実行すると Operator ID, API Key, API Token が取得できますので、以降の API 実行にはこれらを使用します。

#! /usr/bin/env ruby
require 'httpclient'
require 'json'
require './errors'

class SoracomSandbox
  attr_reader :api_base_url

  def initialize
    @api_base_url = 'https://api-sandbox.soracom.io/v1'
    @client = HTTPClient.new
  end

  def post(url:, body: {} , header: {})
    res = @client.post(url, body: body, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def check_status(res)
    return if HTTP::Status::successful? res.status

    res_json = JSON.parse(res.body)
    msg = "CODE: #{res_json['code']} MSG: #{res_json['message']}"
    raise SoracomApiError, msg
  end

  def init(email: nil, password: nil, auth_key_id: nil, auth_key: nil)
    request_body = {
      email:                 email,
      password:              password,
      authKeyId:             auth_key_id,
      authKey:               auth_key,
      registerPaymentMethod: 'true'
    }.to_json
    res_json = post(url: "#{@api_base_url}/sandbox/init", body: request_body,  header: { 'Content-Type' => 'application/json' })

    @operator_id = res_json['operatorId']
    @api_key     = res_json['apiKey']
    @token       = res_json['token']

    @request_header = {
      'Content-Type'      => 'application/json',
      'X-Soracom-API-Key' => @api_key,
      'X-Soracom-Token'   => @token
    }

    return @operator_id, @api_key, @token
  end
end

Sandbox環境での準備: Simデータ作成

続いてテストに使用する Sim のデータを作成します。実際に Sim を契約しなくてもいくらでも Sim を用意できるのが Sandbox 環境の良いところです。 Simデータを用意するには、架空のSimを作成してからそれを登録するという手順を踏みます。架空のSim作成には Sandbox 用の API が用意されています。

Sim作成: sandboxCreateSubscriber https://dev.soracom.io/jp/docs/api_sandbox/#!/Subscriber/sandboxCreateSubscriber

Simの登録は本番環境と同様の API で行います。

Sim登録: registerSubscriber https://dev.soracom.io/jp/docs/api/#!/Subscriber/registerSubscriber

先ほどのクラスに下記のようなメソッドを追加して実行します。

  def create_sims(sim_count)
    sims = []
    sim_count.times do
      res = @client.post("#{@api_base_url}/sandbox/subscribers/create", header: @request_header)
      res_json = JSON.parse(res.body)
      sims << {
        imsi: res_json['imsi'],
        msisdn: res_json['msisdn'],
        registration_secret: res_json['registrationSecret']
      }
    end

    sims.each do |sim|
      request_body = { registrationSecret: sim.fetch(:registration_secret) }.to_json
      post(url: "#{@api_base_url}/subscribers/#{sim.fetch(:imsi)}/register", body: request_body, header: @request_header)
    end
  end

実際にはさらにオペレータ作成とSimデータの作成をラップするコードを用意して、それを毎回 Sandbox 利用時に実行しました。

#! /usr/bin/env ruby
require 'optparse'
require './soracom_sandbox'

opt = OptionParser.new

params = {}

opt.on('-e EMAIL')              {|v| params[:email]       = v }
opt.on('-i AUTH_KEY_ID')        {|v| params[:auth_key_id] = v }
opt.on('-k AUTH_KEY')           {|v| params[:auth_key]    = v }
opt.on('-n NUM_OF_SUBSCRIBERS') {|v| params[:num]         = v.to_i }

opt.parse!(ARGV)

sandbox = SoracomSandbox.new
operator_id, api_key, token = sandbox.init(
  email:       params[:email],
  password:    'superStrongP@ssw0rd',
  auth_key_id: params[:auth_key_id],
  auth_key:    params[:auth_key]
)

sandbox.create_sims(params[:num])

puts "#{operator_id},#{api_key},#{token}"

テストしたい API を実行する

ここまでで Sandbox 環境の準備ができましたので、あとは本番と同様の API を Sandbox 環境向けに実行すればテストをすることができます。 例えば Sim 情報の取得は下記のようなメソッドを書いて実行します。

  def subscribers(status: [], limit: 100)
    parameter = "limit=#{limit}"
    if not status.empty?
      parameter += "&"
      parameter += URI.encode_www_form(status_filter: "#{status.join('|')}")
    end

    get(url: "#{@api_base_url}/subscribers?#{parameter}", header: @request_header)
  end

これで事前準備で登録された Sim の情報が分かりますので、 Activate なども実行してみることができます。

  def activate_subscribers(imsis: [])
    subscribers = []
    imsis.each do |imsi|
      url = "#{@api_base_url}/subscribers/#{imsi}/activate"
      subscribers << post(url: url, header: @request_header)
    end

    subscribers
  end

こちらもラップするコードを書きました。 API_KEY と TOKEN はオペレータ作成時に取得したものを使います。

#! /usr/bin/env ruby
require 'csv'
require 'optparse'
require './soracom'

opt = OptionParser.new

params = {
  api_key:    nil,
  token:      nil,
  file:       nil,
  production: false
}

opt.on('-f SUBSCRIBERS_CSV_FILE') {|v| params[:file] = v }
opt.on('-a API_KEY')   {|v| params[:api_key]    = v }
opt.on('-t TOKEN')     {|v| params[:token]      = v }
opt.on('--production') {|v| params[:production] = true }

opt.parse!(ARGV)

soracom = Soracom.new(production: params[:production])
soracom.set_request_header(api_key: params[:api_key], token: params[:token])

subscribers = CSV.table(params[:file], { converters: nil })
imsis = subscribers[:imsi]

puts soracom.activate_subscribers(imsis: imsis).to_json

activate_subscribers メソッドや subscribers メソッドは前述の SoracomSandbox とは別の、本番と同様の API をまとめたクラスに実装しましたので参考までに抜粋して載せておきます。

#! /usr/bin/env ruby
require 'httpclient'
require 'json'
require 'uri'
require './errors'

API_BASE_URL         = 'https://api.soracom.io/v1'
API_BASE_URL_SANDBOX = 'https://api-sandbox.soracom.io/v1'

class Soracom
  def initialize(
    production:  false
  )
    @production   = production
    @client       = HTTPClient.new
    @api_base_url = production ? API_BASE_URL : API_BASE_URL_SANDBOX
  end

  def production?
    @production
  end

  def set_request_header(api_key:, token:)
    @request_header = {
      'Content-Type'      => 'application/json',
      'X-Soracom-API-Key' => api_key,
      'X-Soracom-Token'   => token
    }
  end

  def post(url:, body: {} , header: {})
    res = @client.post(url, body: body, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def get(url:, header:)
    res = @client.get(url, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def check_status(res)
    return if HTTP::Status::successful? res.status

    res_json = JSON.parse(res.body)
    msg = "CODE: #{res_json['code']} MSG: #{res_json['message']}"
    raise SoracomApiError, msg
  end

  def subscribers(status: [], limit: 100)
    parameter = "limit=#{limit}"
    if not status.empty?
      parameter += "&"
      parameter += URI.encode_www_form(status_filter: "#{status.join('|')}")
    end

    get(url: "#{@api_base_url}/subscribers?#{parameter}", header: @request_header)
  end

  def activate_subscribers(imsis: [])
    subscribers = []
    imsis.each do |imsi|
      url = "#{@api_base_url}/subscribers/#{imsi}/activate"
      subscribers << post(url: url, header: @request_header)
    end

    subscribers
  end
end

まとめ

SORACOM のサービスの魅力は単に Sim での通信というだけでなく、関連する様々なサービスや API を活用することで、よりコスト効率を良くしたり、プロダクト提供までの実装コストの削減や運用時の柔軟性をあげることができるというところかと思います。 API を活用していくためにも、 Sandbox 環境でしっかりテストして安全に利用したいものです。

また弊社では複数ポジションを募集中ですので、興味ある方は是非ご連絡ください! unifa-e.com

Slackのチャンネル(CH)改善しようぜ!

こんにちは。 ユニファでインフラエンジニアをしているすずきけいたです。

弊社の様なスタートアップ企業だけではなく、 広く使われるようになってきているSlack。 外部のアプリやスケジュールなどの連携もできたり、色々便利ですよね。

ただ自由で便利な半面スペースに存在する人数が400人以上になり、【この話題のCHどこ?】【CH数が多すぎ】【ここ外部協力の方いる?】などと悩む事も多いかと思います。 そこで今回は弊社のSlackの運用ルールやプレフィックス、今後の改善、CH整理に関しても紹介しようと思います。


目次


現在のCH数把握

 弊社ではSlackを2015年1月21日から使用しているようで、(私の入社前の事なので弊社CTO赤沼の履歴を遡った)かなり導入時期としては早いですよね。

 現在パブリックChだけでも700、プライベートCHを加えると1000は越えると思われます。

SlackCHの命名ルール

 当初SlackCHの命名ルールは厳格なものなく、自由に作成していたのですが、 CH数が多くなってきた事や、プレフィックス(下記記事参照)が登場した事で、弊社でも命名ルールを設けました。

 SlackCHはサイドバー階層化(下記記事参照)などを除き基本アルファベット順に並ぶため、【接頭】でCHの性質を分類しています。

◆サイドバー階層化に関する記事◆

internet.watch.impress.co.jp

◆プレフィックスに関する記事◆

internet.watch.impress.co.jp

slack.com

f:id:unifa_tech:20200804093901p:plain  画像の様なプレフィックスを付け加える様にしております。

 コーポレート本部は【#corp】、外部協力者を含むCHは【#ex】、プロジェクトに関係するものは【#pj】、 一時的なCHは【#tmp】、趣味やチャットなど仕事とは関係ない息抜きCHは【#zz】などになっております。

 プレフィックス以外にもCH数は少ないですが以下のようなものを接頭につけております。  UniFaに関連する情報に届けたいものは【#unifa】、infraは【#infra】、写真事業関連は【#photo】など 誰にでもわかりやすいCH命名にするようにしています。

CH命名時の問題点

 プレフィックスなどにも弱点があり、上記の命名ルールに従って複数のプレフィックスに該当する場合、CH名がお経の様に長くなりがちです。

例:【外部協力者を含むプロジェクトの一時的なCH】→【#ex_temp_pj_~】

 あるあるですよね。名前が最後まで見えない問題です。  SlackのCH表示ペインは有限のため、SlackCHが長いとどういった内容の話をしてるかなど、中に入らないと確認できなかったりします。

今後のCH命名改善

 下記表記は会社として決まったことでなく、あくまで色々な会社様のSlack関連記事を読んだ上での私一個人のアイディアです。

・プレフィックスを短くする(2文字統一など)。

   例) #corp → #co、#photo → #ph

・3文字目にCHユーザーのレベル感を数字で表す。

  1. 社員は入らなくてはならない
  2. 社員のみ
  3. 社員及びパートナー
  4. 誰でも入れる
  5. 雑談など

   例) #corp_enployee_must~ → #co1

・4文字目に外部協力者の人が含まれているかを判別する。

 y → いる  n → いない

   例) #ex_corp_everyone_talk → #co4y_talk

 このように決めを細かくし浸透させることで、そのCHはどういうものかというのが短く伝わりやすく、表示ペイン内にも収まりやすい。

 また同時に外部の人間はわかりにくいと言う効果もあり、上場を目指すスタートアップ企業としても、セキュリティ面を考えるエンジニアとしても効果が期待できそうだ。

今後のCH整理について

 現在700以上あるパブリックCHもその多くは発言がなく、参加者もいないものが散見される状態で、今までCHを作成したままで放置になっている【ゾンビCH】を一掃しようと画策しております。

 ここも決めの問題になるのですが【半年間発言がないCH】、【参加者が0名のCH】をAPIを使って定期的に強制アーカイブを行おうと思っております。

 そのアーカイブに関してはまた別の記事で発表できたらいいかなと思います。

弊社SlackCH一部を解説

【#unifa_all_must】

 UniFaに関わる全員に伝えるmustな情報CH

【#pj_廊下を写真でステキ空間にしようの会】

 我が子の写真を定期的に募集、投票、掲示までするCH。率先的に行っていて、とてもUniFaらしい。

【#tmp_check_ozo_202007】

 2020年7月のOZO(勤怠報告システム)を提出したら抜けるCH。みんなの悲鳴が聞こえます。誰が残っているのかわかりやすくていいですよね。

【#zz_times_keita】

 私の心のつぶやきやスケジュールなどリマインドCH。ミーティング15分前!とか刻めて役にたってます。

【#zz_we_love_coffee】

 コーヒー大好きな皆さんが集まるCH。レベルが高いコーヒー談義してます。沼です。

おわりに

小さなノウハウの集積ですが、ゼロから考えるのって面白いですよね。 SlackCHの整理に悩んでいる方の参考になれば幸いです!

入社して3週間が経ちました。プロダクトマネージャーの仕事とは?

f:id:unifa_tech:20200705175128p:plain はじめまして!ブログ初投稿です、プロダクトマネージャーの田嶋と申します。

ユニファへ6/16に入社して、約3週間が経ちました。前職から目まぐるしく環境は変わりましたが、とても刺激的で毎日楽しく仕事をしています!

今回の投稿では、スタートアップ企業のプロダクトマネージャーの仕事ぶりについてご紹介します。
面白そうー!と興味を持っていただけたら、まだまだプロダクトマネージャー採用もやってますので、ぜひよろしくです!笑

【目次】

■0.プロダクトマネージャーとは

私は、職種:プロダクトマネージャー(PdM)として採用いただきました。プロダクトマネージャーの仕事は、シンプルに突き詰めると1点に集約されると思います。
エン・ジャパンのPdM,岡田さんのツイートを参照。

ちなみに前職でも同職種でしたが、システムサイドに軸足を置きすぎていたため、よりビジネスサイドも含めて一気通貫でプロダクトに関わりたい!ということも転職理由のひとつでした。
なぜ保育業界を選んだかは、私のnoteにまとめたので、ご参考までにどうぞ。

note.com

■1.入社初日のこと

入社以前から少し説明を受けていた案件について、当日もおさらいし、さっそくプロダクトマネージャーとしてアサインされました。補佐として仕事に慣れるような見習い期間は一切ありません。
少し不安はあったものの、荒削りでも経験を積みながら前へ進みたいと思っていたので、任せていただけたことを本当にありがたく思っています。

またCEOの土岐さんからは、「社運をかけたプロジェクトですね..!」とコメントいただき、、いま冷静に振り返って、入社したての若造にそんな重要なプロジェクトを任せるか?とは思いつつも、とても刺激になったので、辛い時はこのお言葉を思い返し踏ん張りたい所存です!

上のツイートでも少し示唆していますが、この後どんな状況でどう仕事を進めているかを、次でご紹介したいと思います。

■2.何も決まっていない、全て自分で考える

私がアサインされたプロジェクトは、保育現場の ”とある業務” における稼働削減+質向上を狙う新規プロダクト開発です。
ちなみに、ユニファでは他にも同等の目的感で保育現場に様々なプロダクトを提供し、「スマート保育園」と銘打ち、保育現場のICT化を積極的に推し進めています。(どれもロゴがかわいい、、😆)

f:id:unifa_tech:20200705174924p:plain
ユニファのサービスたち

私のプロジェクトは、入社以前から課題概要のみ共有を受けていましたが、プロダクトの輪郭や方向性は一切決まっておらず、注力する課題の特定から始めてくれ。というオーダーでした。

はじめに担当するのは、既存プロダクトのエンハンスやグロースを想定していたので、これは私にとっては嬉しい誤算でした。笑
後々は本当にゼロイチの新規事業をやりたいと思っており、そのための経験を前倒しで積めると思ったからです。

また、ざっくりとしたスケジュール感と予算枠は伺ったものの、それすら費用対効果で応相談とのこと。スケジュールドリブンはいかん!と思いつつも、いつまでに課題をクリアにし、いつまでに解決策を検証し、と自分でスケジュールの目安を組むところから始めました。

■3.誰もいない、自分で巻き込む

現在、専任のプロジェクトメンバーは、私1人です。
入社初日は、上長が一緒に挨拶回りをしてくれたりと付き合ってもらいましたが、もう帰り際には「明日から何やれば良いか想像ついてる?」って感じでしたw

今はまだ情報収集の段階なので、インプットとアウトプットレビュを繰り返しながら、手探りで前進しているような状況です。

インプットについては、様々な文献も活用しましたが、ユニファには元保育士や元幼稚園教諭の社員さんがいらっしゃる恵まれた環境なので、インタビューでお話を伺ったりもしています。また最近は、実際に園へ電話を掛け、オンラインインタビューや視察訪問なども始めました。
本当に現場起点で、"顧客と距離の近い幸せ" を感じているので、こうしたユニファの良さは、また別の機会に書こうと思います(^^)

アウトプットについては、レビュコメントを頂くことで案件や保育現場への自身の理解を深める狙いと、さすがに入社直後に在宅で1人で孤独すぎてアレ的な気持ちもあって、積極的に公開するようにしています。
ただ皆さんお忙しく、私のアウトプットを気にかけて頂ける状況でもないので、勉強会と称して関連しそうな方々をお誘いしてみたり、案件定例を作ってみたりと、ちょこちょこ動き回ってアピールするようにしています。
そうした方々との将来のチームを想像しながらも、孤独な闘いはまだしばらく続きそうです。笑

■4.求められるのは成果だけ

まだまだプロジェクトの初期フェーズに限定した動き方ではありますが、ここまでがざっくりと今の仕事の現状です。ただこの短期間でも、私がこれまでで初めて強く意識したことがありました。それは"競合"、そして"売る"という難しさです。

ビジネスでは当たり前のことですが、恥ずかしながらこれまで経験がありませんでした。
競合を差し置いても選んでもらわなければ自社の売上は立ちません。それがもし部分的な責任範疇であれば「営業の売り方が悪いんだよ」「そもそもプロダクトがイケてないから〜」と言い訳が効くでしょう。
ただ、プロダクトマネージャーの仕事は、その全てを任されています。

責任の大きさに萎縮してしまいそうになりながらも、「そうそう、この経験をしたくて転職したんだ!」と、武者震いするような感覚でいます。

ただ、仕事の進め方はあくまでシンプル。
再掲。

これを突き詰めるプロダクトマネージャーとなるべく、ひたすら顧客と向き合っていく。それのみ。この初心を忘れずにこれから頑張りたいと思います。

また当記事では私自身の現状に焦点を当てたため、会社や社員のみなさんについてあまり触れませんでしたが、相談すれば親身になっていただき、保育業界を本当にを良くしていこうという気概を持った方々ばかりです。
この環境に感謝しつつ、これからもユニファ一丸となって、「家族の幸せを生み出す あたらしい社会インフラを 世界中で創り出す」に向かって行きたいと思います。 そして改めて、採用やってますので、お声掛けお待ちしてます!!

www.wantedly.com

以上、ありがとうございました!

BUSINESS PERSON'S GUIDE TO DESIGN DRIVEN MGMT.

デザインチーム所属の三好です。

コロナの影響でほとんど外出することがなくなり、本を買い漁って家に籠っていたらいつの間にかこの生活が心地良くなってきました。

最近はデザイン関連のものの他に、写真集や政治に関する書籍、聖典「ヴェーダーンダ」の指南書などを読んでいます。

半分は個人の趣向ですが、この先ずっと創作に携わるのなら専門外の世界もなるべく取り込んでおけば、最終的にアウトプットする段階でそれは必ず糧となるはずです。(たぶんヴェーダーンダでさえも…なるはず…なればいいな)

そのための膨大な節操のないインプットが今回の引き籠り生活で多少は実行できたと思います。

ビジネスパーソンのための"デザイン経営"ハンドブック

f:id:unifa_tech:20200701171803p:plain

これは特許庁から無料で発行し公開している色んな意味で衝撃的な資料です。

若林恵さんという尖った感性を持つ著名な編集者が携わっていて、それが国から出ているということに驚きますし、ビジュアル同様に内容も素晴らしいのですがなんとなく目を通すにはあまりにも濃密でやや骨が折れる内容ではあるので、ここでは特にたくさんの人の目に触れてもらいたいと感じた2つの章のみ添付します。

これはデザイナーのための資料ではなく、デザインに関わる人に向けた資料です。 全てのビジネスパーソンに関わる内容と言っていいと思います。

普段から悶々と感じてはいても中々自分自身のスキルでは言語化が難しい部分を、高い次元で表現してくれているので原文をそのまま共有させていただきます。

f:id:unifa_tech:20200701172057p:plain

f:id:unifa_tech:20200701172120p:plain

興味のある方はこちらから全ページPDFで無料ダウンロードできます(全16頁) https://www.meti.go.jp/press/2019/03/20200323002/20200323002-1.pdf

考察

この数年間でデザインの多様化は猛スピードで進んでいて、もはやデザイナー自身でさえ一体何がどこまでできればデザイナーと言えるのか、わからなくなっている人もいると思います。少なくとも私はそうですし、個人的な経験の浅さもありますが自分自身がデザイナーと言われることには違和感しかありません。

ただ曲がりなりにもインハウスデザイナーとして仕事に携わっていたら、今世の中に求められているものは論理的思考力であるということには気がつきました。

例えば今とても重要視されているUI(ユーザーインターフェース)デザインですが、それには必ずしっかりとしたガイドラインが存在していて、そのルールに沿って作成していけばなんとなくそれらしいデザインが完成します。

しかしそこに留まってしまうとデザイナーとしての自力はおそらく育まれない。その先へ進むには高い思考力と、また作業時間も大幅に必要となります。

なによりインハウスデザインにそれが求められているか、追求できる環境にあるかという大前提の問題がありますが、デザイナーという種族は同じ場所に立ち止まることが許されず、常に個人としての行き先を見据えて行動しなければならないと考えると、今置かれている状況だけを基準に判断することはとても危険です。

ただこれを実行するのは本当に難しくて、そもそも自分の身に置きかえてみるとなんとなくのデザインを作ることさえ四苦八苦していますが。

ところでインハウスデザインの仕事は多岐に渡ります。

WEB、アプリの他にもバナー、チラシ、パンフレット、広告や名刺、必要であれば写真撮影やイラスト、そしてコードも書きます。 その中で紙媒体、グラフィックデザインと呼ばれる分野にはUIデザインのようなガイドラインは存在していない為、自ら情報を整理して最善の方法を探りながら要素を組み立てていきます。それは考える力、個としてのデザイン力が身につきます。

デジタルファーストの現代ではどうしてもグラフィックデザインの重要性は低く見られがちですが、思考力を引き上げるためにはそれを極端に軽視するべきではないと個人的には感じています。

まとめ

その論理的思考力というのが上の資料で語られているデザイン思考(デザインシンキング)ということになると思います。

6章ではデザイン思考の重要性、それは常にオープンエンドであり続けること、予想外の発見を柔軟に受け入れられる思考回路を持つことなどがこれからは必要であると書かれています。”解のない世界に、勇気を持って飛び込む”という言葉が印象的です。

7章では企業の文化性を表現することの重要性、ビジネス上の意味の発想からの脱却についてなどかなり大胆な話を展開しています。そしてインハウスデザイナーとしての価値や使命についても触れてあります。

デザイン思考という言葉のとおり、それはデザイナーが常に普段から意識して仕事に臨んでいる思考回路のことであって、その道のプロフェッショナルでもあります。

未熟な自分自身のことは棚に上げちゃいますが、弊社のデザインチームには基本的にその思考力が備わっていますので、このチームからデザイン思考がもっと広いフィールドへ浸透していくための足がかりになればいいと思いますし、また直接的な数値が見えにくいなどの様々な理由で過小評価されやすい現状が少しずつでも改善されていくことを願っています。

引用元:特許庁はデザイン経営を推進しています | 経済産業省 特許庁

機械学習による時系列データの分類

こんにちは、研究開発部の浅野です。あるプロダクトで、得られる時系列データが正常か異常かを判定しているところがあります。現在はシンプルなルールベースの手法で判断をしており、その精度は約80%と改善が必要です。今回はこれを向上していきたいと思います。幸い正常例も異常例もデータが比較的多くあるため、機械学習の手法を用いることにします。

使用データ

正常例と異常例を約1,000例ずつ手作業で集めました(データ収集, クレンジング, ラベリングは辛いときもありますが大事なタスクの一つです)。そのうち80%を学習用データ、20%をテスト用データとしました。下記の例を見るとすぐに規則性を見出すのは難しそうですが、異常例の方が全体的に値が高めに出る傾向にはありそうですね。

f:id:unifa_tech:20200629172606j:plain:w450
使用したデータの例。上段:正常、下段:異常

手法

決定木ベースのアンサンブル学習手法で、汎化性能が高く動作も高速なランダムフォレストを使用します(コードはscikit-learnを使えば自明なので特に示していません)。とかくブラックボックス(判断根拠がわかりにくい)と言われがちな機械学習ですが、決定木ベースの手法を用いるとそのあたりはかなり軽減することができます。

データをざっと眺めてみると、最大の値とばらつき具合が判断に大きく寄与しているように感じたので、まずは最大値と標準偏差の値を特徴量として分類器を作成してみます。2つしか特徴量がないので木の深さの最大値は2、決定木の数は3として学習を行いました。

f:id:unifa_tech:20200629152426p:plain:w220:left
その結果、混同行列は左のようになり、precision, recall, f1-scoreとも91%となりました。この2つの特徴量だけでもかなり分類精度が上がっています。ちなみに、混同行列の対角成分の数字が大きいほどデータを正しく分類できているということを意味します。

特徴量を増やす

もっと精度を高めるため、関連しそうな特徴量を追加していきます。具体的には、最小値、平均値、中央値、歪度(skewness)、尖度(kurtosis)といったデータの分布に関わる統計指標です。特徴量が増えるので決定木の深さを最大3,数を10として学習したところ次のような結果になりました。

f:id:unifa_tech:20200629162220p:plain:w220:left
混同行列は左のようになり、異常例の分類精度はprecision: 94%, recall:95%, f1-score: 94%とさらに上昇しました。


f:id:unifa_tech:20200629162304p:plain:w370:right
右の図は各特徴量の重要度です。先ほど使用した最大値と標準偏差に加えて、歪度や平均値なども分類に効いていることがわかります。

f:id:unifa_tech:20200629163904p:plain:w250:left さらに、「最大値ー平均値」や「平均値ー最小値」などのようにより直接的に分布の歪みを捉えることができるような特徴量を追加したり、汎化性能を高めるために不要な特徴量を削除することで混同行列が左のようになり、最終的に異常なデータの検出においてprecision: 94%, recall:97%, f1-score: 96%まで精度を改善することができました。このあたりの特徴量エンジニアリングは機械学習に携わる者として腕の見せ所の一つです。

決定木の可視化

ちなみに、決定木を可視化するためにdtreevizという素晴らしいライブラリがあります。最大値と標準偏差のみで分類を行った際に学習した決定木の一つを可視化した例が下記です。標準偏差が0.09未満かどうかで分類した後に最大値によってさらに分類を行い、それぞれの分岐で正常例や異常例がどんな割合で含まれるかがよく分かります。学習したモデルの妥当性の確認や、推論結果の根拠を説明する際にとても役に立つと思います。

f:id:unifa_tech:20200629165810j:plain:w500
dtreevizを用いた決定木の可視化の例

まとめ

ランダムフォレストを用いて時系列データの分類精度を80%から96%まで高めることができました。実はこの後にXGBoostなどのブースティング手法や、深層学習であるLSTMを用いてさらなる精度改善を図る予定でしたが、ブログの執筆期限がすぎているのでランダムフォレストで十分な精度が達成できたのでこれで終わりにします。難しい問題をいかに簡単に解くかが大事。