Studyplus Engineering Blog

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

スタディプラス のAndroid事情

https://s3-ap-northeast-1.amazonaws.com/esa.s3.prod.lithium.studylog.jp/uploads/production/attachments/8682/2018/07/30/34447/a298aca0-1cc6-4021-aa28-f6f3bb44beeb.png

スタディプラス のAndroid事情

今回はAndroidアプリ担当エンジニアのNabesukeです。
組み込みエンジニア→Android→放浪→iOS→バックエンド→フロントエンド→Androidというよくわからない経歴を持っています。
今回はStudyplus Android版の裏側をご紹介します。TOP画像はこの前のスタディプラス LT大会2018(夏) - Studyplus Engineering Blogで使ったもの。

Android版の現状

Kotlin化を進めているが未だJavaが8割以上

image.png (8.9 kB)

目下Kotlin化( + ゆるいDatabinding)を進めていますが記事執筆時点でJavaは8割以上残っています。
(入社時は9割5分くらいがJavaでした。あんまり覚えてない)

Studyplusは画面が多いのでその分クラス数も多いです(1000ファイル以上あります)。
したがってヒマを見つけてはKotlin化に励んでいますがまだまだ終わりは見えていません。

MVCで作られています

比較的ファイル分割もされており、見やすく作られているので、学習コストも少ない分見やすいです。

使っているライブラリ

  • RxJava2
  • Retrofit2
  • Okhttp3
  • Glide
  • Orma(Roomに置き換えます)
  • fabric

その他広告SDKやらプッシュ通知など

クラスのpackage分けについて

これは開発者の好みとか諸事情がたくさん絡んでくるのでなんとも言えませんが、現状は「ふるまい」毎に分けられています。adapter/dialog/view/などなど。app直下にactivity達が大集合しています。
数えたらapp直下に160オーバーのActivityがありました。これは絶賛整理中なので、最初はもっとたくさんのactivity達がひしめいていました。

いま、どういうことをしているか

いまのところ私一人で開発しているので、ひとりでがんばってるなー程度に見ていただけたら幸いです。

packageの整理

私は機能単位でファイルを分けたい方なのでActivityやFragmentなどのPresentation層部分は機能別で振り分けています。(messageパッケージの中にメッセージ画面と関連するactivityやfragment、adapterなどをまとめる)
こうすると比較的関連activity/fragmentなどのクラスを探しやすくなるメリットがあります。modelやextentionなど、機能横断で使うものは現行そのままにしています。

Kotlin化

image.png (5.1 kB)

常に最優先で進めています。 いまのところ 「ぬくもりあふれる手作業」 で置き換えています。

ファイル数でいうと

  • java 719 file
  • kotlin 178 file(頑張った!)

その他xml多数

となっております。

なんでKotlin化するか?

ざっくり言ってしまうとKotlinが好きだから。これが一番大事だと思います。Kotlinかわいい。 過去、Androidアプリを開発した方で心がくじけた方もKotlinの書きやすさを見たら戻ってきたくなります(Android StudioやAndroid自体の良さもありますが)。自分もKotlinと会わなかったらAndroidに戻ってこなかったです。

大体以下の理由になります。主観が多分に混じっているので見識者はツッコミお願いします。

  • Googleが公式言語として認めている。
  • 割とコードを短縮できる(体感4/5くらいにはなる気がする)
  • スコープ関数や拡張関数、配列に対しての処理が便利。
  • Null safe。javaにもあるが、まだイマイチ使いにくい印象。
  • コルーチン(のasync/await)が使いたい。

MVCからゆるいMVVMへ

MVCで見やすいとは言いましたが、これのデメリットはよく言われるようにFatActivityになりがちです。
今後の機能追加で肥大化する可能性が大いにあります。
よって、MVVMに移行していますが機能追加との兼ね合いで進捗は芳しくありません。

AACを使ってActivityやFragmentから処理をひっぺがす

今は通信処理や、状態の更新処理などがActivityに書かれているので一旦それをViewModelに切り出します(いずれはViewModelからも引き剥がす)。 AACのViewModelはActivityのライフサイクルに合わせてデータをよしなに管理してくれるのでちょっと気持ちが楽になります。楽になりたい。
尚、Layout.xml側にViewModelをbindさせるかどうかは絶賛考え中です。ものによりますが、CustomAdapterとか定義すると途端にコードが追いにくくなる印象。

テスト

リリースの度に胃が痛くなる原因なんですが、いまのところテストがありません。
ぬくもりあふれる手作業でテストしてます。テスト導入したい。入社してから思ってるけど未だ着手の気配すらない。他にやること多すぎるという言い訳をさせてください。

CIツール

Bitriseを使いはじめました。いまのところBranch毎の各Build Variant向けビルドとfabricへのアップロード、play storeへのアルファ版リリースを自動化してる程度です。

おわりに

ぬくもりあふれる手作業が多い開発ですが、 これを書いている間にKotlinが2割を超えました。

image.png (16.0 kB)

私が入ってから5ヶ月。Convert to Kotlin してコードを修正し、ButterKnifeを取り除きDatabindingに移行するという作業を延々と手作業で繰り返しました。この調子で頑張りたいと思います。

「自分だったらもっとうまくやれるぜ!」や「ここをもっとこうしたら良くなるのにな」と思った方、弊社でお待ちしています

AWS Glueを用いたデータ分析基盤を構築した✨

こんにちは。業務委託の@morix1500と申します。

この度、スタディプラス様からデータ分析基盤の構築の業務委託を受け、AWSのマネージドサービスを用いて構築を行いました。
その際に得られた知見を共有したいと思います。

データ分析基盤について

今回スタディプラス様から受けたデータ分析基盤の要件は以下のようなものでした。

  • S3にあるログをAWS Athenaから閲覧できるようにしてほしい
  • S3にあるJSON形式のログを列指向型のフォーマット(Parquet)に変換してほしい
  • ログは順次取り込み(毎朝、昨日分のログが見れるようにする)

すでにログはS3にあったのでログ収集は終わっています。

データ分析基盤の構成

今回作成したデータ分析基盤はAWSのマネージドサービスで完結してます。
今回構築したのはGlueの部分です。

f:id:mori_morix:20180730165858p:plain

Glueの構成や初期構築の手順は以下のドキュメント通りです。 https://aws.amazon.com/jp/blogs/news/build-a-data-lake-foundation-with-aws-glue-and-amazon-s3/

構築時のTIPS

S3のパーティション分け

Amazon Athena のパフォーマンスチューニング にも記載されていますが、
データをパーティション分けするとパフォーマンスが上がります。

今回取り込み対象のS3のログのパスは以下のようにうまいことパーティション分けされていました。

s3://{BucketName}/2018/07/30/12/hoge.log.gz

この場合は

でパーティション分けされています。

Glueでこの形式のログをロードすると「partition_0」という名前でパーティションをAthenaなどで参照できるようになります。

Glueジョブの対象データの絞り込み

今回行いたいJSONからParquetに変換作業は、Glueの「ETLジョブ」を使用します。
コンソールでポチポチやるといい感じの変換用Pythonスクリプトを出力してくれますが、
デフォルトだと対象のS3パスのデータを毎回全件取り込んでしまいます。

「ログは順次取り込み」という要件があるので、なんとかジョブ対象のデータを絞り込みたいです。

その方法は、上記で作成したパーティションを利用します。
作成したパーティションはGlueのETLジョブでも参照できますので、ジョブ実行時パーティションで絞り込みを行います。
この機能のことを「Pre-Filtering」というそうです。
https://docs.aws.amazon.com/glue/latest/dg/aws-glue-programming-etl-partitions.html

# ソースを一部抜粋

# 1時間前の時間を指定
one_hour_ago = datetime.today() - timedelta(hours = 1)
year  = one_hour_ago.year
month = '{0:02d}'.format(one_hour_ago.month)
day   = '{0:02d}'.format(one_hour_ago.day)
hour  = '{0:02d}'.format(one_hour_ago.hour)
pushDownPredicateString = "(partition_0='{0}' and partition_1='{1}' and partition_2='{2}' and partition_3='{3}')"
pushDownPredicateString = pushDownPredicateString.format(year, month, day, hour)

datasource0 = glueContext.create_dynamic_frame.from_catalog(
    database = "Glue DB名",
    table_name = "テーブル名",
    push_down_predicate = pushDownPredicateString,
    transformation_ctx = "datasource0")

変換後のデータをS3に出力する際パーティション作成

GlueのETLジョブでは、変換後のデータをS3に保存できます。
その際にS3のパーティションを分けないと、Athenaでの参照時にパフォーマンスが上がりません。

こちらの設定もPythonで設定できます。
"year"や"month"と指定していますが、その前のマッピングで
partition_0 と year を関連付けないと使えませんので注意してください。

# ソースを一部抜粋

datasink4 = glueContext.write_dynamic_frame.from_options(
    frame = dropnullfields3, connection_type = "s3",
    connection_options = {
        "path": "s3://{Bucket Name}/",
        "partitionKeys": ['year','month','day','hour']
    },
    format = "parquet",
    transformation_ctx = "datasink4"
)

この設定を行うと以下のようなログが出力されます。

s3://{BucketName}/year=2018/month=07/day=30/hour=12/xxxxxxxxxxxxx.parquet

ログのカラムの増減時の対応

Glueはよく出来ていて、カラムが増えても動作に影響がありません。
ログのカラムが変更されると、CrawlersのTables Updatedが「1」になります。
f:id:mori_morix:20180730165904p:plain

カラムが増えたら、ETLジョブのマッピングのところに追加されたカラムの情報を追加。
カラムが減ったら特になにもする必要はないですが、ETLジョブのマッピングにはその情報は不要なので削除してもよいでしょう。

最後に

このようにログをデータ分析で使える形式にするためにやらなきゃいけないことが
AWSのマネージドサービスですべて済んでしまいました。

そして導入は非常に簡単です。
同じ課題をお持ちの方はこの記事を参考にデータ分析基盤を構築してみてはいかがでしょうか!

また宣伝になりますが、私@morix1500はこのような基盤構築やその他クラウド系のインフラの仕事を副業としてやらせていただいています。
なにかございましたらTwitterのDMなどでぜひ!

スタディプラス LT大会2018(夏)

こんにちは、CTOの島田です。
今回は7/13(金)に行われた社内LT大会についてです。(平成最後の13日の金曜日!?)

LT大会とは?

  • 趣旨
    • 日ごろの勉強・活動・興味関心を共有し、相互理解を促進する
  • 内容
    • 業務内外問わず、自身が技術を用いて取り組んだものを発表
  • ルール
    • 1人5分の持ち時間
    • CTO、外部審査員による審査により、賞を決定し、受賞者に賞品を贈呈
      • 外部審査員にリブセンス創業メンバーの桂大介氏を招待

発表内容一覧

タイトル 概要
AlexaとサーバーレスでつくるVUI勉強記録サービスのプロトタイプ Echo Dot, Lambda, DynamoDB, API Gateway, React を利用して、VUI勉強記録サービスのプロトタイプを作成、その発表をしました。
Graal Graal VM
暗号通貨の裁定取引ボットを作った Node.jsで暗号通貨の裁定取引ボットを作り、お金を稼ぎました
テックブログ考察 各社のテックブログをクロールして分析
iOSDC2018草稿 iOSDC2018で発表する内容を少し話しました
Webアプリの脆弱性スキャン OWASP ZAP を使ってStudyplus for Schoolの脆弱性検査をしました。
弊社の組織課題に関するご提案 ボードゲームサークル向けwebサービスをNuxtとFirebaseで作り始めました
入門 DAapps & Web 3.0 ブロックチェーンを基盤にしたアプリケーションDAppsとWeb 3.0について発表しました。
スタプラAndroid Studyplus for Androidで驚いたことと現状。

発表会の風景

結果

  • 桂賞: 「弊社の組織課題に関するご提案」
    • 寸評:スライドの構成と持ち時間ピッタリの発表内容。実装して動作する環境を用意した点。
  • CTO賞: 「AlexaとサーバーレスでつくるVUI勉強記録サービスのプロトタイプ」
    • 寸評:スマートスピーカとStudyplusのプロダクトを絡めた着眼点と、プロトタイプを実装した点。

受賞作品の紹介

speakerdeck.com

賞品

受賞者には目録が与えられ、それぞれの希望の賞品が贈呈されました。

  • 桂賞:ボードゲーム3万円分

  • CTO賞:Kindle Oasis

最後に

  • こういったLT大会のような場を定期的に持つ事で、メンバーにとって日頃の過ごし方の中にアウトプットの意識が根付くと感じました。
  • 発表内容もそれぞれユニークで、日々の取り組みでは触れる事のない内容もあり、良いインプットの場になったと思います。
  • ボードゲームに興味のある方、スタディプラス で一緒にボードゲームをしませんか?
  • 次回は半年後の1月に実施を予定。その際にはさらにメンバーが増えているかも!?

Railsで作られた管理画面にVue.jsを導入した話

Studyplusのweb版を担当していた久保です。 最近はRailsを触ったりしています。

今回は社内向けの管理画面を作る際に、どうしても動的にDOMを操作する必要があったのでjQueryの代わりにVue.jsを導入してみました。

なぜVue.jsを選んだのか

  • Railsが生成したhtmlをテンプレートとして使うことができる
  • Rails5系以降であれば、webpackerを利用するだけで良いので導入が楽

導入方法

導入時の環境は以下

  • rails: 5.2.0
  • ruby: ruby 2.5.1p57
  • node: v8.11.2
  • yarn: 1.7.0
  • webpacker: v3.5.3

webpacker

gem 'webpacker'
$ bundle install
$ bundle exec rails webpacker:install

Vue.js

$ bundle exec rails webpacker:install:vue

Vue.jsを動かす

webpacker.ymlに記載されていますが、標準だと、app/javascript/packs配下のファイルがエントリーファイルです。

JavaScriptファイルの読み込み

例えば app/javascript/packs/hello-world.js とした場合、ヘルパーメソッドを使って簡単に読み込むことができます。

...
  <%= javascript_pack_tag 'hello-world', 'data-turbolinks-track': 'reload', defer: true %>
</head>
...

今回は app/javascript/components 配下にVue.jsのコンポーネントを何個か定義して、エントリーファイル側でimportする形で実装しました。

開発環境で動かす

bin/webpack-dev-server というファイルができており、これを使うと JavaScript を編集した際にビルドが走りブラウザが勝手にリロードされるようになります。

web: rails s -p 3000
client: sh -c 'rm -rf public/packs/* || true && bin/webpack-dev-server'

みたいなファイルを用意して、foreman を追加し

$ bundle exec foreman start -f Procfile.dev-server

とすると良いかもしれません。

本番環境

細かいチューニングが必要な場合は webpack の設定を弄るべきかもしれませんが、特に何もしなくても assets-precompile 時にいい感じになります。ここが本当に楽で素晴らしいと思っています。

Railsで生成したフォームの変化を検知して動的にDOMを操作する

Vue.jsを採用した理由として

Railsが生成したhtmlをテンプレートとして使うことができる

と書きましたが、簡単にその機能の紹介をします。

【サンプル】select要素の変更に応じてcss classの付け外しを行う

以下が設定の一部とマウント対象のerbです。

const ConversionType = {
    el: '#js-conversion_type',
    data: {
        form: {
            conversionType: document.getElementsByClassName('js-conversion_type')[0].value
        }
    },
    computed: {
        isRequired: function() {
            switch (this.form.conversionType) {
                case 'nop':
                    return {
                        title: false,
                        urlIOS: false,
                        urlAndroid: false,
                    };
                // 以下case文省略
            }
        }
    }
}

export default ConversionType;
  <div id="js-conversion_type">
    <div class="row mb-4">
      <div class="col-xs-3">
        <%= form.label :conversion_type %>
      </div>
      <div class="col-xs-9">
        <%= form.select :conversion_type, MessageDraft.enum_for_selectbox(:conversion_type), {}, class: 'form-control js-conversion_type', 'v-model' => 'form.conversionType' %>
      </div>
    </div>
    <div class="row mb-4">
      <div class="col-xs-3">
        <%= form.label :button_title, 'v-bind:class' => '{required: isRequired.title}' %>
      </div>
      <div class="col-xs-9">
        <%= form.text_field :button_title, id: :message_draft_button_title, class: 'form-control', 'v-bind:disabled' => '!isRequired.title' %>
      </div>
    </div>
    <div class="row mb-4">
      <div class="col-xs-3">
        <%= form.label :button_url_ios, 'v-bind:class' => '{required: isRequired.urlIOS}' %>
      </div>
      <div class="col-xs-9">
        <%= form.text_field :button_url_ios, id: :message_draft_button_url_ios, class: 'form-control', 'v-bind:disabled' => '!isRequired.urlIOS' %>
      </div>
    </div>
    <div class="row mb-4">
      <div class="col-xs-3">
        <%= form.label :button_url_android, 'v-bind:class' => '{required: isRequired.urlAndroid}' %>
      </div>
      <div class="col-xs-9">
        <%= form.text_field :button_url_android, id: :message_draft_button_url_android, class: 'form-control', 'v-bind:disabled' => '!isRequired.urlAndroid' %>
      </div>
    </div>
  </div>

ConversionType.jsの説明

el: '#js-conversion_type'

に関してですが、 公式ドキュメント を読むと

既存の DOM 要素に Vue インスタンスを与えます。

render 関数または template オプションも存在しない場合、マウントしている DOM 要素にある HTML がテンプレートとして抽出されます。

と書いてあります。この設定により、_form.html.erb 配下を Vue.js のテンプレートとして扱うことが可能になります。

data: {
    form: {
        conversionType: document.getElementsByClassName('js-conversion_type')[0].value
    }
},

ドキュメントは こちら です。 select要素の初期値が後述する v-model を用いた場合に設定されなかったため、値を設定しています。

select要素の変更に合わせて、class等を操作する

v-model ディレクティブ と、computed プロパティ を用います。

<%= form.select :conversion_type, MessageDraft.enum_for_selectbox(:conversion_type), {}, class: 'form-control js-conversion_type', 'v-model' => 'form.conversionType' %>

と設定するだけで、onChange 等を書かずに値の変更を検知できます。

更に computed プロパティを用いて form.conversionType の値に応じて各 input 要素が必要かどうかの bool値を持つオブジェクトが算出されるように設定します。

computed: {
    isRequired: function() {
        switch (this.form.conversionType) {
            case 'nop':
                return {
                    title: false,
                    urlIOS: false,
                    urlAndroid: false,
                };
            // 以下case文省略
        }
    }
}

テンプレート側からは

<div class="row mb-4">
  <div class="col-xs-3">
    <%# isRequired.title が true の場合は required クラスが付与される %>
    <%= form.label :button_title, 'v-bind:class' => '{required: isRequired.title}' %>
  </div>
  <div class="col-xs-9">
    <%# isRequired.title が false の場合は disabled 属性が付与される %>
    <%= form.text_field :button_title, id: :message_draft_button_title, class: 'form-control', 'v-bind:disabled' => '!isRequired.title' %>
  </div>
</div>

というふうに参照できます。

まとめ

Vue.js全く触ったことなかったのですが、あくまでRailsメインで画面を組んだ時に、補助としてVue.jsを使うのは導入と学習コストの点から悪くないのではという印象を受けました。

中高生国際Rubyプログラミングコンテスト2018への協賛

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

2018年7月15日(日)から応募受付が開始される中高生国際Rubyプログラミングコンテスト2018にて、スタディプラスはGold PARTNERとして協賛させていただきます。

f:id:studyplus:20180705150648p:plain

スタディプラスではこれまでもRubyKaigi 2018等のカンファレンスへの協賛をしてきましたが、その目的はおおまかに以下のようなものになります。

  • 技術カンファレンスへの貢献
  • メンバーの技術的な知見を広げる
  • スタディプラスの認知拡大

今回のプログラミングコンテストへの協賛は上記の理由とは異なり、スタディプラスのミッションや事業と親和性があり、なんらかの協力ができないかと考えたからです。

弊社のサービス「Studyplus」は多くの中高生の方に利用していただいており、またサーバーサイドはRubyを利用して開発しています。

コンテストに参加される中高生の方々にも、是非Rubyを通して、創ることの楽しさや、新たな可能性を発見をしてもらえればと思います。

nginxのX-Accel-Redirectを使った縮小画像配信サーバ

インフラまわりを担当しております。id:rmanzokuです。

今回は、画像配信サーバをnginxを使ってプチリプレースをしたので その実装方法を紹介します。

課題と対応

Studyplusでは、ユーザーが投稿した画像や教科書の表紙画像を任意のサイズに縮小し配信する機能があります。

この機能はリリース初期から存在し、Javaで実装されていました。 退職済みメンバーの個人リポジトリのライブラリに依存していることもあり、メンテナンスコストが非常に高くなっていました。

(皆さんもそういう経験ありますよね?)

リリース初期では、複数の機能を搭載したJavaアプリケーションでしたが現在では

  • 画像管理テーブルからリクエストされたIDに対応する画像ファイルのURLを取得する
  • そのファイルをクエリ文字列で指定した任意のサイズに縮小しユーザーへ返答する

という非常にシンプルな機能しか残っていませんでした。

今回、この画像配信機能のメンテナンスコストを下げるためリプレースを実施しました。

実装方針

前述の課題に対応するため、3パートに分けて進めました。

  1. 画像IDからDBアクセスし、画像ファイルのURLを取得するアプリケーションサーバ
  2. nginxによるX-Accel-Redirectを使った画像の取得
  3. nginxによるcubicdaiya/ngx_small_lightを使った画像リサイズ

Go言語の採用

画像IDからDBアクセスし、画像ファイルのURLを取得するアプリケーションにはGo言語を採用しました。

Go言語は豊富な標準ライブラリを備えており、今回の要件ではMySQLドライバ程度の外部ライブラリで実装できます。

実際に、100行以下の1ファイルで実装できました。 コードも見通しよくメンテしやすいアプリケーションになったと感じています。

X-Accel-Redirectとは

X-Accel-Redirectとは、nginxの内部リダイレクトを実行させるためのトリガーとなるヘッダです。

アプリケーションサーバから静的ファイルを返答する場合、ロジックのないファイルの返答のためにアプリケーションの処理が専有されてしまいます。 これを避けるために、アプリケーションサーバはX-Accel-Redirectにファイル名などを返しnginxがリダイレクトすることでアプリケーションの負荷を下げることができます。

この機能は、Rails(Rack)にも利用されており、Apache HTTP ServerではX-Sendfileヘッダとして知られています。

要件次第では、静的ファイルへのアクセス方法が一意に決まらず、DBアクセスや認証を必要とする場合があります。 アプリケーションサーバで処理をした結果、X-Accel-Redirectを有効活用することでアプリケーションとnginxの役割分担をすることが可能になります。

と、説明しましたが、nginx上の別のlocationにリダイレクトするためにも利用できます。 今回の実装では、X-Accel-Redirectは画像縮小のためのlocation /image_redirect/へのリダイレクトに利用し、X-Imagefileという独自ヘッダに画像URLを入れています。

具体的なフロー図

画像IDを1111、縮小後サイズを100x100としたときのフロー図です。

f:id:rmanzoku:20180628105537p:plain

画像ファイルURL取得サーバ

Go言語で実装した画像ファイルURLを取得するアプリケーションサーバです。

  1. URL.pathから画像IDを取得する
  2. 画像IDを使って画像管理テーブルから画像ファイルURLを取得する
  3. X-Accel-Redirectに内部リダイレクト先とクエリ文字列を入れる
  4. X-Imagefileに画像ファイルURLを入れる

旧実装では、画像の取得から圧縮まで行っていましたが、 本実装ではヘッダに値を入れているだけで、画像ファイルを扱う必要はありません。

以下にエラーやHTTP部分を除いて一部抜粋したコードを示します。

var db, _ = sql.Open("mysql", dsn)

func handler(w http.ResponseWriter, r *http.Request) {
    uri := r.URL.String()
    path := r.URL.Path

    var id int
    var accelRedirect string
    var imageFile string
    var err error

        // リクエストパスからIDを取得
        id, _ = strconv.Atoi(path[1:])

        // IDから画像ファイルURLを取得
    _ = db.QueryRow("SELECT filename FROM image_entries where id=?", id).Scan(&imageFile)

        // X-Accel-Redirectに内部リダイレクト先とクエリ文字列を入れる
    accelRedirect = "/image_redirect/?" + r.URL.RawQuery
    w.Header().Set("X-Accel-Redirect", accelRedirect)

        // X-Imagefileに画像ファイルURLを入れる
    w.Header().Set("X-Imagefile", imageFile)
}

nginx設定例

nginxでは、画像ファイルURLから渡されるヘッダに基づいてリダイレクトと画像縮小ができるように設定します。

次にserverディレクティブの設定例を示します。

server {
    listen 80;

    # クエリ文字列 `?w=100&h=100` は保持されたまま内部リダイレクトされる
    location /image_redirect/ {
        internal;

        set $path_to $upstream_http_x_imagefile;
        proxy_hide_header upstream_http_x_imagefile;
        proxy_pass $path_to;

        small_light on;
        small_light_getparam_mode on;
    }

    location / {
        proxy_pass http://ImageFetchGo;
    }
}

アプリケーションサーバImageFetchGoから ヘッダX-Accel-Redirect: /image_redirect/?w=100&h=100と返すことで、クエリ文字列を保持したまま内部リダイレクトが可能です。 また、$upstream_http_x_imagefileからヘッダX-Imagefile: https://image-storage/hogehoge.jpgの画像ファイルURLが取得できるため、proxy_passすることで画像を取得できます。

取得した画像はcubicdaiya/ngx_small_lightを利用してクエリ文字列に応じたサイズへ変換されます。 縮小された画像は、無事ユーザーへ届けられます。

まとめ

nginxのX-Accel-Redirectを利用した画像配信サーバのリプレースについて紹介しました。 普段馴染みのないヘッダですが、各機能の実装を最小限にし組み合わせることで、強力な力を発揮できます。

スタディプラスでは、ミドルウェアを活用してアプリケーション全体を効率化できるインフラエンジニアを募集しております! この記事に興味を持ってもらえるならぜひお話だけでも遊びにきてもらえればとおもいます。

合同筋トレ会を開催しました👍

先日こんな記事を投稿した花井(id:hiroyuki-hanai)です。 以下の部分がきっかけで、ファインディ株式会社様筋トレ部と弊社筋トレ部で合同筋トレを開催しました! 👏

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

弊社筋トレ部は、Androidエンジニアである渡辺さんが部長を務める社内部活動です。 平日の業務後に東京体育館で不定期に4人程度で活動しております。 (オリンピックに向けた建て替えにより、6月いっぱいでしばらくお休みになってしまうため、活動継続の危機に瀕しております。)

ファインディ株式会社様といえば、筋トレにちなんだユニークな企画をされていたので、トレーニーエンジニアの皆様はご存知のことと思います。

フリーウェイトを使ったトレーニングをすると予告してしまったため、弊社筋トレ部からは私以外誰も参加しませんでした。😢 ファインディ株式会社様筋トレ部からは、CTOの佐藤さんにお越しいただき、総勢2名で筋トレを楽しみました!

メニュー

この日は2人とも胸の日だったので、開幕ベンチプレスをキメようと思ったものの、長蛇の列… 仕方なくマシントレーニングで時間と大胸筋をを潰します。

ラットマシン

高重量を扱う際はに、広背筋への意識が欠かせませんよね。ということで、ラットマシンで体を温めます。

チェストプレス

今日は胸の日ですから、大胸筋のウォームアップをします。

ダンベルフライ・ダンベルプレス

東京体育館はマシンエリアと、フリーウェイトエリアが分かれており、予約待ちの間マシントレーニングをしていました。 前の人が早めに上がったらしく、別の人にベンチを使われてしまっておりました。 フリーウェイトエリアで待っている間手持ち無沙汰なので、ダンベルフライ・インクラインダンベルプレスで大胸筋にさらなる刺激を与えます。

ベンチプレス

さぁ、本日のメイン・トレーニングです。1人でやっているとなかなか追い込みきれないのですが、今回は佐藤さんの優しいサポートもあり、久しぶりに追い込むことができました!🤗 ドロップセットもこなして、大胸筋はバーンアウトです。

シットアップ

ストレッチをしながら、腹筋トレーニングの話題になったので、そのままシットアップに突入しました。

いつも以上に追い込めたので、とても充実したトレーニングができました。佐藤さんありがとうございました!

最後に記念撮影! f:id:hiroyuki-hanai:20180619153301j:plain