Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

AAC Pagingライブラリ導入の話

お久しぶりです。Androidエンジニアの若宮(id:D_R_1009)です。 今日はAndroid Architecture Components Pagingライブラリ(AAC Paging)の導入例を紹介したいと思います。

アプリを利用してくださっている方はお気づきになられていると思いますが、11月19日よりアプリのデザインを一新しました。 デザインの変更に伴い、アプリ起動時の通信処理や各Fragmentの生成処理の見直しを行いました。安定性が増すことを目標に、見直しに取り組みました。

デザインのアップデートとほぼ同時ではあるのですが、タイムラインのパフォーマンス改善にも取り組みました。 今回は、パフォーマンス改善でAAC Pagingライブラリを導入した際のアレコレを書きたいと思います。

AAC Pagingライブラリとは

https://developer.android.com/topic/libraries/architecture/paging/

AACライブラリの一つとして2018年6月に正式リリースされた、ローカルDBやリモートサーバーから逐次データを読み込む処理をサポートしてくれるライブラリです。 データ読み込みの処理と表示するデータの保持を分けることで、RecyclerViewのスクロール処理がデータの読み込み処理によって遅くならない等の効果が期待できます。

個人的には recyclerview.extensions.ListAdapter がsupportライブラリに入った頃から気になっている存在です。 そのため「ようやく正式版になってくれたか」という気分ですね。

導入に当たって検討した事柄

旧タイムライン処理にはいくつかの課題がありました。一部を抜粋すると下記の通りです。

  • 追加読み込み処理が「最後から5番目」を表示したタイミングになるため、読み込み処理がUI処理に強く依存している
  • リストのデータがRecyclerView.Adapterで保持されているため、Fragmentの破棄時にキャッシュが削除されてしまう
  • ObservableListを利用しているため、通信取得処理のスレッドとUI更新時のスレッドをFragment内で明示的に切り替える必要がある

Pagingライブラリの導入によって次のように解決すると考えました。

  • 追加読み込みのタイミングがPagingライブラリ側でハンドリングされるため、読み込みの通信処理に専念できる
  • リストのデータをメモリやディスク上で保持できるため、Fragmentの破棄時に(不要な)キャッシュ削除が発生しない
  • LiveDataでリストの取得ができるため、UIスレッドとそのほかのスレッドをほぼ意識しないで済む(ViewModel + LiveDataの処理にまとめられる)

導入方法

前述の通り、Pagingライブラリを導入していた時期はアプリのデザインリニューアルの時期とかぶっています。 また導入事例や導入LTなどをあまり見つけることもできなかったため、いくつか安全策をとることとしました

旧タイムライン処理との平行運用

通信時のパースクラスから表示用のFragmentまで、旧タイムラインの処理と切り離して全て新規作成することとしました。 他の新規開発機能とのバッティングを抑え、ライブラリの導入に失敗した場合や想定以上の工数を使ってしまった場合に備えるためです。 結果としてタイムラインを置き換えることはできましたが、タイムラインの一部のみ置き換えることを想定しました。

良かった点

  • 旧タイムラインを置き換え可能な箇所から書き換えることができたため、レビューのコストが想定より低くなった
  • 一部のみの置き換えを念頭に置いて設計した結果、依存度の低いコード記述となった
  • 旧タイムラインは主にJavaで記述されていたが、新タイムラインはKotlinで記述することができ、data classを活用できた

悪かった点

  • JSONのパースクラスから作り直したところ、RecyclerViewのItemレイアウトが使い回せなくなってしまった
  • 旧タイムラインに追加された修正を新タイムラインに反映させていたが、反映漏れが発生した

その他に新しくなった箇所

旧タイムラインのItemをconstraint layoutに置き換えたところ、View間の関係性が漏れていた箇所が多数ありました。 Viewの縦横のサイズ計算に関わる問題となるため、ネストが浅くなるようリファクタリングを行なっています。

また、タイムラインのFragment間でRecyclerViewのViewPoolを共有し、Inflate回数を減らす対応を行いました。 こちらは2018年のDroidKaigiより広く使われている手法なので、弊社アプリもようやく追いついた感覚があります。

感想

導入のためサンプルコードをPCに落としてから、機能としてリリースするまで3週間ほどかかりました。 ほとんどPagingライブラリの導入にかかりきりだったので、長い時間をかけたような気がしています。

導入時に想定していたほど、Pagingライブラリの導入にはコストがかかりませんでした。 これは公式で非常にわかりやすいサンプルプロジェクトが用意されていたことが大きいです。まず公式サンプルに従ってAPIとDBの接続をするのが良さそうです。

逆に想定しきれていなかったのが、レイアウト周りの修正の多さです。 もともとButterKnifeをDataBindingに置き換えている途中だったこともあり、Kotlinとxmlの両方で書き換え処理が多くなりました。 変更に強いデータ構造を作らなければならない、という強い反省になったと感じています。

ひとこと

バリバリAACを使っていきたい! という気持ち溢れるエンジニアがいましたらこちらよりご連絡ください。

Kubernetesハンズオンを行いました

はじめまして、Studyplus開発部でサーバーサイドを担当している栗山です。

背景

Studyplusではコンテナ化を進めたいと思っていますが、まだAWS Elastic BeanstalkEC2上でアプリケーションが動いています。 今後コンテナ化するにあたり、

  • コンテナ化を進めていきたいが、コンテナのデプロイや管理はどうすればいいかわからない。
  • コンテナ管理するためにKubernetesというのが良いらしいが覚えることが多くて難しそう。
  • そもそもコンテナよくわからないし、敷居が高そう

といった不安や疑問が部内にあったのでそれらを解消すべく、Kubernetesのハンズオンを実施しました。

目的

ハンズオンの目的としては以下です。

  • Kubernetesが何を解決するかを知る
  • Kubernetesの機能を知る
  • Kubernetesの各概念を知る
  • GCP上のKubernetesに簡単なアプリケーションをデプロイして、実際にKubernetesのスケーリング機能や耐障害性等々を体験してもらう。

ハンズオンの進め方

ハンズオンの資料は以下にあります。

まず最初にKubernetesの特徴や何ができるか、そして各概念の説明をしました。

  • Kubernetesの各概念の理解 (※説明が間違っている可能性があるので間違いを見つけた場合はPRを送ってもらえると助かりますm( )m)

Kubernetesには様々な機能、リソースがありますが、まずは基本であるPod, ReplicaSet, Deployment, Service, Ingressを理解してもらうことに重点をおきました。

その次に、実際にGCP上でアプリケーションを動かしてもらいました。

GCPを選んだのは

  • GCPは2014年からKubernetesをマネージドサービスとして提供しており、Kubernetesとの親和性が高い
  • Studyplusでは主にAWSを使っているので、違ったクラウドを体験することは見識を広める意味でもプラスになる

といった理由からです。 実際触ってみると、GCPは非常にシンプルでKubernetesがシームレスに組み込まれており、Kubernetesを動かすには最適な環境だと感じました。

このハンズオンは、説明と実際に手を動かすのに全部で1時間半〜2時間くらい必要となります。 そのためスムーズに進める必要があり、以下のことを注意して行いました。

  • ハンズオン資料のコードをコピペすれば動くようにしておく
    • しかしコピペを間違う人もでてくるので、必要なファイルは全て予め用意しておきダウンロードすれば済むようにした
  • 事前に時間がかかる作業(例えばGCPアカウント作成や、SDKインストール作業等々)はハンズオンの前に各自で行ってもらう
  • 出来るだけ早く理解してもらうために、説明文はできるだけ丁寧かつ正確に書いておく。また喋る予定の内容も全て書いておいて読めば分かるようにしておく。
  • 予期せぬエラーがでたり躓いて先に進めない人が出ないように、問題なく進めているかこまめに確認を取る。人数が多いと躓く人が多くなるので、一回のハンズオンの参加者は4人くらいにした。

まとめ

Kubernetes自体の素晴らしさもあり、ハンズオン後はKubernetesを使っていく機運が高まりました。 実際に運用していないのでまだ見えていないつらみやデメリット等があると思いますが、それを上回るメリットがあるように感じているので、今後はKubernetesの導入を進めていけたらと思っています。

スタディプラスではKubernetes導入を一緒に進めていく仲間を募集しております! ご応募お待ちしております!!

補足

ハンズオンの内容は、 WEB+DB PRESS Vol.99のKubernetes特集の内容を参考にしました。ありがとうございました。 また、Kubernetesを知るためにはKubernetes完全ガイドという本が2018/9に発売されており、これを読めば完全に理解できると思います。本当に最高の本です。

インフラエンジニアがオフィス移転で考えたこと[前編]

初めまして、インフラエンジニアの菅原です。

弊社では事業拡大に伴うオフィス移転を予定しております。
インフラエンジニアとして社内ネットワーク移設について検討したことを移転前と移転後の2回に分けてまとめたいと思います。

f:id:ksugahara08:20181110143139j:plain:w300

まだ何も物が入っていないまっさらなオフィスの写真です。結構広い!
移転は年末になり、現在は移転準備をしている最中となります。

移転前に検討するべき項目

1.移転後の要件

まず、移転後の社内ネットワークデザインを決める基礎となる情報を収集しました。

  • 社内ネットワークの利用ユーザー数
    弊社では事業拡大も視野に入れて50人~200人規模の利用ユーザー数を想定しました。
    それだけでなく、イベント時はゲストも繋げるため最大200人の同時接続を許容する必要がありました。

  • ネットワーク接続する端末の台数
    アプリの検証端末などもあるため利用ユーザー数+αの接続が見込まれます。接続数には余裕を持たせたほうが良さそうでした。

  • オフィスのレイアウト
    今回は内装業者さんに座席の位置やパーティション等の図面を早い段階で頂いたため、そちらを基にルーターの設置場所、無線アクセスポイントの配置について検討することにしました。

  • オフィスで行う業務
    一般的な事務、カスタマーサポート、WEBアプリ開発がオフィス業務の中心になります。
    AWSとGCPを利用しているため、サーバーの移設については今回検討不要でした。
    オフィス向けプリンター・複合機は現在利用しているものを利用します。

2.現状の確認

現状どのような業者と契約しているか、どのような機器を利用しているかを把握し、移転後も使い続けるのかを確認する必要があります。今回は以下の現状について確認しました。

  • ISP(インターネットサービスプロバイダ)と光回線業者
    過去の経緯からIP電話とインターネット回線を別々の業者と契約している状況でした。こちらを一つにまとめられないか検討しました。しかし、変更してもランニングコストは変わらなかったため、現状のプランのまま移転することにしました。

  • ネットワーク機器とその設定
    ルーター、無線アクセスポイントについて設定内容を確認しました。現状では接続先アクセスポイントによって回線速度が遅いと感じていました。こちらも移転に伴って変更を検討しました。

  • 現状の回線速度
    引越し後の回線速度とも比較を行いたいため、測定しておきます。ツールは「Speedtest by Ookla」を 利用しました。

  • 固定IPが使われている箇所
    社内ツールなどは固定IPでアクセス制限を行なっている箇所があったため、設定を洗い出しました。
    移転に伴って設定を書き換える必要があります。

3.障害・災害時の検討

SLOによって変わってくるとは思いますが、機器の故障に寄る障害にも強い構成を検討するべきでしょう。
今回の移転ではSPOF(単一障害点)になりうる部分をできるだけ排除し、ネットワークの可用性を向上させることにしました。
具体的には現状ルーターが1つしかないので、機器の故障に備えてもう一つ購入し、多重化することにしました。 業者の保険に入ることで故障時の対策とすることもできましたが、ルーターを2つ購入して設置しておくことで、素早く復旧できると考えたからです。

4.スケジュールの策定

ネットワークの動作確認を考えると、移転の1週間前までには移転先でのネットワーク設定作業を終えていたいと考えました。オフィス移転の前日や前々日がネットワーク移転の作業日になるケースは多いと思います。そうするとスケジュールに余裕がなく、もしもの時に対応できません。
こうした要件を満たしたい場合に問題となってくるのが光回線業者の作業です。 「移転」で依頼した場合は工事作業日が前日や前々日になってしまします。しかし、「新規」で契約し直すことで現オフィスと平行契約期間を設けることができます。これで1週間前に動作確認ができるようにしました。

5.移設後のネットワークの構成と機器を検討

  • ルーター
    現状使っているルーターが故障した時のために同じものをもう1台購入することにしました。
    移転後のオフィスでは利用者が増えるためルータのNAPTテーブルエントリ数(IPマスカレードエントリ数)は購入前に確認しました。 PC1台に対して50〜200程度あれば十分だと思います。
    スループットも重要な確認ポイントになります。 こちらが低いと回線速度を遅く感じられてしまいます。 大雑把ですが、実測値で1Gbps以上出るものを選ぶと良いと思われます。

  • 無線アクセスポイント
    移転前のオフィスでは無線アクセスポイントが2つしかなく、接続先によっては遅く感じられました。
    そのため、移転先のオフィスでは無線アクセスポイントを5つに増やすことにしました。 天井に設置し、電波の強い範囲が被るようにする予定です。
    これでインターネット回線が遅いと言われないようになって欲しいです(願望)。

まとめ

今回はオフィス移転前に社内ネットワークについて検討したことを中心にまとめました。
社内要件を行い、業者さんとコミュニケーションを取りながら移転に向けて準備を進めております。
次回は移転後の話を記事にしたいと思います。

弊社では新しいオフィスで一緒に働いてくれる仲間を募集しております!
ご応募お待ちしております!!

RubyWorld Conference 2018に行ってきた

こんにちはスタディプラスCTOの島田です。

はじめに

スタディプラスは、RubyWorld Conference 2018Platinumスポンサーとして協賛をさせて頂きました。 それに伴って、ブースも出展いたしました。

2018.rubyworld-conf.org

島根到着からブース出展までの内容と、同行した各エンジニアの印象に残ったセッションの感想を紹介させて頂きます。

出雲縁結び空港と会場

出雲縁結び空港には、早速RubyWorld Conferenceのポスターが。

カンファレンス当日は晴天に恵まれました。(大橋川にてしじみ漁をしている模様)

企業ブース出展をする大展示場の様子。

ブース出展

スタディプラスのブースでは、Studyplusのサービスにちなんで、「好きなRubyの技術書を投票!!」という企画を実施しました。

投票してもらうRuby技術書を独断と偏見で5冊ピックアップ。

多くの方に投票を頂きました。

ブースにはMatzさんにも来て頂き、著書にサインと記念撮影をしていだきました。

セッションの感想

島田

基調講演 The Power of the Community(まつもとゆきひろ氏)

Rubyコミュニティのこれまでのヒストリーと、コミュニティの力。 個人的には「Rubyは不景気が生んだ言語」というのが刺さった。

花井

RubyによるDBスケーラビリティ

Leonard Chinさんによる、クックパッドの1000万ユーザーを支えるDB周りについての発表でした。 クックパッドさんほどではないですが、弊社もRails 3の時代からRuby on Railsによる開発を続けており、すぐに業務に役立てたいと思うTipsがある発表でした。 特に

  • ユーザーが多いので遅くなっている人を特定するのが難しい
  • データが多いので再現するのも大変

という点は大変共感できました。

発表では、実際にあったトラブルを例に

  • NewRelicのAverageResponstimeでは問題ないようにみえるレスポンスの陰にあるユーザーの体験を95パーセンタイル、99パーセンタイルも見てボトルネックの発見に至った
  • activerecord のexplainメソッドで計測した
  • indexだけで解決できない問題をRubyの積演算で解決した

など具体的な解決の手順と、そこに至る道筋を追体験できるもので、とても参考になりました。

mruby/cを用いたプログラミング教育向けデバイスの開発

牧 俊男さんによる、mruby/cでArduinoの制御をする事例の発表でした。 今回のカンファレンスには現地の高校生も参加しており、最近のプログラミング教育で具体的にどのような取り組みがされているのかを知る機会に恵まれておりました。 そんな中での本セッションは、プログラミング未経験の中高生に8時間で体験してもらうための工夫や、発展途上のmruby/cでの苦労などが紹介されていて個人的に興味深い内容でした。

Cのコードへ変換するか、直接Cのコードを書くというところにハードルを感じていたのですが、irbの感覚でデバイスの制御ができるという点に関心を持ちました。 業務ではまず使うことのないデバイスですが、この発表を見て早速Arduinoを購入しました。

石上

1日目、2日目の中から気になった講演・発表の感想などを書きます。

基調講演 The Power of the Community(まつもとゆきひろ氏)

タイトル通り、Rubyコミュニティの力についての講演でした。Rubyをデザインしたのはまつもとさんですが、Rubyがここまで大きくなるにはコミュニティの力が不可欠だったことを知ることができました。

スモウルビー3.0の開発とRubyを用いたプログラミング学習への活用

ScratchのRuby版、smalruby(スモウルビー)の開発についての発表。私はこの発表を聴くまでsmalrubyについて知りませんでしたが、発表者である島根大学の武本さんの、プログラミングを楽しむことへの情熱が伝わってきてとてもよい発表でした。Githubに公開されているので、実際に手元へcloneしてきて yarn; yarn run start したら動きました。スモウルビーはscratch-guiをforkしていて、Rubyコードの生成以外は本家と同じようです。

Scratchのようなツールは、存在自体は前々から知っていたものの、普段触ろうとする機会がないので新鮮でした。プログラミング教育の盛り上がりと同時に、こういったプログラミング体験ツールも今後いろいろ出てくると面白いなと思いました。

基調講演 Don't Stop Moving(Chad Fowler氏)

エンジニアがモチベーションを保つためにどうするかという話でした。エンジニアリングに限らず、自己啓発系の書籍や、考え方なども紹介されていました。自分が投資している技術カテゴリを意識することなど、今後のキャリアを考える上で参考になる話が多かったです。

CookpadがRubyと歩んできた10年

Cookpadで実際に存在した、過去のおもしろPull Requestが紹介されていて楽しかったです。 終わったあと、一緒に聴いていた自社のエンジニアと、うちもいろいろありそうですねという話をしました。

RubyによるIoTデバイス制御

mruby/c で下記のIoTシステムを作ったことについての発表。

組み込み系のシステムをRubyで書いた実例として面白かったです。mrubyに対して興味がわきました。

田口

一日目に印象に残った2つの発表についての感想を書きます。

Railsチュートリアル×反転授業: 解説動画を用いた能動的な学びによる驚きの効果

https://speakerdeck.com/yasslab/more-interactive-way-of-learning-rails

Railsチュートリアルを公開しているYassLab株式会社の安川さんの発表です。
個人的にRailsチュートリアルは大変お世話になったので、今回の発表は非常に楽しみでした。
発表を聞く前は「反転授業」とはどういうことかわからなかったのですが、「一斉授業」「反転授業」について以下のスライドでわかりやすく説明されています。

https://speakerdeck.com/yasslab/more-interactive-way-of-learning-rails?slide=30 https://speakerdeck.com/yasslab/more-interactive-way-of-learning-rails?slide=31

「一斉授業」は、現在の学校での授業のような体系です。講義外の課題として、宿題を解いたりします。
「反転授業」はまさに一斉授業の反転で、学校の授業でやっているような「知識のインプット」を講義外で行い、講義中は実戦形式で開発していくといった体系です。
反転授業の体系の話を聞いたとき、なるほどと感じました。自分がRailsチュートリアルを実際にやってみたり、エンジニアとして働いてみて感じたことですが、手を動かして作りながら学んでいったほうが、インプットだけするよりも効果的であると思っています。それに通ずる感覚を、講義を通じて養えると考えると非常に有意義だと思います。また、スライドにもありますが、難しくて挫折しがちな初学者が挫折しないようにすることにおいてもかなり効果的なのは素晴らしいことだと思います。
この発表を見て、学生だけでなく、新しいことを学ぶ社会人にも反転授業が有効であると感じました。料金的にお得な法人向けの動画視聴サービスを公開されているようなので、法人での導入も大いにアリなのではないかなと思います。

プログラミング入門をプロジェクトでやってみた -Rubyで取り組むプログラミング実践-

フェリス女学院大学でのRubyを用いたプログラミング授業の実例の発表でした。上記のRailsチュートリアルの次の発表だったのですが、実践的な教育の事例として関連が深い発表だと感じました。
授業の講師の方と、その授業を受けていた生徒2名の合同発表でした。授業の実際の内容の話を聞くと、4〜5人でチームを組み、チームメンバーの得意分野に応じて作業を分担したり、講師の方との頻繁なやり取りで疑問を解決したりしていて、非常に実践的だなと感じました。個人の実際の作業とチームでの作業を同時に体験できるので、かなり良い経験になるのではと思います。
生徒の方々は文系学部で、IT関連の業界や職務には詳しくなかったと聞きました。プログラミングは遠い世界にあるものという感覚を持っていたそうです。そのような人が、授業を通じてプログラミングを実際にやってみて、さらにそれをチーム全体のプロジェクトとして進める経験を積んだというのは、非常に素晴らしいことだと思います。
成果物として、Rubyで図形を描いて作成したというステッカーを配布していたので、ありがたくいただきました。素敵な授業だと思うので、今後もこの授業を受けて少しでもプログラミングに興味を持ってくれる生徒が増えるといいなと思います。

スポンサーLT

コーヒーブレイクで発表した協賛企業のショートプレゼンテーションを紹介します。

speakerdeck.com

最後に

スタディプラスとしてテック系カンファレンスでブースを出すのは初めてのことだったので、不慣れな点や反省する点がいくつかありました。 しかし色々な方に立ち寄って頂き、ユーザーの方とも触れ合う機会も得たりと実りあるものだったと思います。

今後もメンバーの技術への知見を広げる事とOSSコミュニティへの貢献のため、カンファレンスへの協力をしていきたいと考えています。

Kotlin Coroutinesを(SDKに)導入しました!

初めまして。Studyplus開発部の若宮(id:D_R_1009) です。9月よりAndroidアプリの開発を行っています。

Kotlin、いいですよね。Kotlin Coroutines、シビれますよね。
ということで、Kotlin1.3がリリースされたことを記念してStudyplus-Android-SDKにKotlin Coroutinesを早速導入した話をします。

Studyplus-Android-SDKとは

Studyplus APIの詳細はこちらをご確認ください。

Studyplus-Android-SDKはStudyplus APIを簡単にご利用いただくためのSDKです。社内のAndroidチームメンバーにより開発・メンテナンスされています。
2018年の8月にフルスクラッチを行い1系から2系にバージョンが大きく上がりました。なお、2系からKotlinを利用しています。

Githubリポジトリ : https://github.com/studyplus/Studyplus-Android-SDK-V2

SDKは次の2つの機能を提供しています。

  • Studyplus APIによる学習記録の投稿
  • Studyplus AndroidアプリによるStudyplusとの連携

この学習記録の投稿処理をRxJava/RxAndroidからKotlin Coroutinesへ切り替えたv2.1.0系のリリースを、2018年10月31日に行いました。今回はこのRxJava/RxAndroidをKotlin Corutinesへ置き換えた経緯、置き換えの所感について書いていきたいと思います。

なぜRxJava/RxAndroidをやめてKotlin Coroutinesへ置き換えたのか

stable版がリリースされて嬉しかった 開発チーム内で上がっていた「RxJava/RxAndroidの利用は色々と過剰なのでは」という疑念を解消するためになります。

疑念1 : 利用シーンに対して

SDK内で行う通信処理は、主にサーバーのStudyplus APIを叩き学習記録を投稿する、というシンプルなものになります。そのため、本来Streamを扱うためのRxJavaであるのにOne-Shotな処理を行うに止まっていました。
SDKの基本機能としては現在のSDKでほぼ要求は満たされていると考えており、これ以上Streamを必要とする処理が追加される見込みもありません。

疑念2: SDKが持つ依存関係に対して

SDKを使っていただくデベロッパーの視点に立った時、RxJava/RxAndroidへの依存をSDK側で作成してしまうことに対してセンシティブになる必要があると思っています。
もちろんRxJava/RxAndroidは多くのプロダクトで利用されており、近年では当たり前な選択肢になっています。しかし、Androidの標準ライブラリに組み込まれるには至っていません。

Kotlin Coroutinesでは

Coroutinsのasync/awaitは1回の通信処理に対して1つのCoroutinesとなるため、処理と実装が必要十分な関係性になります。その意味で、SDK内の処理を整理し非同期処理をasync/awaitに統一することは、非常に理にかなった行為であると判断しました。

依存関係については少しチーム内で議論するところなり、はっきりとは解決しませんでした。
ご存知の通りKotlin Coroutinesの導入にはGradle上で依存関係を追加する必要があります。その点において、RxJava/RxAndroidを利用している問題は解決していません。
現時点での結論としては、SDK v2ではKotlinを記述言語として採用していることを重視しました。 RxJva/RxAndroid(+RxKotlin)を利用するよりもKotlin Coroutinesを利用する方が、はるかに依存関係がシンプルであると判断しています。

RxJava/RxAndroidとRxKotlinの比較

RxJava

// API通信
internal interface ApiService {
    @Headers(value = [
        "Accept: application/json",
        "Content-type: application/json"
    ])
    @POST("/v1/study_records")
    fun postStudyRecords(
            @Header("Authorization") oauth: String,
            @Body studyRecord: StudyRecord)
            : Observable<PostStudyRecordsResponse>
}

~~~

// 投稿処理
fun postRecord(context: Context, studyRecord: StudyRecord, listener: OnPostRecordListener?) {
    if (!isAuthenticated(context)) {
        throw IllegalStateException("Please check your application's authentication before this method call.")
    }

    ApiClient.apiClient.postStudyRecords(context, studyRecord)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    { listener?.onResult(success = true, recordId = it.recordId) },
                    { listener?.onResult(success = false, throwable = it) }
            )
}

Kotlin Coroutines

// API通信
internal interface ApiService {
    @Headers(value = [
        "Accept: application/json",
        "Content-type: application/json"
    ])
    @POST("/v1/study_records")
    fun postStudyRecords(
            @Header("Authorization") oauth: String,
            @Body studyRecord: StudyRecord
    ): Deferred<PostStudyRecordsResponse>
}

~~~

// 投稿処理
fun postRecord(context: Context, studyRecord: StudyRecord, listener: OnPostRecordListener?) {
    if (!isAuthenticated(context)) {
        throw IllegalStateException("Please check your application's authentication before this method call.")
    }

    runBlocking {
        try {
            val deferred = ApiClient.postStudyRecords(context, studyRecord)
            val result = deferred.await()
            listener?.onResult(success = true, recordId = result.recordId)
        } catch (t: Throwable) {
            listener?.onResult(success = false, throwable = t)
        }
    }
}

返りの型が変わるだけなので簡単ですね。Singleを使う程度の通信なら、書き換えのコストは非常に低く済みそうです。

まとめ

Coroutinesの導入

通信処理への導入は低コストであると実感しました。 そのためStudyplus Androidアプリへの導入を進めたいなと感じています。 特に新規に追加する機能はCoroutinesを積極的に導入をしようと話をしています。

ひとまず足がかりとして kotlinx-coroutines-rx2 の導入を着々と進めています。こちら、成果が出たら本ブログで公開いたします。

SDKの今後

より依存関係を減らし、軽量なライブラリを目指していきたいと思っています。
GsonやRetrofit、Retrofit-Kotlin-Coroutines-Adapterの依存関係は早期に解消していきます。

今回の置き換えにおいてはRetrofitによる簡潔なインタフェースがあったから迅速に対応できました。 しかしこれらの利便性と何を引き換えにしているか、常に検討しながら開発をしていくべきだとも感じる機会になりました。 今後とも、Studyplus-Android-SDKをよろしくお願いいたします。

最後に

一緒にKotlin Coroutinesを使って開発をしたい方はこちらからぜひご応募ください! 開発部一同、お会いできる機会を心よりお待ちしております。

Sidekiq Enterpriseを使う

お久しぶりです。サーバーサイドエンジニアの花井です。

みなさんは非同期処理に何をお使いですか? ActiveJobでしょうか? Resqueでしょうか? Sidekiqでしょうか?

弊社では歴史的経緯から、上記全てのgemをプロダクトで使っていますが、 この度Sidekiq Enterpriseを導入して非同期処理の統一に着手しました。 Sidekiq Enterpriseの日本語記事があまりなかったので、利用の検討や実際に使う際の注意点などを紹介できればと思います。

What is Sidekiq

SidekiqはRuby製の非同期処理フレームワークです。 resqueを使うと、ジョブのリトライとユニーク性の担保のために自前で実装が必要でつらいので、sidekiqの採用に至りました。

また、非同期処理の流量を制限したい(特に外部サービスへの問い合わせが発生する所)という意図があり、Enterpriseを採用するに至りました。

今回は以下の2機能を使ったので、その設定方法を紹介します。

  • RATE LIMITING
  • PERIODIC JOBS

How to use

install

ここから申し込みをすると利用に必要なキーの情報が送られてきます。 Gemfileに指定された内容を追記して、$ bundle install します。

Enterpriseを入れるとき、同時にProのインストールもします。 Enterpriseを申し込むとProの機能も使えるようになるというのは、こういうカラクリのようです。

Implementation

Wikiが充実しているので、そちらを参考にすればほとんど迷うことなく設定できます。

ActicveJobなどと併用している場合、Sidekiq単体(include Sidekiq::WorkerしたWorker)に変更する必要があるようです(Wikiに例がなかったのと、resqueueからの置き換えだったこともあり、Sidekiq単体で使うように書き換えていますがもしかしたらできるかもしれません)。

RATE LIMITING

大量の子ワーカーを作るような非同期処理1つ1つの中で、APIを叩くようなケースでは、許容量を超えたリクエストをしてしまうことがあると思います。そんな時に指定した量を超えないように制限できる機能がRate Limitingです。 ※Redis 2.8以上が必要です。

弊社ではサブシステムから大量のアプリ内メッセージを送信する際に、APIサーバーへ負荷をかけないようにすることを目的として使っています。

特別な設定は必要なく、流量を制限したいWorkerに、以下のような設定を記述します。

class HogeWorker
  include Sidekiq::Worker
  DELIVER_LIMIT = Sidekiq::Limiter.concurrent('limitter_name', 50, wait_timeout: 5, lock_timeout: 30)

  def perform
    DELIVER_LIMIT.within_limit do
          # 必要な処理
    end
  end
end

Sidekiq::Limiter.concurrent には、リミッター名、同時実行数、ロック空き待ち時間(秒)、ロックを手放す時間(秒)を指定できます。 Web UIにタブが追加され、そこからどのくらいロック待ちが起きたかなどのメトリクスを確認することができますので、チューニングも簡単にできます。

PERIODIC JOBS

非同期処理の定期実行をcron形式で指定できます。Sidekiqを使う場合、sidekiq-cronを使う方も多いと思いますが、Enterpriseにすると標準装備されています。 弊社では集計処理など、ピークタイムを避けて深夜に行いたい処理の実行に活用しています。

/config/initializer/sidekiq.rb の中に以下のように実行を開始する時間と、対象のWorkerを指定します。 cronに設定する時間は内部的にTimeクラスで操作されるので、システムのタイムゾーンに合わせて記述する必要があります。resque-schedulerを使っているとタイムゾーンも指定できるので、システムのタイムゾーンと異なるタイムゾーンを扱っている場合は注意が必要です。

Sidekiq.configure_server do |config|
  config.periodic do |mgr|
    mgr.register('0 20 * * *', Hoge::HardWorker)
    mgr.register('0 21 * * *', FugaWorker)
    mgr.register('0 23 * * *', Piyo::PiyoWorker)
  end

まとめ

プッシュ通知の送信が重複しないように、次はUnique Jobsを利用予定です。 他にも色々な機能があるので、必要に応じて活用していきたいです。

一緒にプロダクトの改善をやってくれる仲間を募集しています!

おまけ

Enterpriseの効果かわかりませんが、設定中に相談のIssueを立ててたところ早めに返事がもらえました。 また、確認の過程で翻訳のIssueが見つかったらしく、コントリビュートもできました。

Google Cloud Functions を利用して社内コミュニケーションを円滑にしていく

はじめに

こんにちはスタディプラスでCTOしています島田です。

現在スタディプラスには新メンバーが続々と入社して、うれしい悲鳴があがっています。 その悲鳴にほんのり応えた例の紹介です。

課題

  • Slackチャンネルが乱立(186のパブリックチャンネル)。そのため新メンバーが、どのチャンネルでどんな目的の会話をしているかを把握する事が困難
  • 上記の理由により、質問や必要な情報を取得するチャンネルを探すコストが上がっていた

f:id:yo-shimada:20181029101901p:plain

理想状態

  • 新メンバーが必要なSlackチャンネルを、迷う事なくすぐに見つけられ発言ができることで業務を円滑に遂行することができる

アプローチ案

理想状態を実現する方法を幅広く考える。

  • A) 全てのチャンネルを把握したメンバーを養成し、新メンバーが必要なチャンネルを聞く
    • =>❌ : 属人性・コストが高い
  • B) 定期的に不要なチャンネルを精査する
    • => ⭕️

定期的に不要なチャンネルを精査する

  • A) 毎月不要なチャンネルかどうかを聞いてまわってアーカイブする
    • =>❌ : コストが高い
  • B) 人力でSlackのアナリティクスから「最後にアクティブだった日」をCSVエクスポートして、そのCSVを利用してプログラマブルなアプローチをする
    • =>❌ : 人力要素がある。CSVだとチャンネルIDがエクスポートされないで、扱いが複雑になりそう
  • C) 不要なチャンネルを「最終投稿から一定期間投稿がない」と一旦定義して、Web APIを利用して過疎化しているチャンネルを探す。それを自動化して定期実行をする
    • => ⭕️

Web APIを利用したアプローチ

このアプローチだけでは「理想状態」へ到達する事はもちろんできないです。しかしそのためのファーストステップとして、まず明らかに不要と思われるチャンネルのアーカイブを自動化するというアイデアは良いかと思いました。

今回やること

  • 不要なチャンネル(30日間投稿がない)をアーカイブする
    • ただしパブリックチャンネルでも_で始まる業務以外のチャンネルは除く。例) #_boardgame
  • アーカイブ処理を自動化して定期実行をする

実現方法

で、実現方法を検討。 - A) チャンネルをアーカイブするプログラムを自分のPCでcrontabを実行する - =>❌ : 属人性が高い - B) チャンネルをアーカイブするプログラムを外部サービスを利用して定期実行 - => ⭕️

大きな課題解決に対する第一歩的でありお試しなので、限りなく運用負荷を低くしたいと思い、以下の技術を利用することにしました。

手順

  1. GCPの事前準備
  2. SalckのTokenを生成
  3. 実装
  4. Cloud Functions デプロイ
  5. Google Apps Scriptで定期実行を登録する

GCPの事前準備

HTTP のチュートリアル の「始める前に」を参考に事前準備。

SalckのTokenを生成

https://api.slack.com/apps より「Creaet New App」からAppをつくりトークンを生成 参考: Slack API 推奨Tokenについて

実装

プログラムの構成

  1. channels.list でチャンネル一覧を取得。
  2. _で始まるチャンネル以外を抽出
  3. 最終投稿を知るために、channels.history で履歴を取得。( channels.info だとAppを作成したユーザーが参加しているチャンネルでないとlatestが取得できなかったため )
  4. 各チャンネルの最終投稿が30日以上経過しているかチェック
  5. 対象となったチャンネルをchannels.archive でアーカイブを実行
  6. chat.postMessage で完了を通知

もろもろ準備

Node Slack SDK をインストール

$ npm init
$ npm install @slack/client -s

アーカイブをする処理を実装

$ vim index.js
const { WebClient } = require('@slack/client');
const token = process.env.SLACK_TOKEN;
const web = new WebClient(token);

function archiveChannels(intervalDays) {
  web.channels.list({ exclude_archived: true })
    .then((res) => {
      const publicChannels = res.channels.filter(c => c.name.indexOf('_') != 0 );
      publicChannels.forEach(c => archiveForlornlyChannel(c, intervalDays));
      // 通知するチャンネルのID
      postMessage('CXXXXXX', 'SlackArchiveを実行しました');
    })
    .catch(console.error);
}

function archiveForlornlyChannel(channel, intervalDays) {
  web.channels.history({ channel: channel.id })
    .then((res) => {
      const ts = res.messages[0].ts;
      const latest = new Date(parseFloat(ts * 1000));
      const now = new Date();
      const diff = now.getTime() - latest.getTime();
      const days = Math.floor(diff / (1000*60*60*24));
      if (days > intervalDays ) {
        console.log(channel.id, channel.name, days);
        archiveChannel(channel);
      }
    })
    .catch(console.error);
}

function archiveChannel(channel) {
  web.channels.archive({ channel: channel.id})
    .then((res) => {
      console.log(res);
    })
    .catch(console.error);
}

function postMessage(channel_id, text) {
  web.chat.postMessage({ channel: channel_id, text: text })
    .then((res) => {
      console.log(res);
    })
    .catch(console.error);
}

exports.ArchiveChannel = (req, res) => {
  archiveChannels(30);
  res.status(200).send('OK');
};

デプロイ

関数をデプロイ

$ gcloud beta functions deploy ArchiveChannel --trigger-http

Slackトークンの環境変数をデプロイ

$ gcloud beta functions deploy ArchiveChannel --set-env-vars SLACK_TOKEN=Slackのトークン

Google Apps Scriptで定期実行を登録する

Google Apps Script(以下GAS)にプロジェクトを作って、定期実行を登録する。

参考:無料でお手軽Cron!Google Apps Scriptを使ってみる

  • GASにプロジェクトを作って、関数をコールするScriptを実装
  • 定期実行をするトリガーを登録
function fetchToSlackArchive() {
  UrlFetchApp.fetch("https://ここに関数URL");
}

実行

  • 設定した時間内で実行がされること(Slackに通知が来ること)を確認

f:id:yo-shimada:20181029102229p:plain

  • GCPのログビューアでみると55チャンネルがアーカイブされました

f:id:yo-shimada:20181029104006p:plain

  • Slakのアナリティクス

f:id:yo-shimada:20181029101940p:plain

まとめ

Cloud Functions を利用して簡単に開発と実行を実現する事ができました。 本質的な課題解決までは遠いですが、まずはその橋頭堡となる仕組みが出来たのではないかと思います。

新しい概念・技術をいきなりプロダクション環境で導入するには腰が重くなってしまいがちですが、社内のプロセスを改善するためにサクッとお試し導入することで、その技術の良さや導入ハードルを経験する事ができたと思います。 スタディプラスでは社内システムやプロダクション環境で積極的に新しい取り組みや技術の活用をして行くことを検討しています。 こういった取り組みに共感ができる方、是非一緒に働きませんか! こちらよりお待ちしております。