シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

RubyKaigi 2018 に参加してきました (@fohte)

基盤チームの川井 (@fohte) です。

2018 年 5 月 31 日 (木) 〜 6 月 2 日 (土) に仙台で開催された RubyKaigi 2018 に業務として参加してきました。
弊社はスポンサー枠ではありませんが、出張として宿泊費や交通費を負担していただいて参加しました。

今回は、印象に残ったセッションと、個人的に興味があった型にまつわるセッションについてご紹介します。

セッション紹介

Day 1 Keynote「箴言 Proverbs」

Matz ことまつもとゆきひろさんのセッションです。 今回は「ことわざについて話す」ということで、プログラミングをことわざで例えた話が中心でした。

初めに紹介されていた「名は体を表す」は、つまり命名は重要であるということです。
物事を十分に理解すれば命名が容易になることがある、ということを話されていて、なるほどと感心しました。

また、Ruby 2.5 で導入された Kernel#yield_self は何がしたいのかを表現しておらず、悪い例であると話されていました。
(これは考え直しましょうというチケット #14594 があり、alias として then を追加する変更が Matz によって発表前日に commit されたそうです。5 年ぶりの commit らしい。)

質疑応答の際に Ruby の型に関する質問があり、型があることで助かるケースはあるが、Ruby の言語仕様として型宣言が入ることはないと名言されておりました。

A practical type system for Ruby at Stripe.

Stripe 社内部で用いられている Sorbet という Ruby の type checker についてのセッションでした。

Sorbet は記事執筆時点では公開されていませんが、Web 上で実際に試せるページが用意されています。

Sorbet は C++ 製なため非常に高速で、社内では好評らしいです。 また、近々 OSS として公開する予定であり、公開時には 公式ブログ で通知するとのことでした。

Ruby Programming with Type Checking

発表者の @soutaro さんが製作されている Steep という Ruby の type checker についてのセッションでした。

Steep は Sorbet とは異なるアプローチをとっており、Sorbet は Ruby コード上で型を宣言しますが、Steep は専用の構文を用いた型宣言ファイルを用意するかコメントで型注釈をします。
また Steep は Ruby で実装されているため Sorbet よりも低速ですが、Ruby 2.6 で導入される予定の MJIT で高速化することを期待していると話されていました。

既存のプロジェクトに導入する際などに便利な型定義ファイルの雛形を生成する steep scaffold コマンドも用意されています。

しかし、ライブラリの型宣言はまだ自分で書いていく必要があったり、Hash の構造の型宣言ができなかったり、動的に定義されるメソッドは型チェックを行えないなどの問題があるとのことでした。

Type Profiler: An analysis to guess type signatures

Ruby commiter の一人である @mametter さんによる Ruby 3 の型システムについてのセッションでした。

既存の型システムとしては Steep, RDL, contracts.ruby, dry-types, RubyTypeInference, Sorbet などがありますが、代表として RDL という半静的 type checker について紹介されていました。

RDL は型検証を行うタイミングを指定でき、静的型チェックを無視して動的型チェックのみを行うようにすることもできます。
また、メタプログラミングもサポートしており、動的に生えるメソッドの型宣言もできます。

Steep のように型宣言がファイルで分かれているほうが良いが、メタプログラミングは難しいということで、RubyTypeInference のように型情報を推定する Type Profiler という構想が生まれました。
この推定された型情報は厳密なものではなく、推定結果を人間がチェックすることを想定した設計になっています。

型情報をプロファイリングするには、void 型の推定が困難なことや、TracePoint API を用いて動的に解析しようとすると非常に遅くなるという問題があると話されていました。

感想

RubyKaigi は今回が初参加でしたが、レポート記事や実況ツイートではわからない会場の雰囲気を味わえて非常に良かったです。
特に、今回は型システムにまつわる発表が多く、個人的に今非常に興味があるので、Ruby 3 に向けた型システムの構想を聞いたり懇親会などの場で参加者と話をできたりして面白かったです。

ぜひ来年も参加したいと思っています。


シンクロ・フードでは Ruby に詳しいエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

EC2上のMySQLからAuroraへの移行作業で失敗・想定外だったこと

こんにちは、pubgで全然ドン勝できない、開発部の大久保です。
今回は弊社が調査含め半年ほどかけて実施した、Aurora移行のお話しをしようかと思います。
ただ、Auroraへ移行するための情報自体は世の中に溢れていると思うので、今回は移行する際に失敗したことや想定外だったことをご紹介しようと思います。 具体的な失敗や想定外をお伝えすることで、移行を考えている方のお役に立てれば幸いです。

移行の概要

とはいえ、さすがに大まかにでもどのように移行をしたかをご紹介しないと、失敗も共有できないので、簡単に共有します。
今回弊社が行った移行は、EC2上にホストしているMySQLをAuroraに移行する、というものです。RDSからAuroraに移行する、というものではありません。

では、ものすごく簡素化した移行の図で、移行の全体像をお伝えします。

移行前

f:id:synchro-food:20180513205218p:plain

なにも行っていない状態はこんな感じでした。
バックアップ用のEC2インスタンスはバックアップや開発用のデータの元となるdumpデータを取るサーバです。当時はOregonリージョンに立てていました。これは別リージョンにもリアルタイムのバックアップをとっておく目的で存在しています。
このサーバ、今回の想定外の1つなので、敢えてご説明しています。

移行準備

f:id:synchro-food:20180513204804p:plain

移行予定のAuroraインスタンスを立て、そこをSlaveとしてレプリケーションをすることでデータを移行します。

移行後

f:id:synchro-food:20180513204844p:plain

移行当日に、AuroraをMasterに昇格させ、アプリケーション側のDBの向き先を一気にAuroraに変更しました。 その他、バックアップ用のサーバが、クロスリージョンリードレプリカになっています(バックアップはAuroraのSnapShotだけでなく、sqlのdumpデータも欲しいので…)。

実際はもう少し複雑な構成なのですが、シンプルにするとこのような流れです。
移行手順としてはオーソドックスだと思います。

失敗・想定外だったこと

では、上記手順でAurora移行をした際に発生した問題をご紹介します。大きくは3つでした。

Aurora→EC2上のMySQLへのレプリケーションはSSLにできない

上記図でいうバックアップサーバは、別リージョンのEC2インスタンス上にMySQLを立て、リアルタイムのバックアップを別リージョンに取るために使っていたのですが、料金を考えると、Aurora移行後も信頼性の低いEC2インスタンス上のMySQLでも十分でした。ですので、当初の予定では移行後も安いEC2インスタンスを使う予定でした。
しかし、調査しているとAurora→外側のMySQLサーバという向きではレプリケーションがSSL通信できないことがわかりました。 別リージョンとなるとさすがにSSL通信をしたかったため、高額ですがクロスリージョンリードレプリカを使うことになってしまいました。今はこのサーバが別リージョンのリアルタイムバックアップと、定期的なdumpデータバックアップを取得する役割を担っています。

これはそもそも別リージョンにリアルタイムバックアップを取る必要があるのか…という話なのですが…。

レプリケーション間のTimezoneがズレていたことによるデータズレ

移行前のMySQLサーバと、移行予定のAuroraサーバのTimezoneがズレていることにより、移行当日のデータ差分チェックで実際に差分が発生してしまい、移行作業をロールバックしています。
具体的には、移行前のMySQLはSYSTEM、というタイムゾーン設定で、これはOSのタイムゾーンを用います。移行前のOSのタイムゾーンはJSTでした。 一方、AuroraのOSのタイムゾーンはUTCですので、そのままSYSTEM設定でレプリケーションをすると、UTCとしてレプリケーション時のクエリが流れてしまいます。 この現象により、データのズレが発生しました。

移行前のMySQLサーバのタイムゾーンを変更することで問題は解決しました。

フェイルオーバー時に旧コネクションを維持してしまう問題

Auroraは障害時、迅速にフェイルオーバーしてくれるのですが、フェイルオーバーをテストしていると、コネクション周りで1つ問題が発生しました。 Auroraのフェイルオーバーは、Master側に障害が発生すると、SlaveのリードレプリカがMasterとして昇格するのですが、障害が発生したMaster側が、復旧後Slaveとして戻ってきます。 つまり、MasterとSlaveが逆転します。 この動きと、Webアプリケーションで行うコネクションプーリングとの相性があまり良くないのです。 具体的には、Webアプリケーションが持つMaster用のコネクションがフェイルオーバー時に破棄されずに保持されており、そのコネクションがSlaveとして復旧したAuroraインスタンスに接続しようとしてしまい、更新時にエラーが起きる、というものです。 毎回コネクションを作成すれば回避できる問題ですが、できればコネクションは使いまわしたいので、以下の解決を取りました。

Tomcatの場合

参考にした記事は以下の通り。

qiita.com

こちらは、context.xmlで設定するValidationクエリで、コネクションの状態がReadOnlyかどうかをチェックするストアドを作成し、それを使う、というものです。 Tomcatはこちらの方法で対応しました。

Railsの場合

Railsでは、基本的にconnectionは永続的に使うようになっているのですが、それを定期的に破棄する設定を加えました。 参考にした記事は以下の通り。

blog.livedoor.jp

github.com

activerecord-refresh_connectionを使うと、コネクションが一定数利用されると破棄されるようになります。 この対応はフェイルオーバーした直後にコネクションの向き先が変わることはないのですが、一定回数使われれば新しく接続されるため、一定時間が経過すれば正しいconnectionが得らるようになります。

クロスリージョンリードレプリカを作成するためのbinlogを有効にすると性能が落ちる

具体的には、binlog_formatMIXEDにしているのですが、これにより性能が劣化することがわかりました。ただし、弊社ではクロスリージョンリードレプリカの作成は必須だったので、受容しました。
詳細な指標は、以下のqiita記事に記載されていますが、かなり劣化しています。

qiita.com

その他、あまり語られることが少ないが、やらないといけなかったもの

  • エラーログ・スロークエリログ
     定期的にS3にログを吐き出す対応を、Lambdaで行っています。

まとめ

以上、弊社が遭遇したAurora移行時の失敗・想定外の出来事です。Aurora移行前は、簡単に移行できるでしょ…と思っていましたが、細かい問題が色々ありました。大抵のことはそうですよね…。
尚、移行して数ヶ月経過しましたが、特に問題は発生していません(気づいていないだけ?)。 これから移行を検討されている方、是非参考にしてみてください!

シンクロ・フードではエンジニアを募集しています。こんなインフラサイドの記事を書いていてアレなのですが、最近ではRailsをバリバリ書ける方を募集中です…。是非ご応募ください!

www.synchro-food.co.jp

AWS 認定ソリューションアーキテクト アソシエイト(新版)を取得しました

基盤チームの安藤です。
今回はAWS 認定ソリューションアーキテクト アソシエイトを取得した話を紹介しようと思います。
インフラ歴は3年ほどで最近までは開発と半々でしたが、積極的にインフラに関わっていくことになったためこの資格に挑戦しました。

AWS 認定ソリューションアーキテクト アソシエイト(新版)とは?

今回受験した新版は2018年2月から受験可能になった新しいバージョンで、内容が刷新されただけでなく問題数や試験時間なども大きく変更されています。
取得した当時は試験は英語でのみ受験可能でしたが、現在は日本語での受験も可能になっているようです。
詳しくは公式ページをご参照ください。
https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/

勉強方法について

新版については情報が少なかったり、対応した教材がまだ少なかったため情報収集に苦労しました。
また、今回は英語での受験ということもあり主に英語の教材を選びました。
準備期間は2週間ほどでした。

Udemy - AWS Certified Solutions Architect Associate 2018 コース

https://www.udemy.com/aws-certified-solutions-architect-associate/
確認用のミニクイズや模擬試験が付いたUdemyのビデオ講座です。
内容はソリューションアーキテクト アソシエイトの試験に特化してかなりまとまった内容でした。
全てを通すとかなり長時間になるため、業務で利用したことがあるサービスは倍速で流し見して、触ったことがないサービスはなるべく手を動かすという形で取り組みました。
一部内容が古く現在では正しくない内容もありましたが、Q&Aで講師や他のユーザーからフォローを受けることができました。

Q&Aには実際に試験を受験した人たちのアドバイスやフィードバックも投稿されていて非常に役立ちました。
新版の情報が少ないなか、どういった範囲の問題がよく出題されるかなど傾向がつかめます。

A Cloud GuruのAWS Certified Solutions Architect - Associateというコースと内容は同じようですが、セール価格で購入できればかなりお得だと思います。
アカウントを連携することでA Cloud Guruのコースに付属している模擬試験も受験できました。

Wizlabs Practice Tests

https://www.whizlabs.com/aws-solutions-architect-associate/practice-tests/
UdemyのコースのQ&Aでよくおすすめされていた練習問題集です。
旧版、新版それぞれに対応した問題が用意されていて、問題数も豊富で解説が丁寧でした。

公式の模擬試験

有料ですが、本番の試験と同様の形式で受験できます。
形式に慣れる意味でもおすすめです。
試験結果は全体の正答率と5つのセクションごとの正答率しかわからず、問題ごとの正否まではわかりませんでした。
また、試験問題を振り返って確認することもできないため要注意です。

試験の流れ

申し込み

AWS training and certificationにログイン後、PSIから試験の予約を行います。
予約の際には受験料16,200円も支払います。今回は会社に全額負担してもらいました。
試験日については、都内であれば平日はほぼ毎日受験可能ですが、休日に受験できる会場はごく一部に限られているようです。

試験当日

会場で受付後、身分証明書(免許証とクレジットカードなど2枚以上の組み合わせ)と紙、ペンだけを持ってブースに入ります。
ブースには身分証明書のスキャナー、Webカメラが設置されたPCが用意されていました。
画面上のガイドと日本語のチャットの指示に従って身分証明書をスキャン後、試験が開始します。
試験中の質問などはチャットを通じて行えます。
試験完了後にはアンケートがあり、最後に試験の合否が画面に表示されて終了になります。

試験終了後

試験後数日以内にAWS training and certificationでスコア等が確認できるようになります。
試験のスコアは100から1000で評価され、合格ラインは720です。
また、セクションごとにMeets Competencies/Needs Improvementの判定があります。

試験の結果

899/1000というスコアで無事合格できました。
セクションごとで見ても全セクションでMeets Competenciesとなり偏りなくスコアを取れました。

感想

  • VPC,サブネット周りの基礎を改めて学べた
  • サービスの詳細な仕様を学べた
    • S3のデータ整合性モデルなど
  • 今まで見落としていた機能、設定を知ることができた
  • 様々なパターンのベストプラクティスを学べた
  • AWSの更新情報に敏感になった

など色々と得るものが多く、とても有用なものになりました。

その他にも

  • AWSサミットで認定者ラウンジに入場できる
  • AWS認定ストアで認定グッズが購入できる

といったおまけもあるようなので、機会があれば試してみようと思います。

最後に

以上、認定ソリューションアーキテクト アソシエイト取得までの紹介でした。
新版についてはまだまだ情報も少ないので、これから挑戦する方の参考になればと思います。


シンクロ・フードではエンジニアを募集しています。 資格の取得なども柔軟に補助がでますので、ご興味のある方は以下よりご連絡ください!

依存しあう複数の Git リポジトリを git subtree で monorepo 化する

基盤チームの川井 (@fohte) です。

今回は、monorepo と呼ばれる複数のリポジトリを単一のリポジトリで管理する運用方法の紹介と、実際にその運用に切り替えた話をします。

弊社は GitHub Enterprise を使っており、Git Flow を独自に拡張した master, develop, feature ブランチの 3 種類からなる運用フローを採用しています。 今回はその環境を前提としてお話します。

monorepo とは?

monorepo は、複数リポジトリを単一のリポジトリで管理するという Git リポジトリの運用方法の一つです。

monorepo のメリットとしては以下の点が挙げられます。

  • 密結合なリポジトリの運用が楽になる
  • 複数リポジトリに横断した修正が簡単にできるようになる

例えば、あるリポジトリ A の変更に従って別のリポジトリ B も変更するとき、リポジトリが複数分かれているとそれぞれのリポジトリに commit が分散することになります。 これは単純に面倒ですし、何より commit 可読性も悪くなります。

また、このようなときはリポジトリごとに Pull Request を立てる必要がありますが、monorepo では 1 つにまとまるため、Pull Request の可読性も向上します。

反対に monorepo のデメリットとしては以下の点が挙げられます。

  • リポジトリが巨大になる

複数に分散しているリポジトリを 1 つにまとめてしまうので、結果として Git リポジトリは統合したリポジトリの分だけ大きくなります。

あまり見慣れない運用方法ではありますが、RailsBabelReact といった巨大な OSS プロジェクトでも採用されています。

monorepo へシームレスに移行する

monorepo に移行するとなると、各リポジトリのブランチも monorepo 側に移さなければいけません。 そこで、開発者の手を止めることなく monorepo に移行する手段として、以下の方法を検討しました。

  • 各リポジトリの feature ブランチを含めたブランチ全てを monorepo に移し、移行直後から monorepo 側の feature ブランチで開発する f:id:synchro-food:20180329222655p:plain
  • master ブランチだけを monorepo に移し、各リポジトリで開発中の feature ブランチは merge 後に移行する f:id:synchro-food:20180329222652p:plain

前者の場合、各開発者の手元の変更は monorepo 側に取り込まれないという欠点があるため、今回は後者の手段を選択しました。 また、弊社では master ブランチとは別に develop ブランチもありますが、develop ブランチも後述の方法で移行すると master と develop が全行差分になってしまう問題があったため、master ブランチ移行後に新規に master から develop ブランチを切り直しました。

移行手順

今回は git subtree コマンドを用いて移行しています。

git subtree 自体は git に内蔵されているコマンドではないため、環境によってはコマンドが無く、その場合は手動でインストールする必要があります。

git clone https://github.com/git/git.git
cd git/contrib/subtree
make
cp git-subtree /usr/local/bin # パスが通っているディレクトリにコピーする

各リポジトリの master ブランチを monorepo に移行する

先に monorepo 化する各リポジトリの remote を登録し、fetch しておきます。

git remote add <name> <repo_url>
git fetch -a <name>
  • <name>: remote の名前です。今回は monorepo のサブディレクトリ名とします。
  • <repo_url>: 各リポジトリの URL です。 (例: git@github.com:rails/rails.git, https://github.com/rails/rails.git)

また、空 commit を作成しておきます。

git checkout --orphan master
git commit --allow-empty -m 'Root commit for master branch'

次に、git subtree add コマンドで各リポジトリを monorepo のサブディレクトリとして追加します。

git subtree add --prefix=apps/<name> <name>/master

--prefix に指定したディレクトリにリポジトリが取り込まれます。 今回は apps ディレクトリ下にフラットにリポジトリを置いています。

最後に、念のため git diff-tree で差分をチェックし、問題がなければ master ブランチの移行は完了です。

git diff-tree -r --stat <name>/master master:apps/<name>

feature ブランチの変更内容を monorepo に取り込む

今回は、各リポジトリで開発中の feature ブランチはそのリポジトリに merge してしまい、その後 monorepo 側に取り込みます。

その後、git subtree merge コマンドでサブディレクトリに merge し、monorepo 側に各リポジトリでの変更を取り込みます。

git checkout master
git subtree merge --prefix=apps/<name> <name>/<branch>

master ブランチ移行時同様に差分をチェックし、問題がなければ feature ブランチの変更の取り込みは完了です。

git diff-tree -r --stat <name>/master master:apps/<name>

所感

移行前に懸念していたこととして、上述のデメリットとしても挙げた Git リポジトリが重くなるかもしれないという点がありました。 今回はアクティブな Rails アプリケーションのみ (5, 6 リポジトリ) を monorepo 化し、commit の総数は 6,000 ほどとなりましたが、現時点では git コマンドに重さを感じることはありません。 ファイルが増えている分容量自体は大きくなっているため git clone 時などに重さを感じますが、これは複数リポジトリを git clone しているようなものであるため許容しています。

また、移行の難易度やコストが高いのではという懸念もありましたが、想像以上に容易に移行することができ、全く問題はありませんでした。

monorepo 化して 1 ヶ月ほど運用されていますが、特に大きな問題もなく、1 リポジトリで完結することの恩恵を大いに受けて生産性が向上していると感じます。

まとめ

今回は monorepo という Git リポジトリの運用方法について紹介しました。 git subtree コマンドを用いると手軽に monorepo 化できるので、密結合なリポジトリがあるときの解決策の 1 つとしてぜひ検討してみてください!


シンクロ・フードでは開発基盤設計に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

Flow で静的型付けしながらフロントエンド開発した話

基盤チームの川井 (@fohte) です。

今回は飲食店リサーチというサービスのフロントエンドを Flow で型付けしながら React で開発して得た知見の話をします。

Flow とは?

Flow は Facebook 社が開発した、JavaScript の構文を拡張して静的型解析機能を提供するツールです。
例えば以下のような Flow を使ったコードは、Flow の型チェックによって事前にバグを検出することができます。

/* @flow */
function square(n: number): number {
  return n * n
}

square('2') // エラー

嬉しいですね。雑にコードを書いていても Flow が型の不一致を警告してくれるため、書いたコードに対して安心感が生まれます。さらに型解析により定義元ジャンプや補完も効くため、開発効率は大きく向上することでしょう。

Flow はあくまで静的型解析機能のみを提供するため、TypeScript などとは異なりそれ自身でトランスパイルは行なえません。実用するためには拡張された構文だけを取り除く以下のようなツールを用いて通常の JavaScript に変換する必要があります。

トランスパイルがほぼ必須となることから気軽さは少ないですが、既に JavaScript のビルド環境が整っている場合には導入しやすいです。

なぜ Flow を使うのか

今回開発した飲食店リサーチというサービスではフォームが動的に生成され、また入力内容によってフォームが動的に変化するという要件がありました。これを実現するためにはそれなりのコード量を必要とする複雑な処理を書く必要があり、そこで静的型解析ができればコード量が増えても安心できるプログラムが書きやすいのではと考え、Flow の導入に踏み切りました。

なぜ TypeScript ではないのか

同じく静的型付き言語の AltJS である TypeScript は、たびたび Flow と比較して語られます。静的型解析が欲しいという要求には TypeScript は十分に満たしているため TypeScript でも問題はありませんでしたが、既に webpack + Babel でのビルド環境が整っていたため、それに乗っかる形で今回は Flow を選択しました。

TypeScript と Flow で機能面での差はほとんどありませんが、TypeScript を選択するメリットとして、コミュニティが Flow よりも大きく (Flow も大きいですが) 歴史も長いためドキュメントが豊富です。 Flow は公式のドキュメントにない情報も少なくなく、また問題が発生したときもリポジトリの issues を見に行くしかないことが多い印象があります。

Flow は着実にバージョンアップを重ねており、このような問題も次第に解消されることを期待しています。

Flow を導入した所感

今回の開発は長期的にかつ断続的に続いたため過去のコードを思い出す手間がありましたが、型定義がドキュメント代わりになっていてその手間はほとんど省けました。静的に型チェックができることで、自分がコードを忘れていても型が間違っていれば即座にエラーになりますし、非常に体験が良かったです。

一方でデメリットとして、複雑な型を表現すると Flow 特有の知識が必要になり、ドキュメントが少ないことも相まって学習難度が上がってしまいます。難しい代表例としては、ある型から別の型を生成する Utility Types が挙げられます。例えばある object 型の key を全て read-only にしたいとき、$ReadOnly を用いればそれを表現できます。

type Props = {
  x: string,
  y: number,
  z: boolean,
}

type ReadOnlyProps = $ReadOnly<Props>

function render(props: ReadOnlyProps) {
  console.log(props.x) // OK
  props.x = 'foobar' // Error

  // ...
}

このような Utility Types は柔軟に型を表現できますが、Flow に慣れていないと難読な型表現になってしまいます。 Utility Types を駆使しないと表現できない型もあり、全てを厳密に型付けしようとすると消耗してしまいます。ときには型検査を捨てる any type を使い、型定義を妥協することが必要になることもあります。 *1

まとめ

今回は JavaScript に静的型解析機能を提供する Flow を紹介しました。型定義に苦戦することはあるものの、静的型解析で受けられる恩恵は非常に大きいです。

ぜひ快適な静的型解析生活を!


シンクロ・フードではフロントエンド開発に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!

*1:mixed type という逃げもあります。

Irisという超絶キュートなキーボードを作成した話

こんにちは、シンクロ・フードの大久保です。 今回はあまり社内の業務と関係がないのですが、Irisというキーボードを作成したお話しをしようかと思います。

f:id:synchro-food:20180213200721j:plain
これがIrisです

TL;DR

  • IrisというキーボードはコンパクトなErgoDoxで、作って良かった!
  • 組み立て式のキーボード制作は思っているよりも簡単
  • はんだ付けへの抵抗感を無くすのにも良さそう

はじめに

僕はErgoDoxという、左右分割型で親指キーがあるキーボードを使っていて、概ね満足はしていたのですが、ErgoDoxは大きく、使わないキーが沢山あるので、もっとスリムにしたいなあ…と思っていました。 いくつか乗り換え先キーボードの選択肢もあったのですが、色々調べた結果、Irisというキーボードを自作しようという結論に達しました。

f:id:synchro-food:20180213200853j:plain
これがErgoDox。親指キーが特徴です。ErgoDoxEZを使っている人は結構多いのではないでしょうか。

Irisとは

Irisとは、RealForceHHKBのような完成品のキーボードではなく、基礎となる基盤のキットやキースイッチ、キーキャップを自分で購入し、組み立てるタイプのキーボードです。同様のキーボードとしては、PlanckLetsSplit(通称レツプリ)などが有名です。

Irisの特徴としては、以下の点が挙げられます。

  • キー配列がErgoDoxとほぼ同じだが、無駄なキーが削れられている
  • qmk_firmwareが使える
  • コンパクト

ただし、組み立てにははんだ付けなどが必要で、ここは少し心理的な抵抗感があるかと思います。僕も電子工作の経験は少ないので、かなり迷ったのですが、同僚のエンジニアも一緒に作る、ということで、2人で作成していくことにしました。 結論から言うと、思っていたよりも簡単に作ることができました。ただし、Irisの場合は、レツプリのように作成ログの日本語情報があまりWeb上にないので(と思っていたら、2018年になって記事増えてますね…)、以下に僕が作った流れを記載しておきます。

部品集め

まずは組み立てるために必要な部品を購入します。 必要なものは大体4つ。基盤関連、キースイッチ、キーキャップ、工具類です。 2万円くらいあれば、すべて揃うと思います。

基盤関連

Irisはkeebioというサイトで購入可能です。僕はキーボードをLEDで光らせる必要がなかったので、LED関連の機器は購入していません。 必要なものは、PCBの商品詳細にすべて書いてあるのですが、僕が購入したものを以下に記載していきます。

PCB

Iris Keyboard - PCBs for Split Ergonomic Keyboard – Keebio

元となる基盤とダイオード、抵抗、ジャックなどのセットです。

Case

Iris Keyboard - Case/Plates – Keebio

基盤を覆うケースです。midlayerという中間層があるタイプとないタイプがあります。

Pro Micro × 2

Pro Micro - 5V/16MHz - Arduino-compatible ATmega32U4 – Keebio

左右で2つ必要です。

TRRS Cable

TRRS Cable – Keebio

左右のキーボードをつなぐケーブルです。「4極ケーブル」や「TRRS」、という名前で探せば、他のサイトでも購入できます。

MicroUSB Cable

Amazon CAPTCHA

Pro MicroとPCを接続するためのケーブルです。なんでもよいと思います。

キースイッチ

キースイッチは56個必要です。無難にいけば、CherryMX互換のものを買えば良いです。 Cherryの茶軸とか赤軸とか、そういうやつです。 僕はNovelKeyというサイトで購入しました。

https://www.novelkeys.xyz/product/outemu-ice-switches/

僕は上記のOutemuIceSwitchのLight Purple軸を使いました。

市販のキーボードではあまり使えない軸を選べるのは自作キーボードの醍醐味です。ですが、注文してから届くまで時間がかかるので、悩みすぎないようにしましょう。僕は注文してから3週間くらいかかりました…。

キーキャップ

キーキャップも56個必要です。キースイッチをCherryMX互換のものを購入した場合は、CherryMX互換のキーキャップを買えば良いです。

僕はpimpmykeyboardというサイトで購入しました。

Pimpmykeyboard.com

Irisのキーキャップは、すべてが同じ大きさなので、keycapsの1xkeyを56個揃えればよいです。 keysetsで買うと安いですが、ちょうどよい組み合わせがないので、多少は多めに購入する必要があります。 DSAとかSAとかはProfileという、形状の種類なのですが、よくわからない場合は、真っ平らなDSAを買うのが無難だと思います。キーキャップは後でいくらでも変えることができるので、気に入らなければ変更すれば良いと思います。

工具類

これは人それぞれですが、僕が使ったものを書いていきます。

はんだごて

白光 ダイヤル式温度制御はんだこて FX600
https://www.amazon.co.jp/dp/B006MQD7M4/ref=cm_sw_r_cp_ep_dp_4NpDAb0CFJP13

温度調整できるので、これで良いと思います。 他にもはんだごてを置く台なども必要なので、セットみたいなものを買うのが無難かもしれません。

はんだ

これは覚えていないのですが、適当なものを買えば良いと思います…。

練習キット

http://amzn.asia/aRcSdG0

はんだ付けが中学生以来…という方は買って練習すると精神衛生上、良いと思います。僕はRaspberryPiを触っていたときに買ったものが残っていたので、これで軽く練習しました。 実際は、練習せずに一発勝負でも問題はないと思います。

吸い取り線

http://amzn.asia/4KxVzY5

失敗した時用。あると精神衛生上、良いと思います。

他にも言い出せばキリがないのですが、絶縁マットやら固定台とかテスターなど色々あるのですが、必要に応じて購入してください。

あとはひたすら待つ

ここまで完了したら、もう完成したも同然です。そして、注文から到着まで、長いです…。 情報収集しつつ、ワクワクしながら待ちましょう。

組み立て

さて、すべてが揃ったら、いよいよ組み立てです。 一つずつ、作業をご紹介します。

ダイオードをはんだ付けする

f:id:synchro-food:20180124202946j:plain

ひたすらはんだ付けです。スイッチの数だけあるので、全部で56箇所?
もし、はんだ付けを初めてやる、という方であれば、とても良い練習になると思います。

f:id:synchro-food:20180125131741j:plain
両手が付け終わった…。(余計な線はペンチで切る)

Irisだと、ダイオードの向きは黒いほうが下になります。

TRRSジャックとリセットボタン、ProMicro用ピンヘッドをはんだ付けする

f:id:synchro-food:20180212191328j:plain
赤がTRRS,青がProMicro用ピンヘッド,黄がリセットボタン

ひたすらちゅんちゅんやるだけ。抵抗はオプショナルなので、つける必要はないです(僕はつけてしまいましたが…)。

キースイッチをはんだ付けする

f:id:synchro-food:20180126124146j:plain
思いっきり会社のデスクで作業してます(お昼休みに作業してました…)

f:id:synchro-food:20180126130010j:plain

ここもひたすらはんだ付けするだけ。

ProMicroをはんだ付け

ピンヘッドに入れて、はんだ付けしたあと、不要なピンを切っていきます。できる限り薄くしたほうがケースと干渉しなくなります。

f:id:synchro-food:20180126194126j:plain
はんだ付けして、ピンを切った状態。左右でProMicroの向きが違います

これではんだ付け作業は終了。あとはケースの蓋を閉じるだけ。

f:id:synchro-food:20180126222309j:plain

QMKfirmwareをビルドする

ProMicroにfirmwareを書き込みます。 自作キーボードの場合、qmk_firmwareというオープンソースのfirmwareを使うのが一般的で、Irisもそちらを用います。 具体的な作業は、PCでソースをビルドし、コマンドを使ってProMicroに書き込む、という作業を行います。

qmk_firmwareをforkする

以下リポジトリをforkします。
https://github.com/qmk/qmk_firmware

ビルド環境を整える

下記リンクに従って、各種OS毎にビルドする環境を整えてください。 https://docs.qmk.fm/getting_started_build_tools.html

僕はMacでした。Macユーザーの場合はhomebrewでバンバン入れていくだけです。この準備、25分くらいかかります。 なぜかEl Captanだと失敗するときがあったのですが、何度か実行すると、うまくいきました(適当ですいません)。Siera環境だと一発で成功しました。

ビルド実行する

Iris向けのキーマップはすでに準備されているので、まずはREADMEに従ってデフォルトを書き込むのが良いと思います。 https://github.com/qmk/qmk_firmware/tree/master/keyboards/iris

ProMicroにmicroUSBケーブルを接続して、以下コマンドを実行すると、ビルドと書き込みが始まります。

make iris/rev2:default:avrdude

最初だけは、両方のProMicroに書き込む必要があります。 ですので、以下の手順で実行していけば良いです。

  1. 左手のProMicroに書き込み(左手のみ、反応する状態になる)
  2. 右手のProMicroに書き込み(両手が左手のキーマップになる)
  3. 左手のProMicroに書き込み(左手、右手が正しいキーマップになる)

デフォルトのキーマップは、左手側のProMicroにmicroUSBを接続することを前提としているので、上記流れになります。 なお、上記対応をやったあとは、左手だけに書き込むことで、両手のキーマップが反映することになります。 ※このあたりの手順を知らずに対応していたので、両手が左手になったとき、めちゃくちゃ焦りました

完成

f:id:synchro-food:20180127004006j:plain
会社では終わらず、自宅に持ち帰って作業。動いたときはめちゃくちゃ嬉しかったです。

これで完成!

あとはキーマップを自分向けにカスタマイズしていくだけです! 基本的にはdefaultのキーマップをコピーして別名にし、それを変更して、そちらをビルドして書き込む感じです。 例えば、mykeymap、というファイル名でキーマップを作った場合は、以下のコマンドを実行する感じになります。

make iris/rev2:mykeymap:avrdude

尚、ErgoDoxのキーマッピングに慣れている方から見ると、KC_が省略されているキーマッピングになっていて、少し??となると思いますが、sourceを読めば理解できると思います。 一応、僕のキーマップもおいておきます。 https://github.com/shunohkubo/qmk_firmware/blob/develop/keyboards/iris/keymaps/chibikubo/keymap.c

キーマップをあれこれ考えるのも、とても楽しく、沼が待ってます。

参考にしたサイト

非常に詳細なビルドログ
https://imgur.com/a/iQH2W#k3cwV69

作成作業の動画。めっちゃ参考にしました。ProMicroの向きが、ちょっと不思議です。
Iris Split Ergonomic Keyboard Build Log - YouTube

2017の自作キーボードアドベントカレンダーはモチベーションを高める意味でも助けれられました!
https://adventar.org/calendars/2114

終わりに

非常に簡単に作成できますので、興味があれば是非作ってみることをオススメします。
尚、Irisは2018年3月時点で品切れ中…。4月には再度生産してくれるそうなので、手に入ると思います。
他の自作キーボードも、基本的にはIrisと同じ流れで作れると思うので、そういったものを作るのも良いと思います。

シンクロ・フードでは、自作キーボードに興味のあるエンジニアが数名在籍しているので、興味のある方は是非ご応募ください!キーボード談義に花を咲かせましょう。(業務でキーボードを自作することはありませんのでご注意ください…)

www.synchro-food.co.jp

AWS Lambda+API Gateway + S3で格安リアルタイム画像リサイズAPIを作成する

こんにちは、シンクロ・フードの大久保です。

今回は実際に弊社で運用しているAWS Lambdaを使ったリアルタイム画像変換APIについてご紹介したいと思います。 リアルタイム画像変換APIについては、あまり詳しく説明しませんが、画像のサイズ変換等をURLパラメータで指定してリアルタイムに変換することです。 弊社の場合はリサイズしか行わないため、画像変換APIというよりは画像リサイズAPI、というほうが適切かもしれません。

リアルタイム画像変換の方法について

弊社の方法をご紹介する前に、リアルタイム画像変換の一般的な実現手法を挙げてみます。

  1. Nginx, ApacheなどのWebサーバのプラグインを用いる
  2. CDNとして提供されている機能を用いる

1は、クックパッド社のmod_tofuが有名で、色々企業で実現されている手法です。サーバを用意しなければならない点や、冗長化を考えると弊社ではコスト的に取り組めませんでした。 2は、Akamaiやfastlyなど、高機能なCDNサービスを使う方法です。このあたりのサービスは以前は高額なものが多かったのですが、fastlyなどはとてもお得だと思います。

APIドキュメント
https://docs.fastly.com/api/imageopto/

料金
https://www.fastly.com/pricing/

こんなブログポストを書いておいてなんですが、リアルタイム画像変換は自作するよりも、fastlyの利用を検討することをオススメします。弊社も、このリアルタイム変換のシステムを作る前にfastlyで実現できることを知っていたら、fastlyを利用していたのではないかと思います。

ということで、以下はそれでもLambdaを使って自分たちで構築したい、という方向けの記事です。

設計

ベースとなる設計は、以下のAWSブログの記事をベースとしています。

https://aws.amazon.com/blogs/compute/resize-images-on-the-fly-with-amazon-s3-aws-lambda-and-amazon-api-gateway/

弊社はこの設計に、さらにCloudFrontをかませており、以下のようなリクエストの流れとなります。元記事よりリクエストの流れを丁寧に説明していきます。

f:id:synchro-food:20180114145809p:plain

前提として、S3のバケットは2つ用意する必要があります。1つはリサイズ元画像を格納するOriginalBucket、もう1つはリサイズ後の画像を格納するResizedBucketです。

  1. ユーザーがCloudFrontにリクエストを送ります。初回リクエストはCloudFront上にキャッシュが無いため、CloudFrontはオリジンとして設定しているS3のResizedBucketにリクエストを流します。
  2. CloudFrontから流れてきたリクエストを受け、S3はBucket内を探しますが、Resizeされた画像が存在しません
  3. 画像が存在しない場合、内部用の画像変換APIへ307レスポンスが投げられます
  4. CloudFrontはS3からの307のレスポンスをそのままユーザーへ戻します
  5. ユーザーは307レスポンスを受け、次は内部用画像変換APIのURLを待ち受ける、APIGatewayにリクエストを投げます
  6. API GatewayよりLambdaの画像変換関数がキックされます
  7. LambdaがS3のOriginalBucketより、画像リサイズ元となる画像を取得します
  8. 7で取得した画像をパラメータに従ってリサイズし、ResizedBucketに保存します
  9. 8で保存した画像へのURLへの301レスポンスをLambdaにて返します
  10. 301レスポンスをそのままユーザーに返します
  11. ユーザーは301レスポンスに従い、もう一度リサイズ画像のリクエストを送ります
  12. CloudFrontからResizedBucketにリクエストが流れ、変換後画像をレスポンスとしてキャッシュしつつ、結果をキャッシュする

大きな流れは以上です。最初に画像を閲覧したユーザーは3回リクエストが飛ぶことになりますが、2回目以降はCloudFrontがキャッシュしているため、高速に戻すことができます。 リクエストが3回も飛ぶ動きなどが一見気持ち悪い手法ですが、この方法で半年以上運用し、特に大きな問題は起きていません。

以降、少し細かい設定方法を説明していきます。

Lambda関数を用意する

流れの図にある、7(S3からリサイズ元画像を取得する)と8(画像をリサイズしResizedBucketに入れる)、という2つの処理を行うLambda関数を作ります。 コードは、元記事も紹介している、以下のコードを改造していくのが良いと思います。

https://github.com/awslabs/serverless-image-resizing

弊社はこのコードを元に、以下の修正を加えて運用しています。

  • 不正なリサイズリクエストを除外するためのハッシュチェック
  • 細かいバリデーション、ログ埋め込みなど
  • serverlessフレームワーク化(まったく必須ではありません)

ハッシュチェックについては、誰かれかまわずリサイズできると困るため、確実に自分たちのサービスからのリクエストであることをチェックするために、アプリケーション側で作成したハッシュ値をLambda側でチェックしています。

API Gatewayの設定

こちらは特に特殊なことをやっているわけではないのですが、API Gatewayは分かりにくいため、記載しておきます。

リソースの作成

まず、API Gatewayにある「APIを作成」というボタンから新しいAPIを作成したあと、「アクション」→「リソースの作成」を選択します。

f:id:synchro-food:20180114161809p:plain

その後、上記画面のような入力画面が表示されるのですが、ここで「プロキシリソースとして設定」にチェックを入れて、リソースを作成してください。進むと以下のような画面が出ます。

f:id:synchro-food:20180118155859p:plain

こちらでは統合タイプを「Lambda関数プロキシ」、Lambdaリージョンは、後述するLambda関数のリージョン、Lambda関数は、作成したLambda関数を指定してください。

デプロイ

「アクション」→「APIのデプロイ」を選択し、ステージを選択します。 最初はステージがないため、「新しいステージ」を選択し、ステージ名を入力してください。 とりあえず、prod、という名称にすることが多いように思います。

デプロイが完了すると、APIGatewayのURLが生成されます。

https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod

これでAPIGatewayの準備は完了です。

S3の設定

上記設計図におけるResizedBucketは、StaticWebHostingを有効にしておく必要があります。それ自体は簡単なのですが、同時にリダイレクトルールを以下のように設定しておく必要があります。

<RoutingRules>
  <RoutingRule>
    <Condition>
      <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
    </Condition>
    <Redirect>
      <Protocol>https</Protocol>
      <HostName>${API Gatewayのホスト名}</HostName>
      <ReplaceKeyPrefixWith>${API Gatewayのステージ名}/resize?key=</ReplaceKeyPrefixWith>
      <HttpRedirectCode>307</HttpRedirectCode>
    </Redirect>
  </RoutingRule>
</RoutingRules>

もう一つ、Lambdaが作成した画像ファイルを都度閲覧可能にする必要があるため、バケットポリシーの設定も必要です。この設定がないと、Lambdaが作成したリサイズ後画像がPrivate権限となっていて、画像参照ができません。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::${S3のバケット名}/*"
        }
    ]
}

CloudFrontの設定

こちらは通常通り作成し、オリジンにはResizedBucketを指定します。

キャッシュ問題について

ただし、このままですと、CloudFrontは1回目の307リクエストの結果をキャッシュしてしまい、変換後の画像を表示してくれません。

この問題は、CloudFrontがキャッシュするかどうかは、S3のオブジェクトが持つCacheControlヘッダを使うようにすれば、解決します。

具体的な操作は以下の通りです。

  • CloudFrontの「Behaviors」→「Edit」→「Object Caching」を、「Use Origin Cache Headers」から「Customize」にする。
  • Lambda側のコードで、リサイズ後画像をS3にPutする際に、CacheControlヘッダを追加する。
      .then(buffer => S3.putObject({
          Body: buffer,
          Bucket: BUCKET,
          ContentType: 'image/png',
          CacheControl: 'max-age=2592000, s-maxage=259200',
          Key: key,
        }).promise()
      )

これでCloudFrontが適切にCacheしてくれるようになります。

基本的な設定の流れは以上です。 この設定を行うことで、以下のようなURLを渡すと、そのサイズにリサイズされた画像が戻ってくるようになります。 呼び出しイメージは以下のような雰囲気です。

https://xxxxxxxxxxx.cloudfront.net/hash/300x100/xxxx.jpg

実際に運用した感想

実際に上記のような設計でリアルタイム画像変換APIを作成し、半年ほど運用をしましたので、所感を列挙します。

  • 安定しています。CloudFront,S3はもちろんですが、lambdaでのトラブルは一切ありません。
  • 画像以外のリクエストが多い。faviconや画像以外のファイルリクエストが沢山きます。リリース後、余計なリクエストを除外する仕組みを入れました。
  • エラー検知は、とりあえずCloudWatchにて、Lambdaの実行エラーをアラームにしています。変換に失敗したときにメールが飛んできますが、エラー内容がメールに記載されていないので、少し面倒です。
  • lambdaのメモリ設定やtimeout設定などは、最初大きめに取り、ログを見ながら最適なメモリサイズやtimeoutに落としていく、という方法が良さそうです。
  • (これはリクエスト数次第ですが)費用は安いです。一度変換した画像は二度と変換が実行されないため、lambdaの呼び出し回数も少ないです。

まとめ

特に手法自体は新規性がないと思いますが、実際にプロダクション環境で運用している事を公開することで、誰かの参考になるかなと思い、ご紹介しました。 リアルタイム変換を今から導入しようと思うのであれば、まずはFastlyを第一に検討すべきかと思いますが、なんらかの問題があった場合などにご検討ください。

シンクロ・フードでは常にエンジニアを募集しています。 ご興味のある方は以下よりエントリーしてみてください!応募ではなく、話を聞いてみたい的なものでも結構ですので、お気軽にお問い合わせください。