ハコベルコネクトをリリースしました!

サーバサイドエンジニアの加藤です。
昨年8月に印刷から運送マッチングサービスのハコベルのチームに異動し、ハコベルコネクトの開発を進めてきました。

ハコベルコネクトとは

現在の物流業界における最大の課題はドライバー不足。ハコベルチームでは、現場の課題を調査し、その原因を探ってきました。見えてきたのは、複数の運送会社によって仕事が行われていることで、システム化が進まず紙・電話・FAXなどアナログで生産性を上げにくい現場環境でした。

ハコベルコネクトは、この情報断絶を解決すべく、大手物流荷主、一般貨物事業者など運送会社を自由度高くつなぐ物流プラットフォームを目指しています。

先月 1月24日に記者発表をし、日経新聞やTechCrunchにも取り上げられました。弊社CTOの泉がスマホのGoogle Chromeの新規タブで出てきたとのことで、社内でもちょっとした話題になりました。

今までも提供してきたサービスは、ハコベルカーゴ※に名称変更。これからもラストワンマイル向けのマッチングサービスを提供していきます。

仕事をする人に心地よい体験を

ハコベルコネクトは、PMやデザイナー・エンジニアが現場に足を運び、MVPを開発、実証実験を重ねて、徐々にその姿を明らかにしてできてきたプロダクトです。いくつもの運送会社の事業所に出向き、たくさんの方々にインタビューさせていただいたり、トラックに同乗させていただいたり。仕事をする人がどんな課題と向き合っているのか。様々なプロセスを経て、方向性が定まってきました。

ドライバーアプリイメージ

 

ハコベルコネクトという名前もその中で生まれてきました。SNSのように、会社と会社がつながりスムーズに情報のやりとりができることで、快適に仕事が出来るようにという想いが込められています。

ハコベルコネクトイメージ

技術負債の芽を最低限に ー今しかないでしょ!ー

さて、プロダクトとしてのリリースに向け、エンジニアとしてはどんなチャレンジがあったでしょうか。

  • 実証実験向けに急いで作られた側面があったため、既に技術負債気味なところがある
  • 業界独自の業務をモデリングしているため、キャッチアップしにくい
  • よくある機能でも複雑度が高く、要件の不整合が起きて開発が進みにくい

ハコベルチームには、私を含めラクスルの開発に関わってきたメンバーもいます。

「そうだ、ラクスルを作り直そう!」という投稿や「生まれ変わらNight -技術的負債からの一発逆転-」というイベントでご紹介させて頂いた通り、ラクスルは技術負債の課題に向き合ってきました。

(ちなみにまだまだ先はありますが、経営陣の理解もあり、ラクスルの技術負債の解消は着々と進んでいます。)

その教訓を活かして、ハコベルコネクトでは、リリース前に負債の芽の解消にも時間を使ってきました。リリース前ならば、ユーザー影響を気にせずにあらかじめアーキテクチャの整備を進めることが出来ます。「今しかないでしょ!」を合言葉に、気なっていたところはどんどん書き直しました。

ただし、特定業界向けのSaaSというサービスの成長特性を考えて、最初からドメイン分割やマイクロサービスを採用することはしていません。ラクスルで蓄積してきているベストプラクティスを取り入れ、まずはRails アプリケーションとしての Rail にきれいに乗るというところに集中しました。

フロントは、ラクスルの他のプロダクトでも採用しているVue.jsを使い、実証実験後にUIをフルリニューアル。Web APIが必要だったため、コントローラやサービスは約80%を書き換えました。

Web APIの開発中には、GraphQLが話題になったり、gRPC-Webが正式リリースになったりしたタイミングで、チーム内でGraphQLのPOCも行なったりしていたのですが、開発の状況を考慮して今回はオーソドックスにRESTful APIで開発していくことにしました。

フロントメンバーとのコミュニケーションやAPI エンドポイントの可視化のため、API仕様はSwaggerで記述することにしています。

チームでプロダクトを開発する ーペア&モブプロ、デザインスタジオー

先ほども書いた通り、ハコベルコネクトは、業界独自の業務をモデリングしているためキャッチアップしにくくチームの生産性がなかなか上がりにくい状態でした。

6ヶ月間でチームも倍増。新メンバーのキャッチアップスピードをあげたり、モチベーションを保ちつつ機能開発スピードを上げるのが重要課題になっていました。

そのため、複数人でコーディングするモブプログラミングやペアプログラミングを取り入れて、難しいドメイン知識を共有しつつ開発を進めたり、モチベーションが上がりにくい機能開発をわいわいと議論しながら進めました。

モブプログラミング用のスペースも導入され、今ではハコベルだけでなく他のチームで取り入れています。

モブプログラミングの様子

また、ラクスルの他のチームで行なっていた、コラボレーティブデザインの手法である「デザインスタジオ」も取り入れました。機能に対して色々なアイデアが出るのはもちろん、デザイナーも含めてこれから作る機能のデータ構造を共有したり、機能の背景を理解するのに役に立っています。

ハコベルチームには、運送業界出身のメンバーもいます。要件がわからない時はすぐに話を聞けるのも、チームで開発を進める上ですごく助けになりました。

新メンバー募集中です

さて、ハコベルコネクトはリリースしましたが、私たちの毎日を支えてくれている物流の現場には課題がたくさんあります。課題を解決すべく、ハコベルコネクトも成長させていきたいと思っています。

「仕組みを変えれば、世界はもっとよくなる」

エンジニアリングの力で世界を変えたいフロントエンドエンジニア、サーバサイドエンジニアを絶賛募集中です。興味を持たれた方は是非一度オフィスに遊びにきてください!

※ 2019/3/4 ハコベルマッチングからハコベルカーゴに再名称変更したためブランド画像更新しました。

“CircleCI agent received a kill signal midway through the job” と急に言われるようになった話(2/12追記)

ラクスルでサーバサイドエンジニアをやっている小林です。

追記(2019/02/12)

CircleCI のバグ(?)だったようで、すでに修正されてます。

また、原因を少し追ったので、追記しました。

結論

CircleCI の command 内で exit すると、エラーになるので、

true / false コマンドを使って回避しました。

 

本編

今日も元気に開発を行っていたら、CircleCI のテストが下記のようなエラーで

失敗するようなりました。

最初、メモリが足りなくて殺されたのかなと思い、Rebuild with SSH でコンテナに ssh して、

topps を見ていたのですが、どうやらメモリ不足というわけではなさそうでした。

また、過去にテストの通っていたブランチを Rebuild してもテストがコケるので、

CircleCI の挙動が何かしら変わったのかなと思い、.circleci/config.yml の設定を見直してみました。

すると、command 内で下記のようなことをしているコードがありました。

status=0
for file in $(
  circleci tests glob \
  "src/*/*Bundle/Tests/**/*Test.php" | \
  circleci tests split --split-by=timings
)
do
  php ./bin/phpunit -c app --log-junit $CIRCLE_TEST_REPORTS/phpunit/${file}.junit.xml ${file}
  if [ $? -ne 0 ]; then status=1; fi
done
exit $status

最後の exit が明らかに怪しいので、試しに消してみたところテストが通りました。

どうやら、exit すると circleci-agent も死ぬようになったのかなと推測されます。

というわけで、下記のように true / false コマンドを使うように修正しました。

status=true
for file in $(
  circleci tests glob \
  "src/*/*Bundle/Tests/**/*Test.php" | \
  circleci tests split --split-by=timings
)
do
  php ./bin/phpunit -c app --log-junit $CIRCLE_TEST_REPORTS/phpunit/${file}.junit.xml ${file}
  if [ $? -ne 0 ]; then status=false; fi
done
$status

変数にコマンドを代入すると、その変数を評価することで、コマンドを実行することができます。

2時間ほどハマったので、同じようにハマった方のお役に立てれば幸いです。

 

追記(2019/02/12)

CircleCI の挙動が何かしら変わった

のところを、もう少し深ぼってみました。

まず、再現条件をもう少し明確にするために、空のリポジトリに下記のような .circleci/config.yml を用意して CircleCI を走らせてみました。

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1

    steps:
      - checkout

      - run:
          command: exit 0

しかし、これではテストは失敗しませんでした。

問題の起きた .circleci/config.yml と見比べてみると、問題の起きたテストでは shell を変更していたので、下記のように .circleci/config.yml を修正してみました。

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.5.1

    shell: /bin/bash --login

    steps:
      - checkout

      - run:
          command: exit 0

すると、前述のエラーと共にテストが失敗するようになりました。

shell を指定しない場合、 /bin/bash -eo pipefail で実行されているので、念の為、
/bin/bash -eo pipefail --login を設定してみましたが、やはりテストが失敗しました。

どうやら、/bin/bash --login を設定し、かつ exit するとダメなようです。

さて、--login オプションを指定すると、起動時に ~/.profile を、終了時に ~/.bash_logout を実行するようになります。

Man page of BASH#起動

そこで、Rebuild with SSH でログインして、~/.bash_logout の中身を見てみると、下記のようになっていました。

# ~/.bash_logout: executed by bash(1) when login shell exits.

# when leaving the console clear the screen to increase privacy

if [ "$SHLVL" = 1 ]; then
    [ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
fi

試しに、/usr/bin/clear_console を手動で叩いてみたところ、無事(?)「”CircleCI agent received a kill signal midway through the job”」というエラーと共にテストが失敗しました。

どうやら、/bin/bash --login で exit すると、~/.bash_logout内の /usr/bin/clear_console が実行され、テストが失敗する、というシナリオのようです。

さらに、/usr/bin/clear_console の中で何をやっているのかを追ってみました。

https://github.com/linuxmint/bash/blob/master/debian/clear_console.c

コードを見たところ、/dev/tty/dev/tty0/dev/console 、stdinstdoutstderr の順番に、ioctl(fd, KBDKBTYPE, &arg) を実行し、キーボードの種類が取得できたものについて、コンソールをクリアするという処理を行っていました。

特に kill している風でもなかったので、また、Rebuild with SSH でコンテナにログインして、開いているデバイスファイルを順に確認することにしました。

screen コマンドをインストールし、順番にデバイスファイルに接続していきます。

すると、screen /dev/console を実行すると、「”CircleCI agent received a kill signal midway through the job”」とエラーになってしまいました。

どうやら、/dev/console を参照しようとすると、エラーになるようです。

と、ここまで調べたところで、CircleCI側で修正されたようで、/dev/consoleを参照しても、エラーにならなくなりました。

無事、修正されたということで、追加の調査はここまでになりました。

MeetUpシールがとことん簡単に作れるプロダクトの話

突然ですがシールの作り方、わからなくないですか!?

ラクスルなら誰でも簡単にデザインできるサービス担当の武政です。

弊社でもミートアップや勉強会がよく行われていますが
企画にステッカーがあると一体感がでてテンションも上がりますよね。

でも忙しい中でデザイナーさんにお願いするのは申し訳ない…外注だと予算もかかる…
そんなお悩みの声に応えて、パパッとステッカーを制作できるプロダクトをリリースしました!

オンラインデザインのシール・ステッカー・ラベルです。

  • 円形、正方形、長方形が合計9サイズ
  • シール・ステッカーのテンプレートも約300点公開(2018/11現在)

MeetUp用のシールを作ろう!

私もオンラインデザインで、リリース記念のシールを作ってみました

MeetUp用シールの無料デザインテンプレートも公開!

ミートアップですぐ使えるテンプレートも用意しました!
ぜひロゴやテキスト入れて遊んでみてください。
※画像をクリックするとオンラインデザインに遷移します

①ミートアップ:おしゃれ  (円形 直径50mm)
円形はPCに貼るには定番ですね

テンプレート編集はこちらから>>

②ミートアップ:シンプル (正方形 50*50mm)
ロゴを置いて、背景色をロゴ内の色に変えるだけでそれらしくなります

テンプレート編集はこちらから>>

シールでこんな遊び方も!?

少年時代に熱中したキラキラ風シールも作れちゃいます。
③キラキラ風シール:※ホログラム加工ではありません (正方形 50*50mm)

テンプレート編集はこちらから>>

シールになった弊社CTOもニッコリ。

シール制作プロセスの何をシステムで解決したのか?

シールのデータ制作はコンテンツとしては驚くほど簡単ですが
制作データの構造が難しいという不思議な商品です。

  • 具体的に難しいポイントは「カットパス」データの作成
  • カットパスとはシールの形に切り抜くためのパスデータのこと
  • カットパスと聞いた瞬間に印刷業界以外の方は「んんん?」となりますよね

デザイナーさん以外にはこの「カットパスの理解と制作」が難しい。
これがシール作成における「できる/できない」を決定づける障壁です。


システムが解決したシール制作者の課題は「カットパスをつくる必要がない」という部分です。
これには制作が簡単になることに加え、制作作業の大幅な短縮効果があります。

このようにしてシールが簡単に作ることができるプロダクトができました。
ぜひお試しください!
https://raksul.com/online-design/sticker/

ラクスルではエンジニアを積極採用しています

ラクスルは画像処理や印刷技術、SVGに関わる開発ができるレアな環境です。
興味を持たれた方は是非一度オフィスに遊びにいらしてください!

WebpackerをやめるならWebpackManifestというgemが便利、という話

先日、社内のSlackでpixivさんのブログ記事 今日から簡単!Webpacker 完全脱出ガイド がシェアされてて、『あっ、これは…弊社でもやったやつではないか。』とおもいました。Webpackerは便利なんですけどね。

本記事はこのpixivさんのポストを受けて WebpackManifest というgemを紹介します。

ラクスルでのWebpackerを辞めた経緯

  • もともとWebpackerを使った管理画面プロジェクトがあった
  • そこにECサイトも乗せるようなった
  • package.jsonは管理画面、ECサイトで分けて管理したかった
  • Webpackerは1個のpackage.json、1個のwebpackコマンド、1個のmanifest.json前提の作りなので、package.json分けて複数のwebpackビルド処理系を作りたいラクスルの用途に合わなかった => 脱Webpacker

pixivさんのブログで紹介されてるとおりで、manifest.json を Rails に組み込むための view helper が必要になったので、弊社内でも lib/ 以下に小さなライブラリを作っていました。

gem化

そして、社内で他にもRailsアプリが立ち上がりだしてきて、『そろそろ、gem化せななあ』とおもっていたところにこのpixivさんの記事でしたので、本記事執筆にあたって WebpackManifest というgemにしました。rubygemsよりインストール可能です。このgemを使うとwebpackerを使わずに webpack の webpack-manifest-plugin が出力するmanifest.jsonに従ってview helperが asset のパスやscriptaタグをrenderingしてくれるようになります。

もともとの社内にあった view helperのメソッド名よりpixivさんのメソッド名のほうが適切でしたので、ヘルパー実装部分はpixivさんの実装を参考に組み込み直しました。

使い方

  • Webpacker gem のアンインストール
  • 代わりになる webpack.config.js の作成
  • 素のwebpackの webpack-manifest-plugin を用いて manifest.json を出力する
  • WebpackManifest gemをインストールし、↑のmanifest.jsonのパスをセットする(詳細はgemの README を参照する)
  • gem付属のview helperを使う

という流れになります。

不具合等ありましたらPRいただければとおもいます。

まとめ

WebpackManifest というgemを使ったWebpackerをやめる方法をご紹介しました。

ラクスルではエンジニアを絶賛募集してます

ご興味ありましたらどうぞオフィスへ遊びに来てください。

 

1週間やる、楽しい社内ハッカソンの作り方。

RakSul Hack Week Logo

はじめに

こんにちは。ラクスルプラットフォームチームのエンジニアの二串です。

先日、ラクスルではRaksul Hack Week #1という1週間通しでやる全員参加型の社内ハッカソンを開催しました。その運営を担当しまして、本記事では1週間やる楽しい社内ハッカソンの作り方をご紹介します。

開催の背景

もともとラクスルのエンジニア向けの制度としてHack-It Dayという月に1日、自由なテーマで開発することができる日がありました。もちろん活用はされていたものの、1日では満足いくものが作れなかったり、日々のリリースを優先してしまったり…といった課題も見えてきていました。そこで今回「Raksul Hack Week」に名称を改め、皆で1週間集中して面白いプロダクト・サービスを作ったり、新しい技術チャレンジをしたりする機会をつくろうと考えました。

第1回目となる今回のテーマは「HACK THE SYSTEM, HACK THE WORLD. (仕組みをハックすれば、世界はもっとカッコよくなる)」です。1週間通しでハッカソンをやる、というのは個人的に(そして他のエンジニア達も)初めての体験でありましたが、結果として自分たちの設定したテーマに全力を注ぐことができてよかったと思います。取り組んだ内容は記事後半の方で紹介します。

イベント概要

どんな感じのイベントだったのか?

  • 参加者はエンジニア、プロダクトマネージャー、デザイナーとする
  • ハッカソンの期間は1週間とし、期間中はハッカソン100%コミット、普段の開発業務はやらない(※ ただし緊急対応系は最優先で)
  • チーム制とする
  • 最終日に各チームは成果発表する
  • ラクスルの事業、ステークホルダーに関わることであれば何に取り組んでも良い

開催週の月曜日が祝日のため実際には前3日間開発、4日目成果発表会となりました。開催場所は企画段階で社外のコワーキングスペース等を借りるという案も出ましたが、初回ということで慣れたオフィス内開催としました。

準備

参加者の自発性を重視する形で、チーム構成は自由、また取り組む内容もチーム毎に自由に決定可能としました。また、イベント開催1ヶ月以上前から、各自で取り組みたいことを考えてもらってアイデアを膨らませてねーとお願いし、少しつづ Hack Week を盛り上げることにしました。

今回やったチームを決めるまでの流れは次の通りです。

  1. まず、社内(非参加者方面)からネタ(お困り毎など)を募集する
  2. 1を参加者に展開してアイデアの種にしてもらう
  3. 参加者は各自やりたいことをシェアする(専用confluenceページに書いてもらう)
  4. 出たやりたいことに賛同した人たちでチームを組む、もしくは3の起票者がメンバをリクルーティングしたりして組んでもらう
  5. チームを決めかねてる人たちはシャッフルで決定。取り組み内容は3を参考に決めてもらう
  6. Google Formでチームエントリ

全チーム構成が決まったのが開催の1週間前でした。この時点で大体のチームがやることもざっくり決めれている状態でした。

普段の業務に近いメンバーと組んだチームもありましたが、結構普段組まないメンツでの構成のチームもあり、結果的には何が出来上がるかな〜と期待が膨らんでくるチーム編成となりました。

ステッカー作りました(もちろんラクスルで作りました)

ハッカソン当日

ハッカソンが始まってしまえばあとは各チームもくもくと作業するだけです。社内の様々な場所で開発が行われました。

ちなみに、私は運営ではありましたがチームの一員としても参加しており期間中はがっつりと開発してイベントを楽しむこともできました。事前の準備が大事! 普段はサーバサイドの開発がメインですがHack Weekでは違う技術をということでTypeScriptNuxt.jsでフロントエンド中心のプロジェクトをやりました。難しかったですが勉強になりました!

ちなみに、1日の終わりに各チーム進捗報告をSlack #hack_week チャネルにしてもらうようにしました。進捗サマリとともに開発風景やスクショ等を共有してもらうことで、チーム間でもコミュニケーションが生まれたり、ビジネスメンバ等も様子を垣間見れたりして良かったとおもいます。

CTO泉率いるチームの開発風景。なんか楽しそうです。

 

ベトナムの開発拠点もリモート参加

取り組み内容紹介

一部ではありますがアウトプットを紹介します!

Fax2Web

アナログとデジタルとのブリッジになるための技術開発、ということでFAXとwebをつなぐチャレンジ。FAXを受信するとシステム連携されwebの注文ステータスが変更されたり、文字認識により自動入力されたりする仕組みのPoCを取り組みました。

メンテゲーム

サイトのメンテ中に遊べるゲームの実装にトライ。システム構成は Vue CLI 3 を使った静的サイト。

発表会・打ち上げの様子

発表会は1チームづつ順に発表。ベトナムの開発拠点とも接続して中継しました。

発表会の様子

 

1週間のハッカソンを振り返って話題が尽きない打ち上げとなりました

その他細かい運営のこと

参加必須のため参加者が50人弱ということでそれなりに入念に準備しました。まず、各チームの開発繁忙期と被らないようにスケジュール調整し日程を決めました。その後は運営コアメンバを招集してのキックオフ(これが開催3ヶ月前)、あとは週1回定例会を開きまして、少しづつ準備進めていきました。

なお、企画にあたっては以下の記事を参考にさせていただきました。有益な情報ありがとうございました。

まとめ

Raksul Hack Week #1 という社内ハッカソンを開催しました。

初めての長期社内ハッカソンということで、開催前には本当に盛り上がるのだろうか、普段1週間開発を止めてまでやるほどの価値が出るだろうか、といった不安も運営的にはありましたが、結果的には開催後のアンケートや、また経営陣へのイベント後の振り返り報告でも好評で良いイベントになりました。

普段の業務では要件通りに仕事をするということは大切ではありますが、今回のようにエンジニアやデザイナーの自発的でクリエイティブな発想で仕事をするというのも価値があることだなと感じました。また、使ったことのない技術スタックを試す機会としても良かったとおもっていて、実際今回使った新しい技術を業務に取り入れることにしたチームもありました。

今後もラクスルのテックカルチャーの1つとして開催していければいいなとおもいます。

社内ハッカソンイベント Raksul Hack Week #1 を開催中です

RakSul Hack Week Logo

エンジニアの二串です。

最近少しづつ朝晩が冷えるようになってきて秋の到来を感じますね。さて、秋といえばハッカソンですよね(!!?)

というわけで今週、ラクスルでは社内ハッカソンイベント「Raksul Hack Week #1」 を開催しています。

RakSul Hack Weekとは?

もともと月に1日、エンジニアは直接的な業務を離れ自由に開発をしていい Hack It Day という制度がありました。今回はそれを拡張し、1週間に渡って、全社的に(エンジニア、プロマネ、デザイナー混ぜて)やろうよ!というイベントです。さらりと書きましたが、1週間普段の開発を止めて、自由な発想でおもいっきりやってみる企画になります。今回が記念すべき(?)第1回目となります!!!

今回はやりたいことを事前にメンバーから集めて、出てきたアイデアをベースに数名1チームでチームを組んで、チーム=プロジェクトを作って取り組む方式を採用しました。

今週はシルバーウィークで月曜がお休みなので火曜から金曜まで計4日間の開催し、最終日にはチームごとの成果発表を開催します。成果発表会では、参加者の他、非参加者(ビジネスやカスタマーサポート)も交えての会となります。

楽しく作戦会議中の様子

各チームで作業が進んでます

今回は1週間ということで普段なかなか試せない技術スタックを試してるチームも多く、参加者達はそれぞれ盛り上がって来ているようです。

どんなことをやってるの?

例えば、

  • AWS lambda を使ってのFAXとウェブをつなげるサービスの開発
  • Nuxt.js + TypeScript で作るSlackと連携したウェブサービス
  • AIによる注文審査の自動化システム

などなど…. 技術領域も解決する課題も様々。今回は12チーム、総勢41名が参加しています。

もくもく作業中!?

さて、どんな成果物ができるでしょうか!!? 今から楽しみです。

また詳細はレポートします。

wifi打刻システムをつくった話

出社時/退社時の打刻ってとても面倒くさくないですか?

わかっていてもついつい打刻するのを忘れてしまいます。そして月末の勤怠締めで打刻忘れの箇所を手動で1つずついれていく。。とても非生産的ですね。

そこでシステム的にこの問題を解決するべく、メルカリの記事を参考にwifi打刻システムをつくりました。

wifi打刻システムとは社員さんが持っている端末(携帯やPC)がwifiに繋がった時に出社打刻をし、最後にwifiに繋がっていた時間を退社時刻として退社打刻を行うシステムです。

それではwifi打刻システムを作る方法について説明していきます。大きくわけて3つのステップに分けることができます。

  • 社員が持っている端末がwifiに繋がっていることを検知する
  • wifiに繋がった端末から社員を特定する
  • wifiに繋がった時に出社or退社の打刻をする

社員が持っている端末がwifiに繋がっていることを検知する

携帯やPCなどの端末にはMac Addressという端末毎に固有な物理アドレスが割り振られています。

wifiルーターに繋がっている端末のMac Addressを取得することでwifiに繋がっていることを検知しました。

Mac Address取得する方法はいくつかありますが、今回はsnmpwalkというコマンドを使いました。

ルーターと同じネットワーク内にあるサーバー上でsnmpwalkコマンドを打つと接続している端末の情報が得られます。

あとはその出力をparseすることでwifiに接続している機器のMac addressの一覧を取得しました。

wifiに繋がっている端末から社員を特定する

wifiに繋がっている端末のMac Addressの一覧が取得できたので、どのMac Addressがどの社員のものなのか特定する必要があります。

今回は単純にMac Addressと社員番号を紐づけるデータベースを用意しました。

どのMac Addressがどの社員のものなのかはwifi打刻の利用申請を出す際にMac Addressも教えてもらうようにしました。

iPhone/AndroidのMac Addressの調べ方は以下のサイトが参考になると思います。

これでMac Addressの一覧を社員番号の一覧に変換することができました。

wifiに繋がった時に出社or退社の打刻をする

社員番号の一覧が取得できるようになったので、後は出社or退社の判定をして打刻をすればよさそうです。

判定を行うために社員番号を取得するスクリプトをバッチで数分間隔で実行し、wifiに繋がっている社員番号一覧を取得した際に、その時の時刻をwifiに接続していた時刻として社員番号と共に各社員番号について記録しておきます。

{
  1: { # 社員番号:1の人がいつwifiに接続していたかを記録
    connected_at: "2018-09-05 14:31:52 +0900"
  },
  2: { # 社員番号:2の人がいつwifiに接続していたかを記録
    connected_at: "2018-09-05 14:31:52 +0900"
  },
}

出社についてはその日初めてwifiに繋がった場合に、その時刻を出社として打刻すればよさそうです。

退社については最後に接続した時刻と次にwifiに接続した時刻が一定時間以上離れていた場合に退社時刻として打刻するようにしました。こうすることでランチなどで外出してwifiが切れた時でも退社打刻されないようにしました。

ラクスルの勤怠システムにはAPIで打刻する機能があったので出社or退社と判断された時にAPIを使って打刻を行いました。また打刻されたかどうかわかるようにAPIで打刻した際にslackでDMを送って通知するようにしました。

これでwifiに繋がった時に出社/退社打刻してくれるwifi打刻システムが出来上がりました。

まとめ

打刻忘れや打刻修正などの無駄な作業をなくすべくwifi打刻システムをつくった話を紹介しました。

実際にwifi打刻を使っている社員さん達から「すごい便利」「快適になった」と言った声も頂いています。wifi打刻の仕組み自体はとても簡単なので、やろうと思えばすぐに導入できると思います。

打刻が面倒だと感じたら導入を検討してみてはいかがでしょうか。

ラクスルではエンジニアを積極採用しています

ラクスルに興味を持たれた方は是非一度オフィスに遊びにきてください!

宛名印刷ができる年賀状・喪中はがきをリリースしました

はじめに

ラクスルでエンジニアをしている藤田です。
先月、「年賀状・喪中はがき」という商品をリリースしました。
ラクスルで扱っている印刷サービスは基本的に、PDF等でアップロードされた印刷データを対象の商品(チラシや名刺など)に印刷してお客様へお届けするというものです。
今回リリースした商品は宛名印刷という機能があり、印刷データと一緒に宛名リストをアップロードしてもらい、印刷データと共にそれぞれの宛名をはがきに印刷します。他の商品では全く同じ印刷物が注文された部数だけ出来上がるのに対して、宛名印刷では一つ一つの印刷物が宛名の箇所だけ異なる内容になる点が特徴です。
一見するとシンプルに思われるこの機能ですが、実際に開発してみると意外と奥が深く面白かったので紹介したいと思います。

宛名印刷の難しさ

宛名印刷の難しさは「どうやって宛名を違和感なく宛名面にマッピングするか」にあります。組版処理とも呼ばれます。
下の画像を見ていただくと伝わるかと思うのですが、役職・部署の有無や文字数などで宛名面のどこに印刷するかが大きく変わります。
そのため、様々なパターンを考慮する必要があり必然的に開発も難しくなります。

今回はこのよう組版処理を実装するにあたって、開発スピード等を考慮して既成の組版ソフトを利用する方針としました。このソフトはWindows上でしか動作しないため、別途windowsサーバーをたてる必要がありました。

システム概要

少し簡略化していますが、組版処理に関連するシステムの概要図は上記のようになっており、次の通り処理されていきます。上述の組版ソフトは⑥で使用しており、SQSからキューを取得したりS3にアップロードしたりといった周辺の処理はWindowsとも相性が良いGolangで開発しました。

1. ユーザーが印刷データと宛名リスト(CSV)をアップロード
2. 印刷データと宛名リストをS3に保存
3. SQSに組版処理をエンキュー
4. SQSからキューを取得
5. S3から印刷データと宛名リストを取得
6. 組版処理を実施
7. 処理結果ファイルをS3に保存

Webアプリケーションと組版ソフトが動作するWindows Serverを繋げる方法として、Amazon SQSを利用しました。Windows Server上にAPIサーバーをたてる方法もありますが、やや開発コストが増える点と、宛名リストの件数によっては組版ソフトの処理に時間がかかるためキューイングが必要だったこともあり、SQSを採用しました。

マニアックな機能: CIDチェック

今回、開発した機能の中で面白かったものがCIDチェックです。CIDとはアドビ社が定めている文字ごとに一意に振られる番号のことで、フォントの文字を識別するために用いられます。例えば、フォントでは異体字の字形を区別する必要がありますが、Unicode等の一般的な文字コードでは同じコードが割り当てられていることがあるため、CIDが利用されます。
宛名印刷では、宛名に用いられるフォントとして正楷書CB1、リュウミンなど4種類から選択することができます。これらのフォントはカバーしているCIDの範囲以外の文字には使用することができないため、宛名リストで入力された文字が範囲内に含まれているかどうかをチェックすることが必要です。
CIDチェックの実装方法ですが、

1. 入力された文字に紐づくCIDを取得する
2. 取得したCIDが範囲内に含まれるかチェック

という順序でチェックをしていきます。
私が探した範囲ではRubyのライブラリ(CIDチェックはRailsアプリ内で行います)で文字列からCIDを取得するようなものはなかったため、UTF-8のコードとCIDをマッピングしたファイルを作成してUTF-8からCIDを引けるようにしました。
幸い、アドビ社が提供しているcid2code.txtというファイルにCIDと主な文字コードとの対応表がありましたので、このファイルを利用することでマッピングファイルを作成することができました。
細かい話になりますが、最終的に実装は下記のようになりました。

# app/validators/adobe_japan13_validator.rb

class AdobeJapan13Validator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.nil?
    chars = invalid_chars(value)
    record.errors.add(attribute, :invalid_character, invalid_chars: chars.uniq.join) if chars.present?
  end

  class << self
    def valid_strings_map
      return @valid_strings_map if @valid_strings_map
      @valid_strings_map = {}
      File.open(Rails.root.join('config', 'adobe_japan13.dat'), 'rb') do |f|
        f.each_line do |line|
          line.chomp!
          _cid, utf8 = line.split("\t")
          @valid_strings_map[utf8] = true
        end
      end
      @valid_strings_map
    end
  end

  private

  def invalid_chars(target_string)
    target_string.each_char.reject do |c|
      utf8 = c.unpack1('H*')
      self.class.valid_strings_map.key?(utf8)
    end
  end
end

バリデーターの中で読み込んでいる adobe_japan13.dat がマッピングファイルになっており、次のような内容になっています。左側の数字がCID、右側がUTF-8に対応しています。

# config/adobe_japan13.dat
1	20
2	21
3	22
4	23
5	24
6	25
7	26
8	27
9	28
10	29
11	2a
12	2b
13	2c
14	e28091
# 以下略

おわりに

この記事では宛名印刷という開発案件について紹介しました。
組版処理やCIDチェックといったものは印刷サービスならではで、他の会社ではあまり経験できないラクスルらしいテーマを紹介できたかなと思っています。

そうだ スピードチェック入稿のテストを自動化しよう

ラクスルのサーバサイドエンジニア 加藤です。

今回はスピードチェック入稿(テスト対象が印刷用ファイルの場合)の自動テストについて書いてみたいと思います。

再びスピードチェック入稿とは

チラシや名刺などの印刷をインターネットで注文したことのある方はいらっしゃいますか。

印刷したいものを選んで購入し、自分で用意した印刷データをアップロードして待つと、チラシや名刺などが届きます。通常の通販サイトと違うのは、自分で印刷したいデータを用意し、アップロードする作業です。さらに入稿データはラクスルでチェックし、印刷に適した形式に変換されます。

ラクスルでは、昨年8月末にそれまでオペレータが手動で行ってきたこの工程を自動化、スピードチェック入稿をリリースしました。それまで、1日以上かかってしまうこともあったデータチェックが、その場で完了。印刷物のお届けまでの日数が想定よりかかってしまうということもなくなり、多くのお客様に使われるようになりました。

リリースまでの過程は、スピードチェック入稿リリース秘話でご紹介させていただきました。

スピードチェック入稿のその後

さて、スピードチェック入稿は、その後もどんどん進化してきました。

1. 対応商品の拡大
最初は、チラシ・フライヤーと名刺だけだった対応商品に、ポスター/ポストカード/カード/チラシ折加工/折パンフレットが加わりました。

2. 入稿ファイル形式の追加
入稿ファイル形式も最初はPDFのみでしたが、ai/jpg/png/tiffでも入稿できるようになりました。また、ラクスルで提供しているWEBブラウザから利用できる無料のデザインソフト「オンラインデザイン」で作成していただいたデータも入稿することができます。

3. UXの改善
面裏の入稿原稿向き、折の開き向きなどを実際の印刷物により近い形でご確認していただくために、仕上がりプレビューが生まれました。実際に、ひっくり返したり、開いたり閉じたりして仕上がりを確認することができます。

折加工商品の仕上がりプレビュー

さて、様々な機能追加があると、大変なのがテストです。

ファイルの変換が入るために、テストを自動化しようにも、通常のWebテストのフレームワークだけでは対応できません。悩んでいるうちにも開発は進み、リリースのたびにプロジェクトメンバーがかなりの時間をかけて、データを手動でチェックしてテストをしていました。また、テストの実施もれが発生してしまうことがありました。これでは、チームとして自信を持って開発・リリースすることができません。

テストを自動化するぞ!

なんとかしなきゃとディスカッションしていたところ、「正常リリースで生成された印刷データと変換方法に変更を加えた後に生成した印刷データを、画像に変換して比較したらいいのではないか」というアイデアと、実際にImageMagicのcompareコマンドで検証結果がチーム内でシェアされました。

印刷データ画像

差分なし

差分あり

変換方法を変えて差分ができた場合は、差分箇所が赤く表示されます。これなら、一目で問題に気づけそうです!

このアイデアをとっかかりに、さらに印刷データのファイルに関してテストしたい項目を整理してみました。

1. ファイルの規格、各BOX(*)のサイズ、カラーが注文情報と比較して正しいこと
2. 問題のあるファイルに対して正しくエラーが返されていること
3. 変更前後の印刷データの画像差分がないこと、もしくは差分が妥当であると確認できること

* ドキュメントの用紙サイズ、印刷用の裁ち落としや仕上がりサイズの定義

そして、実際にテストを開発に組み込んでいくために、非エンジニアでも気軽にテストを実施したいと考えました。それぞれテストファイルが30個だとしても、この3項目に対してテストすると3倍の90項目。テストが大変だという理由で改善が進まないのは残念です。

なるべく早く問題に気づけるようにする

早速、設計に取りかかりました。

自動テストの場合は、社内ツールでユーザは自分も含め同じプロジェクトのメンバーです。要件はユーザ向けの機能と違って自由に決められますが、今回は開発がスムーズに進むようにするためなので「開発のなるべく早い段階で問題に気づけるような仕組みを作る」を指針にしました。

スピードチェック入稿のシステムは大きく3つのレイヤーに分かれています。

・フロント:データチェック用のAPIを呼び出し結果を描画
・データチェック用のAPI: フロントから受け取ったリクエストを受けてジョブをキューイング
・バックエンドジョブ:データチェックファイル変換を実施

シンプルにするために、今回の3項目のテストの対象はバックエンドジョブに絞りました。

システム構成

先ほどの指針にしたがって、1. ファイルの規格のテストに関しては、QA環境でスピードデータチェック入稿が使われると必ずジョブ内で自動で実行され、問題が発見されればslackに通知されるようにしました。

 

ファイルアップロード後の処理の流れ

上記の処理の流れの中で、サムネイルの生成が終わったらステータスは終了にして、フロントが描画できるような状態にしつつ、データチェックエンジンのチェック用のスクリプトが走るような作りです。

2. エラーのテスト3. 画像比較テスト に関しては、実行するとしばらくQA環境のジョブを占有してしまうので、実行タイミングをコントロールできるように、手動でslackから実行できるようにしました。

実行するとテストケースのYAMLから、上記の処理のファイル検証〜サムネイル生成を行うジョブをまとめて生成、結果チェックまたは画像比較が順次実行されます。画像比較テストの場合はテストケースが何十個もあるため、期待画像を一括に生成するコマンドも用意。テストケースの更新の手間も軽減しました。

テストの結果も、既存ツールに表示しようかと思いましたが、もう少し要件があったため、以下の画像のように、Datacheck APIの管理画面に表示しています。

画像比較テストの結果一覧(差分がない場合)

 

画像比較テストの結果一覧(差分がある場合)

これで1日〜2日かかっていたテスト作業が、30分以内で実施できるようになりました!
さらにミスもなくなり自信を持ってリリースすることができるように。ファイルの処理で処理フローに変更が必要になって試行錯誤することがあっても、すぐに確認できると開発効率は上がりますし、様々な改善のアイデアも湧いてきます。

自動テストの開発後、スピードチェック入稿当初からやりたかった複雑な処理変更や細かい改善を無事にリリースすることができました。

自動テストを使い続ける

自動テストは、テスト自体のメンテナンスも必要ですね。

今回は同じリポジトリにテストケースの定義を含めたため、通常のWebテストフレームワークと同じように機能修正のPull Requestに含めてテストの変更、追加を行なっています。

自動テストに関するREADMEも用意しメンバーにシェア。エンジニアだけでなく、データチェックのエンジンの設定メンバーも、テストケースの変更・追加を行なって、テストを最新の状態に保っています。

まとめ

スピードチェック入稿の印刷用ファイルを対象にテストを自動化しました。

「開発のなるべく早い段階で問題に気づけるような仕組みを作る」を指針に3つの項目のテストが短時間に実施可能に。これにより、すぐにミスに気づけるようになり、開発効率も向上しました。

テストの自動化は後回しになってきましたが、スピードチェック入稿が進化していく上で重要な施策だったと思っていますし、早速その効果を実感することができました。

ラクスルでは、絶賛エンジニア募集中です。

「仕組みを変えれば世界はもっとよくなる」

ユーザ価値をさらに向上させつつ、より社内外のサービスと繋がっていくプラットホームを開発していきたいエンジニアを募集しています。是非一度オフィスに遊びにきてください!

運用中のRailsプロジェクトをなめらかに複数DB化した話

エンジニアの二串です。

ラクスルのRailsのプロジェクトではデータベースとして Amazon Aurora(MySQL) を採用しています。そしてO/Rマッパーとして当然ActiveRecordを使っています。

さて、ActiveRecordを使っていて悩むのが複数DBの接続ですがみなさんはどうしていますか? ActiveRecordは標準では1つのデータベースにしか接続できないので、複数の異なるデータベースサーバや、マスター・リードレプリカの接続を切り替えるには工夫が必要です。しかし、世の中には素晴らしい方々がいて複数DB切り替えを可能にするgemが提供されています。

今回はそれらgemの中からSwitchPointを採用し、運用中のRailsプロジェクトになめらかに導入したときの経緯と方針、さらに具体設定を紹介します。

確認したバージョンは以下のとおりです

  • Rails 5.1.5
  • Ruby 2.4.2
  • switch_point 0.8.0
  • arproxy 0.2.3

最初からまとめ

記事では以下のことを書いています。

  • 運用中のRailsプロジェクトにSwitchPointを導入し複数DB化しました
  • マスター接続をデフォルトにし、オプショナルでリードレプリカへ接続するようにしました
  • 複数DB化した後も、いままでの素のRails(シングルDB運用)での開発のスピード感を損なわないよう、工夫をしました

導入前の事情

複数DB化の背景を少しだけお伝えしますと、今回のRailsプロジェクトは元々はphpで書かれた管理画面の刷新目的でスタートしたプロジェクトでした(詳しい事情は過去の記事でも触れています)。しばらくの月日の後、プロジェクトがいい感じに育ちまして表側の機能も提供するまでに進化しました。現在では、そうだ、ラクスルを作り直そう!でも触れられているRaksul Platform Projectの1コンポーネントを担っています。

今回の対応はその表側の機能も提供することになったフェーズでの話で、状況は以下のとおりでした。

  • これまで管理画面のみを提供していたので、マスターのみの接続で割り切ってた
  • 今後、raksul.comのパブリックな機能も提供することになった
  • read mostly, write sometimesなサイト特性上、参照系はリードレプリカへ逃したほうがベターだが、マスターに向けてしまっても当面は捌ける規模感
  • とはいえ、マスターの負荷を増加させないようリードレプリカへの接続も用意しておきたい

このような経緯で複数DB化の対応を入れることにしました。

複数DB化によるマイナス面の考慮と導入方針

これまでの経験上、複数DB化することにより以後の開発スピードが落ちたり、運用工数が増えるケースがあることを知っていました。具体的には、

  • 実装工数増加(この save! はマスターに接続して、次はSELECTするからリードレプリカに接続して…と考えたりする工数)
  • コードの見通しが悪くなる(SwitchPointの場合 with_readonly { with_writable { ... } } のようにブロックで囲ってネストするので)
  • テストの実装工数(同上 + FactoryBotで作成する際の考慮等)
  • レプリ遅延の考慮が発生する (INSERT直後にリードレプリカ側でSELECTして空振ったら、マスターにフォールバックして参照する、レプリ遅延を考慮したCI環境にするかどうか、etc..)

等々。

これらのデメリットを受け入れるというのも一つのやり方ですが、そうはしたくありませんでした。なるべく開発スピードを素のRailsでの感覚のまま、これまでのシングルDBオンリーでの開発のスピード感のままに、複数DB化できないだろうか、という点をとても大事にしました。

また、改修にあたっては、既に管理画面向け機能がたくさんあるのでコードの修正範囲は最小にしようとおもいました。

そこで、改修にあたって以下の方針を立てました。

  • gemはSwitchPointを使う
  • マスターDBデフォルト、リードレプリカ参照をオプショナル
  • コントローラのアクション単位で参照を切り替る
  • レプリ遅延を考慮したコードは書かないようにする
  • RAILS_ENV=testではシングルDBで
  • マスター・リードレプリカ接続状況をログに記録する

以下でそれぞれ詳細説明します。

SwitchPoint

SwitchPoint 1択でした。理由は、私の前職のプロジェクトで使っていて問題なく動いているのを知っていたのと、そのときに内部実装を大体把握していたからでした。よって他のgemは検討しませんでした。

マスターDBデフォルト、リードレプリカ参照オプショナル

SwitchPointのREADMEを読み進めると、モデルクラスはマスター、リードレプリカどちらに接続するかを with_readonly {} または with_writable {} ブロックでどちらも指定した例に見られます。この方針で進めると、我々の場合既存の管理画面のコントローラをすべて修正しなければならなくなってしまいます。コードの改修量は最小にしたかったのでこれは避けたい。

そこで、まず、with_readonly {}with_writable {} も指定せずにActiveRecordでクエリを実行した場合はデフォルトでマスターに接続されるようします。そして重いクエリが実行されるページやアクセスが多いページでは都度 .with_readonly {} を使ってリードレプリカへ接続させるようにしました。

# こういう処理が既にあって...
Order.find(params[:id]).update!(order_params)

# 素で SwitchPoint を導入すると以下のような書き換えが必要だが...
order = nil
order = Order.with_readonly { Order.find(params[:id] }
Order.with_writable { order.update!(order_params)

# そうではなくマスター接続をデフォルトにすれば、既存コードを書き換えずに済む
Order.find(params[:id]).update!(order_params)

これを実現するにはいくつか設定が必要です。READMEにはあまり書かれていなかったので内部実装も読んでます。

database.yml

# writable 系は Rails 標準のネーミング development, production, etc とする.
# これは https://github.com/eagletmt/switch_point/issues/16 の通りで、標準のネーミングの設定が無いと
# Rails が起動しないケースに遭遇したため.
development:
  # 接続情報(略)
  database: raksul_development
development_raksul_readonly:
  # 接続情報(略)
  database: raksul_development

test:
  # 接続情報(略)
  database: raksul_test
test_raksul_readonly:
  # 接続情報(略)
  database: raksul_test

production:
  # 接続情報(略)
production_raksul_readonly:
  # 接続情報(略)
  • マスターの接続情報はRails標準のdevelopment,test,productionに定義する
  • リードレプリカの接続情報は#{Rails.env}_raksul_readonly に定義する

switch_pointの設定

SwitchPointでマスターDBへの接続をデフォルトにするには SwitchPoint.writable!(接続シンボル名)をアプリケーション初期化フェースでcallします。

# config/initializers/switch_point.rb

SwitchPoint.configure do |config|
  raksul_config = {
    readonly: :"#{Rails.env}_raksul_readonly",
    writable: :"#{Rails.env}"
  }
  if Rails.env.test?
    # 通常SwitchPointは2本コネクションを作る(readとwrite).
    # しかし、database cleanerでtransaction strategyを使っているとテストがfailする.
    # なぜなら、transactionのbeginはwriteコネクションでcallされ、
    # SELECT は read コネクションで呼ばれるが、
    # FactoryBotで作られたレコードはcommitされるまでSELECTしても参照できないのでテストが成立しない.
    #
    # この問題を解決する2つの方法:
    #   1. RAILS_ENV=testでのみSwitchPointのコネクションを1本にする
    #   2. transaction以外のstragegy(truncation or deletion)に変更する
    #
    # 我々は1を選択した。2の方法ではテストが遅くなったため。
    #
    # 1をやるには、readonlyのコネクション設定を消すことで実現できる.
    # こうするとSwitchPointはwritableコネクションにフォールバックする挙動
    # ref https://github.com/eagletmt/switch_point/blob/v0.8.0/lib/switch_point/proxy.rb#L131
    raksul_config.delete(:readonly)
  end
  config.define_switch_point :raksul, raksul_config
end
# Change @global_model of the proxy for raksul :writable
# ブロック指定なしの場合の挙動を writable にする
SwitchPoint.writable!(:raksul)


# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
 self.abstract_class = true

 # モデルのSwitchPoint組み込み
 use_switch_point :raksul
end

なお、一点database_cleanerでtransaction strategyを使っていたので上のコメントの通りコネクションを1本にする対策を入れました。

これで既存のコードは修正せずに済みます。

ではリードレプリカを参照したい場合には with_readonly { } のブロックを書かなければならないのか? これも次の方法で楽をします。

コントローラのアクション単位でリードレプリカに切り替える

開発ポリシとして、コントローラのアクション単位でリードレプリカへ接続を向けるようにします。

例えば、OrdersControllerの #index, #show, #new, #edit ではDBへの書き込みはなく参照しか発生しない、というケースは良くあることかとおもいます。なので、around_action filter を使って with_readonly {} をcallしてあげます。そうすることで、リードレプリカ参照においても with_readonly {} のブロックを意識することなく filter を設定するだけで済みます。ブロックがないのでコードの見通しが損なわれません。

class ApplicationController < ActionController::Base
  private

  def with_readonly
    ApplicationRecord.with_readonly { yield }
  end
end

class OrdersController < ApplicationController
  around_action :with_readonly, only: %i[index]

  def index
    # アクション内で .with_readonly { .. } を意識しなくて済む
    @orders = Order.all.order(:id)
  end
end

中には参照系のペーシだけど別途行動ログを書き込みたいといった場合もあるかもしれませんが、そういうケースではそのそのアクションではリードレプリカへ接続させずマスターへ接続させる、で割り切ります。

レプリ遅延は考慮しないコードを書く

1つのアクション内でいくつかレコードをSELECTしINSERT or UPDATEする時、理想的には参照はリードレプリカへ、書き込みはマスターへ、と細かく切り替えるのがDB負荷を考えると理想的です。ただ、レプリ遅延により書き込み直後のリードレプリカ側のSELECTが空振って想定してなかったバグが発生したり、またその対策としてマスターへフォールバックさせるコードを書いたり…という経験がある方もいるとおもいます。このような状況では開発工数も運用工数も増えます。

そう、この考慮したくない、そうおもいました。ですので思い切って割り切ることにしました。つまり、書き込みが発生するアクション内ではずっとマスターと接続させておく。こうすることでレプリ遅延しても同じアクション内でレプリ遅延は発生しないので考慮する必要はなくなります。(リダイレクト先のアクションでレプリ遅延がある可能性はありますが)

トラフィック特性上、更新系のアクションをマスターに向けても即座に詰まることはないので、これで良いと思っています。もし、トラフィックが激増して厳密に切り替えないと回らない…という状況になったら対策するとして、そうなったときはつまりサービスが拡大しているということなのでとても嬉しい状況ですね。

RAILS_ENV=testではシングルDBで

上述の通り、レプリ遅延を極力考慮しない割り切りなので、テスト実行においてレプリ遅延が発生するケースをあぶり出すような考慮はしません。ですのでCI環境やローカル環境ではシングルDBなのでレプリケーション設定もしてません。

マスター・リードレプリカとの接続状況をログに記録する

開発時に便利なので、ActiveRecordのクエリログに readonly or writable どちらに接続したかを記録するようにしました。ログの拡張には cookpad/arproxy を使いました。とても便利で感謝しかないです。

設定

# config/initializers/arproxy.rb

if Rails.env.development? || Rails.env.test?
  require 'switch_point_logger_enhancement'

  Arproxy.configure do |config|
    config.adapter = 'mysql2'
    config.use SwitchPointLoggerEnhancement
  end
  Arproxy.enable!
end

# lib/switch_point_logger_enhancement.rb

class SwitchPointLoggerEnhancement < Arproxy::Base
  def execute(sql, name = nil)
    proxy = SwitchPoint::ProxyRepository.checkout(:raksul)
    mode = proxy.mode
    name = "#{name} [#{mode}]"
    super(sql, name)
  end
end

 

ログサンプル

SCHEMA [readonly] (0.8ms)  SHOW FULL FIELDS FROM `tickets`
Ticket Load [readonly] (0.3ms)  SELECT `tickets`.* FROM `tickets` WHERE `tickets`.`staff_id` IN (100, 201, 12, 13, 71, 10)
Staff Load [writable] (0.3ms)  SELECT  `staffs`.* FROM `staffs` WHERE `staffs`.`id` = 2 ORDER BY `staffs`.`id` ASC LIMIT 1
Role Load [writable] (0.4ms)  SELECT `roles`.* FROM `roles` INNER JOIN `abilities` ON `roles`.`id` = `abilities`.`role_id` WHERE `abilities`.`staff_id` = 2

[readonly] [writable] の箇所です。

まとめ

運用中のRailsプロジェクトにSwitchPointを導入し複数DB化しました。

マスター接続デフォルト、リードレプリカ接続をオプショナルとすることで、既存コードの改修を最小に押さえて導入の敷居を下げつつ、リードレプリカを参照させるときはコントローラのアクション単位で制御するようにしました。

また、レプリ遅延の考慮は極力意識しなくて済むよう、マスター、リードレプリカの接続を混ぜるアクションを実装しない割り切りをしました。

これらにより、複数DB化した後も、素のRails(シングルDB運用)と同じレベルの開発スピード感を維持することができました。

実際、本番導入からしばらく立ちますが問題なく運用できており、また開発もこれまでどおりのスピード感で進めれています。

ラクスルではエンジニアを積極採用しています

いろいろ詳しく書きましたが、複数DB化にあたっての検討は私一人で行ったわけではなく、自社のサービス傾向や開発運用スタイル似合うかどうかを、周りにいるRailsに詳しいエンジニアにも相談しながら、進めていきました。1人で決めるより話し合って決めるラクスルのスタイルはとてもやりやすく感じています。

そういうわけでして、ラクスルでは私達と開発指針を議論しながら開発したいエンジニアを絶賛募集しています。是非一度オフィスに遊びにきてください!