クラウドワークス エンジニアブログ

日本最大級のクラウドソーシング「クラウドワークス」の開発の裏側をお届けするエンジニアブログ

SQLとの付き合い方

こんにちは、HuluよりNetflix派の田中です(ロゴがお洒落)。

みなさん、SQL書いてますか?

私自身は未だに使う機会が多いことからもなかなか枯れない技術の1つだなと感じながら、日々愛着を持って書かせてもらってます。

ただ、触り始めた頃はわかってないことが多く、プログラミング言語とは勝手が大きく違うことからも、ある程度書けるようになるまでは時間を要した記憶があります。

正直それまでは、あまり好きにもなれませんでした。

今回はそんな経験から、今では好きと言えるレベルになっている「SQLとの付き合い方」をまとめてみました。

クラウドワークス開発におけるSQLとの関わり方

始めに、普段の開発でのSQLとの関わり方を簡単に紹介しておきます。

クラウドワークスの開発ではO/Rマッパー(Ruby on RailsActiveRecord)に任せることが多く、素のSQLはさほど出てきません。

ただ、日常的なデータ分析や抽出作業、または分析基盤の裏側等ではゴリゴリのSQLが必要になることはままある状況です (エンジニア以外の分析チームやマーケティング関係のメンバーもSQLを書きます)。

RDBMSとしてはMySQL、DWHとしてはRedshiftを用いており、ベーシックなSQLを用いつつそれぞれの特性を活かした関数や拡張仕様を利用したりしなかったりしています。

続きを読む

Rails4アップグレードのつまずきどころ

みなさんさようなら、@h3_potetoです。

いよいよRails4アップグレードシリーズもラストです。

最後は、Railsの仕様変更によるアプリの改修です。 これから書くことは、移行前からわかっていて対策していたものもあれば、テストをしたときに発覚したものもあります。 もっとひどいのはリリースした後に発覚したものもあります。 なんて悲しいことなんだ……。

ActiveRecord周り

やはり一番壊れそうなところですから、予想通り壊れましたね……。

絞り込み結果がArrayではなくなる

Rails4では、has_many 関連の中身が ActiveRecord::Associations::CollectionProxy で返ってきます。 かつては has_many 関連も Array として扱えていたため、Array のメソッドを呼んでいたりする場所がいくつかありました。

reject! とかね!

これらのメソッドが undefined method になってしまいます。

配列で欲しい場合は、to_a するとか、target メソッドを呼ぶと配列がもらえるので、それを使って回避できます。

default_scopeの罠

悪名高きRailsdefault_scope ですが、Rails4へのアップグレードでも名前を聞くとは……。

Rails3の場合、joinsやincludesしたときに、default_scope が反映されません。

しかし、Rails4になると、joinsやincludesのときにも default_scope が反映されます。

これ、default_scope に限った話だけではなくて、scope を使っていた他のクエリも、結果が変わるものがいくつもありました。

techlife.cookpad.com

github.com

たとえば、こんなモデルが定義されていたとします。

class Poteto < ActiveRecord::Base
  has_many :poteto_categories
end
class PotetoCategory < ActiveRecord::Base
  default_scope -> { where(remove_flag: false) }
end

このときに、

Poteto.joins(:poteto_categories).where(hoge: "fuga").to_sql

みたいなことをしてみると、結果が変わります。

Rails3

SELECT
  `potetos`.*
FROM `potetos`
INNER JOIN `poteto_categories`
ON `poteto_categories`.`id` = `potetos`.`category_id`
WHERE
`potetos`.`hoge` = 'fuga'

Rails4

SELECT
  `potetos`.*
FROM `potetos`
INNER JOIN `poteto_categories`
ON `poteto_categories`.`id` = `potetos`.`category_id`
AND `poteto_categories`.`removed_flag` = 0
WHERE
`potetos`.`hoge` = 'fuga'

今まで効いていなかった scope が効くようになったってことは、実はRails4になって、むしろ正しく動くようになったってことです。

今までがダメダメだったということが判明して悲しみ……。

serializeのタイミングが変更になっている

あまり褒められたことではありませんが、ActiveRecordのserializeを使って、JSONとかYAMLをDBに保存している場所がありました。 そして、この部分のバリデーションが落ちるようになりました。

Rails3では、serialize(dumpメソッドの呼出)は値の保存時に行われていました。そのため、バリデーションに入ったときには、値はserialize前の値をチェックしていました。

しかし、Rails4.2より、serializeは値を代入したタイミングで行われるようになり、そのため、バリデーション時には既にserialize済みのデータチェックになってしまいます。

Controller周り

paramsのクラスが変更されている

controllerでパラメータを取得したとき、普段は何も意識せずにハッシュとして扱っています。 実はこれ、Rails3では、 ActiveSupport::HashWithIndifferentAccess というクラスでした。

しかし、Rails4になるとこれが、ActionController::Parameters という、ActiveSupport::HashWithIndifferentAccess のサブクラスになります(StrongParameterが入るにあたって追加されています)。

Class: ActionController::Parameters — Documentation for rails (4.1.7)

普通にハッシュとしてアクセスしている限りは何も困ることはないはずです。

ただ、パラメータをDBに保存していたり、パラメータをキャッシュに保存していたりすると話は違ってきます。 特に、今回のように、Rails3とRails4の共存期間がある場合 には、双方のデータに互換性がある必要があります。

この場合で言うと、Rails4になって新しいクラスができているので、Rails4で保存したパラメータのデータはRails3側からは読み込めなくなります。

そもそも、パラメータをそのままDBに保存するとか、そのままキャッシュに保存するとか、かなりよろしくない仕組みであることは間違いないんですが……。

とりあえずこの互換を保つだけなら、ActionController::ParametersActiveSupport::HashWithIndifferentAccess のサブクラスなので、ActiveSupport::HashWithIndifferentAccessに変換してやれば問題なさそうです。 変換したパラメータに関しては、StrongParameter系のメソッドが使えなくなってしまうので、完全にRails4移行して、StrongParameterに書き換えるときまでには、ActionController::Parameters にしておく必要はありますが……。

params[:poteto].to_h.with_indifferent_access

とかしてやれば、変換できます。

いや、やはりrubyのオブジェクトをそのままDBに保存するとか、memcachedに保存しておくとか、非常に良くないと思うんですよね。

DB周り

まさかActiveRecordより深いところで困るとは……。

Rails4になると、DBとの接続がstrict modeに強制されます。

Rails3時代のCrowdWorksは、かなりゆるゆるにsql_modeを設定していたため、strict modeによるエラーが出ました。

もちろん、sql_modeを別のものに指定することもできます。

qiita.com

ただ、本来であるならエラーになるべきであるものを、握りつぶしている事自体がおかしい。 ならばこれを期にstrict modeにしよう。 というわけで、strict modeは維持して、エラーを潰すことにしました。

Data too longって怒られる

varchar(255)のカラムにやたら長い文字列が入っているような場合があります。 一般的にはバリデーションで弾くべきですが、バリデーションがない!

そのため、DBに300文字くらいを入れようとして、エラーになっていました。 場所にもよりますが、ユーザが入力するような場所の場合には、バリデーションを入れましょう。

Incorrect string valueって怒られる

たいていの場合は絵文字で起こっていました。 DBのテーブルがutf8で定義されている場合に、4バイト文字である絵文字を入れようとするとエラーになります。

今までは、悲しいことにエラーを握りつぶして、絵文字以降の文字列を切り捨てて保存していました。

できれば絵文字も保存できるように、utf8mb4にしたいところですが、これはテーブルによってはかなり難しい……。インデックスのサイズが変更になるので、インデックスを貼っていたりすると一筋縄ではいかない。

qiita.com

もちろんutf8mb4になっている方が、今後嬉しいことが多い予感はしますが、それは今回の対応範囲ではなさそうな大きさです。

なので、ここも一応バリデーションで逃げています。

まとめ

一般的な事象ではまったのはこのくらいですが、これ以外にも、モンキーパッチを作っていたりした部分は盛大にコケたりしていました。 たいてい一番悲しいのはここで、特にActiveRecordの実装に大きく依存しているモンキーパッチを作っていると、直すのはかなり辛くなってきます。

解決策を自分たちで考えなきゃいけない上に、そういうものに限ってテストが書きにくかったり、そもそもテストが無かったり(!!)、絶望的です。

今回はそういったモンキーパッチをかなり消しました。 機能的にまだまだ使わない、長大な機能を提供していたり、そもそも現在ではgemで解決できたり、そういったものの大部分は削除する方向になりました。

おかげでRails4アップグレードによって、ソースが少し綺麗になりました。

ただ、それでも消せない機能は残ってしまいます。だから、テストだけは、独自拡張のテストだけは絶対に書いておきたい。

We're Hiring!

期せずして、アップグレードついでに掃除までやりましたが……。 アップグレードを考えると、こういうことをやっちゃいけないなーというものが結構上がったと思います。

クラウドソーシングのクラウドワークス では、こんなものたちを一緒に掃除をしてくれるエンジニアを募集しています。次回Rails5に上げるときはこんな悲しい思いをしなくて済むといいな!!

www.wantedly.com

Rails 4移行時のサービス監視について

⠀人
/ ⁰⊖⁰ \ オカメインコエンジニアの五十嵐(@ganta0087)です。

本連載のなかでRails 3/4の環境を並行稼動させたことについては既にお話させていただきました。 この状態で今までどおりの監視をしていると、何か問題が発生したときにRails 4が原因なのかが切り分けづらくなってしまいます。そのため、次のようなことを追加で行うようにしました。

  • Rails 3と4でエラーアラートの通知を分離
  • Rails 3と4のパフォーマンスのメトリクスを比較

これによってRails 3のときとは異なる事象を発見したり、エラーの原因の調査に役立つことが期待できます。 実際にアプリケーションサーバーでRails 3のときよりも負荷の上がる事象が発生し、特定のコントローラーで問題が発生していることがすぐに特定できました。

今回はこれらの設定方法について紹介します。

f:id:ganta0087:20160714183514p:plain

続きを読む

フレームワークのアップグレードにおけるテスト戦略とリリース戦略

所(@ctokoro_me)です。

弊社のメインサービスであるクラウドワークスフレームワークをRails3からRails4へと移行を行った事に関しての連載です。本記事ではアップグレードに際してのテスト戦略とリリース戦略に関して書きたいと思います。

engineer.crowdworks.jp

テスト戦略

サービスを開始して4年を超えたクラウドワークスのコードベースは、今回行われたフレームワークのバージョンアップのような大規模な変更は未経験でしたが、開発体制として自動テストと継続的インテグレーション(CI) は早い段階から組み込まれており、今回のRails4移行においてもCI上で Rails3/Rails4 のクロスビルドは行われておりました。

しかし、特に直近のクラウドワークスは事業規模もエンジニア数も急拡大した時期であり、それに伴いコード量も10万行を超えコードの成長速度も加速していったものの、自動テストの拡充はコードの成長に追いついているとは言えない状況でした。

f:id:uzuki-first:20160712155203p:plain

続きを読む

KPI に寄与できない開発課題を、組織全体で取り組むということ

はじめに

クラウドワークスエンジニアの八木です。 先般の記事でも触れられていた通り、クラウドワークスではシステムのフレームワークとして採用している Ruby on Rails を 3 系から 4 系に移行しました。

残念ながら、「こことそことあそこを直して、さあリリース!!!」とはいかず、それなりの時間を投入して行いました。

今回は、Rails のバージョンアップをスムーズに行えないという技術的課題は一旦脇に置いておいて、フレームワークのバージョンアップという「事業の KPI に直接寄与できない開発課題」に対して、クラウドワークスの開発チームがどのように取り組んだか、組織体制の話を書いてみたいと思います。

チーム体制の変遷

まず、クラウドワークスで Rails4 対応するために組んだ組織構成について、時系列に沿って簡単にご紹介します。簡単にご紹介と言いつつ先に要約すると、最初は Rails4 対応に専任チームを設けていて、最終的にはテストを全エンジニアで分担して時期を調整しながらリリースを進めました、といったところです。

1. Rails4 対応黎明期

当時インフラを担当していたエンジニア1〜2名で、検索負荷の対策や開発環境の構築、いろんなインフラタスクの合間に Rails4 の対応を始めました。この間は、アップグレードガイドを参照して修正を行ったり、Rails4 に対応させるパッチを書いたり、テストの拡充をしていたりしました。

2. 休止時代

だんだんとエンジニアが増えていくにつれてプロジェクトも増えていき、サーバ構築の自動化と Rails4 対応が並行して行われている時期がありました。しかしそれでは中途半端ということで、一旦インフラのメンバーはサーバ構築の自動化に注力し、Rails4 対応は小休止する期間がありました。

3. チーム時代

インフラ自動化がひと段落したあと、 Rails4 対応する組織が、正式に「Rails4チーム」として発足しました(筆者もこのときからジョイン)。

まずは Rails4 で CI を動かしたりアプリケーションを立ち上げてひたすらバグ潰しを行い、ある程度目処が立ったところでテスト環境の構築*1や、機能ごとに Rails4 をリリースすることができる環境を作るためのインフラ構成変更を担当しました。

4. テスト分担時代

テスト環境の準備が整ったあと、クラウドワークス全体の機能を分割して全エンジニアに割り振りました。それぞれの機能はリリースする日がスケージューリングされていて*2、割り振られた各エンジニアがスケジュールから逆算したテスト期間でテストを行っていました。

Rails4チームはプロジェクト全体進行役として、テスト環境の更新やリリース、特急で作った新インフラ構成の運用を自動化する作業などを行っていました。

5. コード掃除・インフラ掃除週間

晴れて全機能を Rails4 化させたあと、Rails3/Rails4 を互換で動かすためのアプリケーションコードや、並行運用に使ったインフラリソースのコードを消すために、お掃除週間を設けました。全リリース終了後に、Rails4 チームだったメンバーが一週間集中して、残課題の解消に取り組みました。

続きを読む

© 2016 CrowdWorks, Inc., All rights reserved.