RareJob Tech Blog

レアジョブのエンジニアによる技術ブログです

オンライン英会話のDMPを支える技術

インフラエンジニアとして採用してもらい、4月からはマイクロサービスでの基盤を作るチームのリーダーをさせてもらってます塚田と申します。 引き出しの中はランニングのシューズや着替えやサプリやらで溢れかえっており、ランナー版の「釣りバカ日誌」のハマちゃんだと思っていただければよろしいかと思います。

3月までEdTech(Education 教育 とTechnology テクノロジー)の領域でデータをさらなる活用するべく、DMP(Data Management Portal)プロジェクトを立ち上げリードしてきました。 今回はEdTechの領域におけるDMPについてお話ししたいと思います。

DMPとは

自社サイトのログデータなどを一括管理、分析し最終的にアクションプランの最適化を実現するためのプラットフォームで、主に広告業界で使われるデータのプラットフォームです。

このDMPを構築する前にも、我々は独自の解析プラットフォームを持っていました。 しかし、様々な課題があったのでここで一度作り変える判断をしました

旧データ解析基盤の課題

レアジョブ英会話のサービスがはじまり、今年で12年目になります。 12年前にサービスがローンチして程なく旧データ解析基盤が作られ、アーキテクチャこそシンプルですが、そこに肉付けに肉付けが重なり、旧データ解析基盤は巨大肉団子状態になっていました。

f:id:sumito1984:20190513183313p:plain
旧データ解析基盤

なにより辛いのがソースコードにコメントが無いため、作り上げられた当時どのような思想のもとその計算式に至ったのか、今の担当者である我々から見てサッパリわからないという問題がありました。

また、レッスンで使った教材の統計や、売上情報、KPIなどの集計が日付が変わった瞬間バッチ処理として1つのサーバの上で大量にさばいておりましたが、 サーバはマルチコアではあったものの、集計が多すぎるため処理が直列になってしまい、何時間たっても処理が終わらないという課題がありました。

それでこの度、旧データ解析基盤を改修していくのではなく、根本的に作り直す方向に舵を切りました。

DMPのアーキテクチャ

アーキテクチャは以下の通り。参照先DB、計算処理、格納先、描写をそれぞれ分け疎結合に作り替えました。

f:id:sumito1984:20190513183220p:plain
rarejob DMP

当初データベースからデータを持ってきてBigQueryに入れるだけであればembulkで進めていたのですが 複雑なクエリになりすぎるメンテが難しくなるという課題があったので、中間データベースに一時的に出力させるようにしました。 登場人物は増えたものの、そのおかげで抽出処理のコードがシンプルになりました。

計算処理はコンテナベースで作っています。 コンテナイメージをECRへpushし、fargateのタスクスケジュールとして起動しています。 パスワードのようなセキュアな情報はAWS Systems Manager パラメータストアに保存しており、限られた人だけが見られるようにしています。 ちなみにタスクは以下のようなjsonで作っております。

{
    "executionRoleArn":"arn:aws:iam::123456789:role/DMP_RDS",
    "containerDefinitions":[
        {
            "logConfiguration":{
                "logDriver":"awslogs",
                "options":{
                    "awslogs-group":"/ecs/dmp-student",
                    "awslogs-region":"ap-northeast-1",
                    "awslogs-stream-prefix":"ecs"
                }
            },
            "entryPoint":[
                "sh"
            ],
            "portMappings":[

            ],
            "command":[
                "/var/www/dmp/execute.sh"
            ],
            "cpu":1,
            "environment":[
                {
                    "name":"ENV",
                    "value":"prd"
                },
                {
                    "name":"ORG_DB_USER",
                    "value":"dmp"
                },
                {
                    "name":"CLASSNAME",
                    "value":"Student"
                }
            ],
            "ulimits":[
                {
                    "name":"cpu",
                    "softLimit":0,
                    "hardLimit":0
                }
            ],
            "mountPoints":[

            ],
            "secrets":[
                {
                    "valueFrom":"DMP-DB-PASSWORD",
                    "name":"DB_PASSWORD"
                }
            ],
            "memory":2048,
            "memoryReservation":2048,
            "volumesFrom":[

            ],
            "image":"123456789.dkr.ecr.ap-northeast-1.amazonaws.com/dmp/extract",
            "interactive":true,
            "essential":true,
            "pseudoTerminal":true,
            "readonlyRootFilesystem":false,
            "dockerLabels":{
                "KeyName":""
            },
            "privileged":false,
            "name":"dmp-student"
        }
    ],
    "placementConstraints":[

    ],
    "memory":"2048",
    "taskRoleArn":"arn:aws:iam::123456789:role/DMP_RDS2RDS",
    "family":"dmp-extract-student-task",
    "requiresCompatibilities":[
        "FARGATE"
    ],
    "networkMode":"awsvpc",
    "cpu":"1024",
    "volumes":[

    ]
}

その後、以下のコマンドでタスクを再定義しています。

aws ecs register-task-definition --cli-input-json file://student.json 

DMPを構築する上での課題

参照先データ

DMPを作っていく上で、リアルなデータは必須になります。 本番の生のデータを元に開発したいものの、多くの開発者には見せたくないデータもあります。 直接本番環境に繋いでもらうわけにもいきません。 一方で、ステージングや開発環境で作るにしてもDMPが実行する膨大なSQLを実行すると、その他のプロジェクトに影響を与えてしまう恐れがあり、まずは環境を整理する必要がありました。

膨大すぎるレコード

データベースの中にはサービス開始時から蓄積された12年分のログがあります。 とにかく重く、indexを貼っても許容できるまでのレスポンス速度まで改善されません。

既存のデータベースの複雑性

やはり12年も継ぎ接ぎしてきたデータベースはとにかく複雑になっています。 同じようなデータを複数箇所に持っていて、どっちが正しいかわからない状態のものもあります。

fargateでのデバッグ

日付がかわったタイミングで大量の処理を行いため、EC2のように処理の数だけサーバを立てて処理するようでは、処理していない間コストがかさみます。 そのためfargateを用いて処理しているときだけ課金させるようにしました。 しかし、fargateはコンテナインスタンスを管理しないため、コンテナインスタンスSSHし、docker execなどでコンテナ内部でシェルを起動することができません。

解決したこと

参照先データ

本番環境のデータベースのバックアップを取得し、見せたくない情報をマスキングした上でスナップショットを取得しました。 ステージング環境へ毎日スナップショットを共有させ、ステージング環境で復元するようにしました。 こうすることで開発者がマスキングされたデータを参照することができ、リアルな数字がステージング環境でも見えるようになりました。 また、この復元したデータベースはDMPでのみ使うものとしました。仮にどんなに重いクエリを投げても業務影響は与えません。 データの解析は実際のデータを元に動かさないと正しいのか判断できないことがあるため、この手法は有益でした。

膨大すぎるレコード

データベースの作り直しを行い根本対応とするべきと考えましたが、今回そこまでの時間と労力は割けられません。(それに直したいところは他にもたくさんあります) そこでDMP用に解析に必要な分だけ抽出し、新規テーブルを作り必要なだけinsertさせ、そちらを利用することにして高速化を図りました。

mysql -u$dbuser -p$dbpass -h$dbserver $databasename -e "CREATE TABLE registration_recent LIKE registration";
mysql -u$dbuser -p$dbpass -h$dbserver $databasename -e "INSERT INTO registration_recent SELECT * FROM registration where lesson_date > (CURRENT_DATE() - INTERVAL 30 DAY)";

既存のデータベースの複雑性

同じようなデータが複数ある場合、とにかく過去のロジックを洗うしかありません。

しかし、先々これでよいか疑問に思っています。このままSQLを駆使してデータベースを検索していくのではなく、 マイクロサービスで箱を用意し、イベントドリブンでデータを入れていくよう抜本的なところから構成を変更していきたいと思っています。

fargateでのデバッグ

コンテナに穴を開け、sshを許可させるやり方も頭をよぎったのですが、コンテナの思想に合ってないと思いそれは行いませんでした。 現在行なっているのはCloudWatchでログを出力させ、さらに詳細の情報が欲しい場合は同一セグメントにEC2インスタンスを立てて、fargateで動かしている同じコンテナを動かし、docker execすることで再現させるようにしています。

なぜこんなに頑張るか

外国語の習得はすぐにマスターできるようになるようなものではなく、マラソンのトレーニングのように習慣化させ長期的に取り組んでいく必要があります。

村上春樹さんの著書「走ることについて語るときに僕の語ること」からの抜粋です。

大事なのは時間と競争をすることではない。どれくらいの充足感を持って42キロを走り終えられるか、どれくらい自分自身を楽しむことができるか、おそらくそれが、これから先より大きな意味をもってくることになるだろう。

つまり、いかに利用者に充足感を与え、楽しみながら英語学習というマラソンを走り切ってもらうことが、我々が提供すべきことになると思ってます。 英語学習者がランナーであれば、我々は伴走者であり、時にはコーチでありたいと思ってます。 オンライン英会話のパイオニアとしてデータは十分手元にある。データの中に答えがある。故に我々はDMPを軸にデータを活用していくことで、利用者に充足感を与え走り切ってもらうことができると考えています。

また、現在はまだDMPは社内だけに閉じてプライベートなものとして使っていますが、将来的には協業でのシナジーなども考えるとパブリック向けにも作り公開するという未来もあるかもしれません。 そのステージになると今以上にデータが集まり、rarejobのサービスミッションである「日本人1,000万人を英語が話せるようにする。」の実現を早め、新たな局面で英語教育に貢献できると考えています。

Swift 3 からSwift 4.2 にリプレイスとリファクタリングした話しを紹介します。

開発本部APP・UXチームの玉置です。主にiOSAndroidの運用を担当しています。
先月の4月からレアジョブに入社しました。
チーム内で前回の記事を投稿したチームリーダーの羽田の事を社内で浸透していない「ジャンボさん」と呼んでいる数少ないメンバーです。

rarejob-tech-dept.hatenablog.com

それまではフリーランスとしてモバイルアプリのエンジニアをしていました。
レアジョブ(RareJob)アプリに入社してすぐに取り組んだことがiOSのレガシーとなっている技術負債の返済です。
その名もSwiftとXcodeのバージョンを引き上げるというリファクタリング作業です。
いきなり大きなプロジェクトを任されましたが、約2週間の作業で事を終えることができ今ではSwift 4.2で開発しています。
今回はそのSwiftのバージョンを上げる事を通して得られた知見について紹介したいと思います。

レアジョブアプリ自体は2016年、つまりちょうど3年前にリリースされました。
当時のSwiftの最新バージョンがSwift 3.2 でした。

逆に同一バージョンで3年間も正常に動くSwiftに感動しています。

と前置きはこれぐらいにして本題に移りたいと思います。

RareJobには次の行動規範があります。

  1. やりたいことをやろう (High Alignment High Autonomy)
  2. ストーリーを語ろう (Share the Whole Story)
  3. 変化を生み出そう (Make a Difference)

入社してから上記3つの行動規範を元にレアジョブアプリのリプレイス作業に取り組みました。

やりたいことをやろう、に基づいて3年間運用していたSwift 3 を最新バージョンに近いSwift 4.X系にリプレイスすることを決意しました。

本当は今のSwiftの最新バージョンである Swift 5 に一気にリプレイスする方がユーザーのストレスを 最小限に抑えられていいのですが諸処の事情により一つ前のSwift 4.2 にバージョンアップという決断に踏み込みました。

Swift 4.2 にして得られたメリットについて

結果的にはSwift 4.2 にバージョンアップさせる事で下記のメリットを得られました。

  1. Xcode のバージョンを引き上げることができるのでアプリの寿命を引き伸ばせた
  2. SwiftのAPIを最新のものにリプレイスできた
  3. Xcode のビルド速度の向上

最初の課題について

Swift のバージョンを上げる事自体はそれほど難しくありません。
XcodeBuild SettingsSwift Language Versionを3.2 から 4.2 に変更するだけで完了します。

注意はSwift 3.2 の頃は Xcode 9.4 でビルドしていましたがXcode 9.4 ではSwift 4.1 までしかサポートしていないのでXcode 10.1 に変更することです。

f:id:qed805:20190426201559p:plain
Swift 4.2

ただし、そこからが大変でした。
Swift 4.2 に移行すると以下の変更点が発生してしまいます。

  1. Xcodeのnew Build System への対応
  2. Swift APIの修正
  3. 使用しているライブラリのSwiftバージョンのリプレイス
  4. Warning対応

それぞれ解説していきます。

Xcodeのnew Build System への対応

Swift 4.2にバージョンを上げた時に最初に起きた問題は
Xcodeの既存のBuild SystemだとMultiple commands produceエラーによってビルドが失敗するという現象でした。

f:id:qed805:20190426202442p:plain
Multiple commands produce

解決方法は2つあります。

  1. Xcodeの設定でSwift 3のビルドシステムを維持する(レガシービルドの採用)
  2. 新しいビルドシステムを採用してライブラリやキャッシュなどを一度削除して再インストールする

1, 2 の選択についてはXcodeFile < Workspace Settings上で設定することができます。

f:id:qed805:20190426202802p:plain
Buidl System

古いビルドシステムのまま開発を進めることができますがそれだとSwiftのバージョンを上げるメリットを享受できません。
さらに新しいビルドシステムにした方が開発が快適になるはずです。
そのため弊社は新方式のビルドシステムを採用する方針でリプレイスを進めました。

デフォルトでは新方式のビルドシステムなので設定で古いビルドシステムにするのではなく、
既存のライブラリなどを再度インストールすればいいだけです。

レアジョブアプリではcocoapodsとcathegeを使ってライブラリ管理を管理しています。
これらのlockファイルを一旦削除して入れ直せば動くようになります。
また、同時にXcodeのキャッシュを削除してclean buildをします。

これだけで新方式でのビルドシステムでアプリが起動するようになります。

Swift APIの修正

f:id:qed805:20190510020754j:plain
Swift 4.2

ビルド方式を新方式にしてアプリが認識するようになってからがリプレイス作業の本番でした。
次に襲ってきた問題はAppleが提供しているSwiftのAPIが変わったことによるビルドエラーです。

例えば、次のコードはSwift 4.2 ではエラーになってしまいます。

self.view.bringSubview(toFront: self.button) 

上記コードをSwift 4.2 で動かすには次のように修正します。

self.view.bringSubviewToFront(self.button)

このような文法チェックを逐一修正していきました。

他にも

return UITableViewAutomaticDimension

というiOSエンジニアであれば懐かしい文法を拝見することになりました。

こちらは

return  UITableView.automaticDimension

このように変更しなければいけません。

主な変更点を挙げてみますと

  • UIViewのaddSubviewメソッド
  • UITableView のデリゲートメソッド
  • RxSwift のVariableの初期宣言
  • NSNotificationNameの書き方の変更
  • NSAttributedString のKeyの指定の変更

などが挙げられます。
これらを一つ一つ修正していきました。
といっても総計150個ほどのエラーなので集中的に行えば2,3 時間でリプレイスできるボリュームの作業になります。

ここまで正しく修正を行うとXcode10.1でのビルドが成功するようになりました。

使用しているライブラリのSwiftバージョンのリプレイス

レアジョブのアプリは調べられば分かるのですが様々なライブラリに依存しているプロジェクトです。
そして設計はMVVMアーキテクチャを採用しています。当然RxSwiftも利用しています。
そのため次のタスクはこれらのライブラリのSwift バージョンを上げる作業になります。(念のためSwiftのバージョンは3.2です。)
全てのライブラリをSwift 4.2 にバージョンアップさせたいのですが確認すると50個ほどのライブラリを使っていました。

これらのライブラリを全てバージョンアップさせるのは非現実的だと判断したので部分的に重要かつ変更が簡易なライブラリのみを Swift 4.2 にバージョンアップさせることにしました。

cocoapodsのバージョン管理はPodfile上で

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = "3.2"
    end
  end
end

という風に本プロジェクトとは別に管理しています。
そこでRubyコードで次のようにすれば部分的にライブラリのバージョンを上げたり維持したりできます。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      if ['RxSwift', 'RxCocoa'].include? "#{target}"
        config.build_settings['SWIFT_VERSION'] = "4.2"
      else
        config.build_settings['SWIFT_VERSION'] = "3.2"
      end
    end
  end
end

if else end文に該当するコードを差し込みました。

あとは各ライブラリを個別に上げていき修正可能なエラーであれば対応していく作業をするだけです。
これである程度安全にアプリの品質を担保することができます。

このような作業をしたことによってレアジョブアプリはRxSwiftとRxCocoaなどメジャーなライブラリのSwift 4.2化が完了しました。

Warning対応

細かく書くことはないのですが、
Swift 3 の時のXcodeで表示されていたWarningの数が300数個あり今後の開発効率が下がる可能性がありました。
そのためリファクタリングという意味も込めてこのタイミングでWarning対応をできる限りすることにしました。
結果的には約100個ほどと半分以下まで修正することに成功しました。
半分が本プロジェクトコードの対応で改善できたもの、残り半分はライブラリのアップデートで改善されました。

これである程度目通しの良いコードになったと思います。

リリース前にAVAudioSessionで致命的なバグが発生

今リリースしているアプリはもちろんSwift 4.2 で正常稼働しています。
もちろんSwift 3から一気にSwift 4.2 にバージョンアップしましたのでどんなバグがあるのかは未知でしたので
リリース前に社内テストで全画面・全仕様を洗い出しリリース前のテストを実施しました。

その時にやはりSwift のバージョンアップに伴って2,3 個のデザイン崩れ・数個の既存バグを発見しました。
またチームリーダーの羽田がAppleへの審査前にレッスンルームのテストをしてくれたおかげでBluetoothに関する致命的なバグを発見することができました。

レアジョブアプリはレッスンルームの音声のやり取りでAVFoundationを利用しています。
AVFoundationにはAVAudioSessionというクラスが存在してこのクラスにはsetCategoryというメソッドがあります。

// Swift 3
try session.setCategory(AVAudioSessionCategoryPlayback)

このようなコードがありました。

Swift 4.2 にしてからこのメソッドがさらっとsetCategory:mode:modeをしなければならない仕様に変わっていました。

// Swift 4.2
try session.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default, options: [])

作業中はこの新APIはそこまで問題にならないだろうと思い深追いせずに進めましたが、Bluetoothとの接続に関わるメソッドだったようで Swift4.2 + Xcode10.1のバグの可能性によりそもそも「動いていない」問題が発生しました。つまりBluetoohイヤホンだと音声が聞こえないというバグです。
(Xcode10.2を使えば解決できるのですが社内事情によりXcode10.1 でのリプレイスのため)

細かい話しは下記のリンクが参考になります。

stackoverflow.com

そこで行った対応は最終手段としてObjective-Cのカテゴリを利用してAVAudioSessionクラスを拡張して古いAPIを使うことにしました。
次のようなObjective-Cクラスをimportします。

AVAudioSession+Swift.h

#ifndef AVAudioSession_Swift_h
#define AVAudioSession_Swift_h

@import AVFoundation;

NS_ASSUME_NONNULL_BEGIN

@interface AVAudioSession (Swift)

- (BOOL)swift_setCategory:(AVAudioSessionCategory)category error:(NSError **)outError NS_SWIFT_NAME(setCategory(_:));
- (BOOL)swift_setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError NS_SWIFT_NAME(setCategory(_:options:));

@end

NS_ASSUME_NONNULL_END

#endif /* AVAudioSession_Swift_h */

AVAudioSession+Swift.m

#import <Foundation/Foundation.h>
#import "AVAudioSession+Swift.h"

@implementation AVAudioSession (Swift)

- (BOOL)swift_setCategory:(AVAudioSessionCategory)category error:(NSError **)outError {
    return [self setCategory:category error:outError];
}
- (BOOL)swift_setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError {
    return [self setCategory:category withOptions:options error:outError];
}

@end

初めてObjective-Cクラスを使いましたのでSwiftクラスで使えるようにBridging-Header.hを導入することになりました。

これでSwift クラスで使用するときにsetCategory:modeではなくsetCategoryのままで利用できるようになりますので解決できました。
ただこの対応は黒魔術のようにも見えますので他にも解決策がありそうでしたら教えて頂けると嬉しいです。

最後に

このように最後までどこでバグが混入してしまうのか分からないのがSwiftのバージョンアップ時のリスクです。
それらをリリース前に防げた点といい、本当に開発しやすい環境で助かりました。
最後のバグの発見と改修はファインプレーに近いです。

ということで大変長くなりましたがSwift 4.2 へのバージョンアップした際のお話しはこれで全部になります。
長々ではありましたが読んで頂きありがとうございます。

レアジョブに欠かせない連携

はじめまして、レアジョブで基盤システム、インフラを見ております岩堀です。よろしくお願いいたします。 ジャンボとは呼びづらいので羽田さんと呼んでしまいますが、前回は羽田さんよりレッスンルームについて 書かれておりましたが、その開発の中で日本−フィリピン間での開発が行われていることについての記載がありました。 今回は私からは日本−フィリピン間のエンジニア間での連携に焦点を当ててお話をしたいと思います。

フィリピンとの関わり

レアジョブの英会話サービスは日本側に生徒がいて、フィリピン側に講師がいることで成り立っております。 各システムはそれぞれの文化や特性を理解した上で、開発を行なっておりますので、日本側のシステムは日本側で、 講師側のシステムはフィリピン側で作る形をとっております。 それぞれ開発されたプログラムは同じインフラ基盤で動いており、同じDBを利用してシステムを動かしております。 そのため、意図しないところで、同じデータを参照していて各々のサイド勝手にデータやテーブルの削除を行うと システムが正常に稼働しなくなることもあります。そのため、システムは異なりますが、常に日本、フィリピン間で 連携を行なっている必要があります。

フィリピンとの連携

フィリピンとの連携を行う上で重要なことは、まずは他国文化を認めることが大事と思っております。 日本では考えられないことが、他国だと当たり前であり、それを強制して日本に合わせるようにしても あまりいい結果は産まないと感じます。 フィリピン人は真面目であるので基本的に言われたことはこなしてくれます。ただし日本人のような 行間を読んだ対応はできないので、その辺りは端折らずに確実に伝えることが必要になります。

これまでの取り組み

これまで私はインフラ業務やAPI開発を行ないながら、フィリピンスタッフと多々一緒に業務行ってきました。 その際に業務を進めるために行った取り組みが下記になります。 f:id:creelcyclone:20190426185339j:plain

* 技術レベルを合わせる

  1. 技術スキルを、無理やり日本にレベルを合わせようとすると失敗します。そのためレベル感を合わせるためにも、開発ルールを明確化しました。
  2. レビューを日本-フィリピン相互で行い、その中でディスカッションを行うことでお互いの考え方のレベルを合わせていきました。
  3. トラブルシューティングにおいてはクラウド上の共有の開発環境を用意した上で、お互いのトラブルをその環境で再現させ、どこが問題かを明確にし、すぐに共有できる状態にしました。

これらを行う事で、お互いの考え方や意思の統一を図り、考えたを理解した形で動きやすい状況を試みました。

* 連帯感を高める

  1. 異なる国で働くため、状況が逐一確認できない状況のため、MTGを毎日実施しお互いのタスクを共有する
  2. 日々の業務の中で連帯感が薄れを防ぐために、常時ビデオチャットをつなぎ、お互いの様子を確認できやすい環境を作り、話しかけやす状況を作る
  3. 大規模な作業の際には現地に行って、一緒に作業を行う。

こちらはお互いに一緒の目標を持って一緒に業務を取り組んで達成感を高める形でお互いのモチベーションを作る試みをしました。 それによってお互いに疎外感をなくし、高いモチベーション持ったまま仕事に取り組める形を作りました。

困難なところ

 開発ルールや設計ルールを作ったとしても、ルールを浸透させる点では直接説明しますが、やはり言葉の壁もあり、なかなか伝わらない部分もでてきます。  そのため、ルールを浸透させるためにルール作りに関わった方をレビューに含めるなどを行いましたが、なかなか浸透させるのは難しいものでした。  この点は今も課題として残っております。

今後の取り組み

共通のリソースについては今後も引き続き、日本、フィリピンで協力しながら、構築・開発を行なっていかなければなりませんので、 連携のよりよい仕組みを考えてトライを続けていく考えではあります。

また一方でインフラについては、フィリピン側のサービスに関しては、これまでは日本側が主導して設計・構築を行ってきましたが、 その辺りをフィリピン主導でやれる形を現在検討しております。これにはナレッジやドキュメントの共有の仕組みを考え、各種フローを整理し 日本、フィリピン間での技術レベルを合わせられる形にしていかなければならないです。 正解のない取り組みですが、未知な事なので何をやるにしても楽しみではあります! f:id:creelcyclone:20190427192552p:plain

こんな、未知な取り組みでありますが、こんなことにも興味がありましたら、一緒に切り開いてくれる仲間を募集しています!

レッスンを技術で科学する、レッスンルームの開発現場

どうもジャンボです。レアジョブではあだ名の宣伝に失敗して羽田さんと呼ばれることが多いです。よろしくお願いします。今は技術本部の副部長や、デザインチームのリーダーをしています。体験開発の責任者です。 この記事では我々の提供しているレッスンルームについて書きたいと思います。

レッスンルームとは

f:id:jumbos5:20190419191941j:plain:w500
レッスンルーム

弊社はずっとスカイプ英会話と呼ばれて来ましたが、EdTechのリーディングカンパニーになるべく、自社システムであるレッスンルームへの移行を進めています。これまでチューニングできなかったレッスン中のUXをゴリゴリに改善すべくバリバリに開発を進めています。

www.rarejob.com

レッスンルームを支える技術

一言にレッスンと言っても、ただ会話をするだけではありません。最適なレッスン体験を安定して提供・改善するために私たちは様々な技術セットを選んでいます。Webだけではなく、アプリからのレッスンを可能にするためにネイティブアプリの開発にも力を入れています。 Webでは一般的なSPA構成で使われるVue/Typescript/Webpackを中心に、コアとなるWebRTCにおいてはNTT Communicationsが提供しているSkyWayを使うことで安定したインフラを最小限のコストで利用できています。

この辺については話したいトピックが2億個以上あるのですが、今回は血泪を流してWebRTCについて書きます。

skyway.io

WebRTC

WebRTC(Web Real-Time Communication)はブラウザでリアルタイム通信を行う仕組みです。ブラウザで電話をする仕組みではないです。データのやり取りの一環として音声や映像があるだけで、規格として電話のような動作を保証するものではありません。 なるべく簡単な図を用意しました。

こちらをご覧ください。

f:id:jumbos5:20190419200334p:plain

・・・

お分りいただけたでしょうか。そう、思い出してください。ブラウザで電話をする仕組みではないのですよ、内部的には非常に複雑なことをしています。そこまで簡単になりません。しかしこれでもSkyWayの導入により非常にシンプルになっているのですが、それでも我々の悩みはつきません・・・なぜでしょうか?

WebRTCの悩み

WebRTCを扱うエンジニアが直面する問題がいくつかあります。それについて説明していきます。

1. 総合格闘技である

一発目からパンチがあるのですが、WebRTCはフロントエンド技術の皮を被ったフルスタック技術なので非常に網羅性の高いエンジニアリングスキルが求められます。ちょっと思いつくだけでも

  • ネットワーク
  • ICEやシグナリング等の基本的なWebRTCの仕様
  • セキュリティ
  • モダンフロントエンド
  • ブラウザの持つAPIやその差分
  • バイスアクセスとそのコントロール
  • バイスへのパーミッション管理もろもろ
  • リアルタイム通信という状態の多いシステムの状態管理を実現可能なフロントエンドアーキテクチャ
  • リアルタイム通信という状態の多いシステムのUX
  • リアルタイム通信という状態の多いシステムのエラーハンドリング
  • 複雑な通信経路内のボトルネックを発見する能力
  • etc...

という感じで、安定したオンラインリアルタイムレッスンの提供するためには非常に広い範囲のスキルが必要となります。 幸いなことに我々はSkyWayの導入によって、いくつかの問題、特に「ICEやシグナリング等の基本的なWebRTCの仕様」「セキュリティ」のような問題は解決ができています。どれか一つがダメでもその瞬間「繋がらない・動かない」と言われてしまうため、ユーザーにとっての馴染みがありシンプルな体験である「通話」というのを0からシステムデザインすることがいかに難しいかを物語っています。

2. 通話へのハードル

インターネットと違い、我々が長く使い続けて来て骨身まで安定していることが沁みている電話回線というプロダクトにより人類は「繋がって当たり前」という印象を持っています。一方でWebRTCはまだ枯れておらず、いまだにアップデートが多くありまだ改善の余地のある仕組みなため、当然我々の実装や環境に依存して繋がらないという状況が生まれます。

なんで?と言われると一言で表すのは難しいんですが、コールをするためにも様々なるプロセスがあり、そのどのプロセスもボトルネックたりうるからです。

f:id:jumbos5:20190420081607p:plain:w600

実際の手順等はこちらに。 qiita.com

この問題を理解・分解することが非常に求められ、かつそれを適切な人、それはエンジニアなのかユーザーさんなのか、伝えていく必要があります。またSkypeやLineのように無料で高品質な通話を当たり前にしてきた世代にとって、有料サービスなのに繋がらないなんてどういうこっちゃという感情は当たり前なので、このハードルを乗り越えていく必要があります。

3. 環境依存

一言に「繋がらない」という問題に対して「お客様環境」というものが何パターンあるか考えたことはあるでしょうか?

f:id:jumbos5:20190419202356p:plain

問題に対してお客様環境がどんな状態か、これの問題がどこにあるか、そしてそれは解決・発見できる状況かというのが運用上すぐ直面する課題だと思います。多くの企業ではこれらの全てをサポートするのは難しいでしょう、またサポートするにも多くのリソースが必要となることが容易に想像できます。

またサーバを介さない通信のため、行動のログの解析も容易ではありません。またこの状況の複雑性は技術やブラウザの進歩により日々加速度的に増えていっています。

そんな容易でないWebRTCを使ったプロダクト開発ですが、こういった問題にどのように我々が向き合っているのか、ほんの一部だけ紹介します。

WebRTCとの戦い

1. 「総合格闘技」には正攻法では挑まない

f:id:jumbos5:20190419195934p:plain:w500

真正面からこれらの問題とは戦えません。少人数のチームで毎月何十万のレッスンを提供するシステムを支えるためには、工夫と適切な技術選定が必要になります。SkyWayやVue.jsなど、エンタープライズ・オープン技術を組み合わせて少人数でも運用可能なレッスン基盤の構築を目指しています。

正直まだまだやれることは全然ありますし、マニュアルな運用しているところも非常に多いです。それでも運用当初よりレッスンルームを使ったレッスン提供数も格段に増え、これを支える開発・運用は日本とフィリピンで連携して進めています。

f:id:jumbos5:20190419211127j:plain:w500

めちゃくちゃカメラ目線のalbert氏は去年1年近く日本に出向してくれて、一緒にレッスンルームの開発をしていました。日本とフィリピンを横断したシステム開発とQAで複雑度の高いシステムを改善しています。

2. 「通話へのハードル」に向き合うUX

Webとモバイル、それぞれのデバイスでUIを大きく変えています。 それはインタビューや行動のログから見えて来たそれぞれの期待される体験が大きく違うことや、それぞれの持つ環境特有の問題を解決することを目標としています。

その中には多くのリトライや、エラーハンドリングの仕組みが混在されておりWebではVue/async-await、アプリではRx系のライブラリをふんだんに使うことで複雑度の高いアプリケーションを実装しています。

また入室前に実際にレッスンルームを体験できるレッスンルーム デモページも提供しており、 これにより初めてレッスンを受ける人でも安心してレッスンが受けられる取り組みを入れています。

またこのハードルを越えるために社内外多くの人に支えられていて、SkyWayの技術チームとは共同でイベントをしたり技術交流がありギークな面々と仕事ができて楽しいです。

developer.medley.jp

3. 「環境依存」と向き合うチーム開発

我々のメインコンテンツであるレッスンを支えるシステムはもはやサービスのインフラといっても過言じゃありません。これを支えるためには営業数値への貢献も必要ですが、適切に定量化された数値をベースに改善をする必要があります。当たり前のことですが、まずは「あたりまえ」クオリティを満たすために必要なKPIの選定は慎重にしています。

我々はここ1年ほど、安定性を支えるKPIをチームで持ち、ビジネス・フィリピンチーム含めた皆で改善に当たっています。 接続率や、入室率、結果としてのレッスン提供数など様々なKPIをエンジニア含め皆で改善しています。

f:id:jumbos5:20190419204615p:plain

内製しているロギングの仕組みの結果をredashで可視化し、redash apibot経由で毎日のモニタリングや問題の早期発見をしています。 このデータにアクセスできるようになったことで、レッスンの本当の実態が見えて来ています。

これにより、デバイス別・ユーザー別・講師別・プラン別などなど、様々な切り口からの問題特定をする、または気づける環境を作っています。より多くのデータを活用して、いかに安定していい価値を届けられるかが我々のお仕事です。

なぜこんながんばるのか

開発をしていて「いやもう正直、めっちゃわからん!」ということもあるのですが、日々の改善により問題の本質や数字が改善されていくことはエンジニア冥利につきます。難易度も複雑度も高く、解決することがビジネスや自身のキャリアへインパクトがあると感じています。

また本当にユーザーさんが日々25分もレッスンを実際にするサイトなので、フィードバックや効果は毎日のように来ます。 ゲーム以外で1日に25分も同じURLのサイトを見続けることってありますか?あまりないと思うんですよ、そうなると人はいろいろなことに気づいたり、感情が生まれたり、そうしたものが届きます。これは講師も然りです。

もちろん全てがいいフィードバックではないですが、数字を見ても反応を見ても確実に日々進歩していると感じています。これまでは科学できなかったレッスンの中身を知り、様々なる技術を総出でガシガシに改善するのは非常に面白いです!

是非一緒に働きry)という前に是非一回使って欲しいです。

無料体験を是非レッスンルームで受けてください!よろしくお願いします。

技術ブログ始めます

はじめまして。レアジョブでCTOをやっております山田と申します。 弊社でもエンジニアによる技術ブログを開設することになりました。

レアジョブとは

Chances for everyone, everywhereをグループビジョンに掲げ、英語教育を事業ドメインとするEdTech Companyです。 優秀なフィリピン人を講師としてオンライン英会話サービスを中心に、個人だけではなく法人・教育機関向けにも英語学習サービスを提供しております。日常英会話とビジネス英会話が学べる「レアジョブ英会話」、短期集中の「レアジョブ本気塾」、スピーキング力アップの成果を保証する法人向けプログラム「レアジョブ英会話 スマートメソッド(R)コース」、 英語学習アプリを展開しております。

f:id:rj_tech_dept:20190404162005j:plain

サービスに関する情報はこちら

技術ブログの目的

我々が技術ブログを書く目的は以下となります。

  • 発信する情報で誰かの役に立ちたい
  • 外部に発信することを学びに変えたい
  • エンジニア目線からレアジョブをもっと知ってもらいたい

発信する内容

我々が持つ技術スタック、フィリピンとの拠点を跨ぐ開発、英語やEdTechに関するあたりを発信出来ればと思います。 ちなみに技術/環境としてはすべてではないのですが、主に下記のものを利用しています。

ネイティブアプリ

  • Swift
  • Kotlin
  • bitrise

Webフロント

  • TypeScript
  • Vue.js
  • jQuery
  • webpack

サーバサイド

インフラ

  • AWS
    • EC2, RDS(Aurora), ElastiCache, API Gateway, Lambda, AppSync, DynamoDB, CodeDeploy ... more
  • GCP
    • BigQuery
  • Sakura Cloud
  • Elasticsearch
  • Zabbix, Cloud Watch

ツール系

  • GitLab, GitLab CI
  • Jenkins, Capistrano
  • Confluence, Cacoo, JIRA, Redmine
  • Slack, Chatwork

最後に

「株式会社エンビジョン」の設立による学校向け・子ども向け事業の加速、教育の価値最大化を目指すR&Dプロジェクト「EdTech Lab」発足、 シンガポール英会話学校Geosの子会社化しグルーバル拠点の構築など、市場からの期待という追い風もあり急速に事業を拡大しております。 ニュースリリースこちら

これはエンジニアにとっても多くのチャレンジとチャンスがあり、失敗も含めた学びがたくさんあると思います。そういったことも可能な限りこのブログを通じて発信していきたいと思います。

更新頻度は週1を目標に、長くゆるくやっていきますので、よろしくお願いします。