DIGGLE開発者ブログ

予実管理クラウドDIGGLEのプロダクトチームが、技術や製品開発について発信します

ふりかえり(レトロスペクティブ)を活用してチームの自己組織化を促す

UnsplashRavi Palweが撮影した写真

フロントエンドエンジニア兼デザイナーの大澤です。
今回は主にふりかえりによってDIGGLEの開発チームがいかに自己組織化を達成したか、その肝である「ふりかえり」をどのように実施したかについてお伝えします。

なぜ自己組織化を求めるのか

自己組織化したチームには以下の利点があります。

  • 少ないルール
  • 少ない管理コスト
  • 問題が放置されづらい(誰かが拾って対応できる人に渡される)
  • 互いがサポートする中で生まれる安心感
  • 良い意味で交代可能(穴は誰かが埋めてくれるという信頼がある) -> 有給取りやすい
  • そもそもスクラムに必要

DIGGLEではスクラムを採用したので自己組織化は必須でした。
ただ仮にスクラムを採用しなかったとしても上記の利点は魅力的です。
個人的には、ルールを守るために注意を割かれるのが嫌い(そんなことより仕事自体に集中したい)なので、少ないルールで動けるチームであると嬉しいと考えていました。

実際に得たこと

ふりかえりを起点とした改善行動によってDIGGLEの開発チームは、おおよそ自己組織的になったと思います。
上記のメリットに加えて、以下のことも達成できました。

  1. 安定したベロシティ&バーンアップの上昇
  2. 全員がスクラムマスターになれる(輪番制)
  3. 上記をフルリモート環境で運用できている

これらは継続的なふりかえりによる問題発見と改善によってもたらされたと考えています。

過去の失敗経験と学び

狭い経験内の話ではありますが、DIGGLEほど自己組織化に成功したチームはありませんでした。
過去に自己組織化に失敗した事例を思い返すと、以下の理由が思い当たります。

  1. スクラムやアジャイルのプラクティスを綺麗に回すことだけに気を取られる(形式重視)
  2. 既にあるチームや組織の慣習から離れられない
  3. プラクティスの適用を急いでしまう

特に3番目は、1と2が合わさると発生しやすくなります。
素早くプラクティスを適用して、悪い/非生産的(と思っている)文化からの脱却を図ろうとします。しかし、急いで適用するためプラクティスは浸透しません。メンバーは怒られないために仕方なくやっている状況となり、主体性に欠けた受け身のチームが出来上がります。

自分なりの回答: 自己組織化は時間がかかる

自己組織化はチームが主体的に改善を繰り返していく中で達成できます。改善のほとんどは小さなものですが、この積み重ねが大きな変化をもたらします。チームが自分達のものだと強く認識でき、結果として自己組織化が促されます。

自己組織化は一朝一夕で達成できるものではなく、継続的に取り組んだからこそ得られる状態です。
そして、チームの改善行動を促す仕組みこそが「ふりかえり(レトロスペクティブ)」なのです。

シンプルに始める

DIGGLEでは以下のことを重視しました。

  1. 必要なスクラムプラクティスから少しずつ取り入れる
  2. ふりかえりでの問題発見と改善行動の繰り返しの中で「少しずつ」改善する

スクラムを取り入れた当時、必要最小限と考えたプラクティスは以下のものです。

  • スプリント(タイムボックスの役割)
  • プランニング(タスク見積もり)
  • ふりかえり(問題発見と改善)

初期はデイリースクラムもレビューもリファインメントもありませんでした。
今でこそこれらのイベントも実施していますが、加わったのはふりかえりの中で問題が見えてきた後になります。
問題が顕在化してから適用することで「なぜやるのか」が明確になります。

問題を率直に共有することの重要性

「部屋の中の象」という言葉があります。
誰の目にも明らかな問題が存在するのに、誰もそのことに触れない状況のことを指します。もし部屋に象がいたら、それこそが「本物の問題」です。ふりかえりで共有される問題が「本物の問題」でなければ、ふりかえりの効果が極めて限定的になります。

以下のような状況では率直な共有がされません。

  • 問題報告者が面倒ごとを押し付けられる
  • 問題を共有したけど無視される
  • 切断処理(「君の問題だよね?」)
  • 心理的安全性を欠く

上記のような状況を引き起こさないためにファシリテーター(スクラムの場合はスクラムマスター)が率先して状況を切り開く必要があります。
と言っても自分から問題に突っ込むというより、メンバーに率直さと積極性を促すことが重要です。呼び水となる問いかけをしましょう。ふりかえりの場ではチーム全員が話して良いという雰囲気を作り出すことで、本物の問題を引っ張り出すきっかけになります。

ふりかえり手法について

DIGGLEではKPTを用いていますが、問題を共有して改善行動に繋げることができれば何でも良いと思っています。
大事なことは以下の通りです。

  • 定期的に開催する
  • 対象を限定する(ex: 直前のスプリント)
  • 思いつく限り書き出す(書き出すハードルは低めにする)
  • 具体的な改善行動につなげる(次のスプリントで実行できると望ましい)

具体的な改善行動まで繋げられると理想です。とはいえ、最も重要なことはチームメンバーが感じている問題を共有することにあります。問題が共有されていれば、後はどうとでもなります。

反対に最も怖いのが「問題が見えない」ことです。チームメンバーが萎縮したり、無力感を感じていると必要な共有がされません。ふりかえりには心理的安全性が必要です。ファシリテーターはこれを維持する努力をしましょう。

「ふりかえりではどんな問題も話し合える」と感じてもらえるのが理想です。

結果の出ないふりかえりになる覚悟を持つ

ここまで色々書いてきましたが、いつだってうまくいくわけではありません。
例えば、以下のような状況になって十分な成果を出せないときもあります。

  • 議論が長引いて、残りの問題について話し合う時間が不足する。結果、適当に終わらせてしまう。
  • 疲労などの影響で注意が逸れて必要な議論が不足する(面倒になってしまう)
  • 自信を失って話し合いを打ち切ってしまう

毎度完璧で効果的なふりかえりができるなんてことはありません。うまくいかない日は必ずあります。
そんな日があったとしても、改善プロセスを繰り返すことこそが重要なのです。
「次こそはもっと良くしよう」「小さくても良いから次の改善に繋げよう」という意識を持ち続ける限り、チームを成長させることができます。

諦めずに続けていきましょう。

まとめ

DIGGLEではふりかえりを改善プロセスの中心に据えることで自己組織化を達成できました。
この結果としてスクラムの運用とチームの独立性を保てていると思います。

もちろん今後がどうなるかは分かりません。
しかし、ふりかえりを中心にした改善プロセスが回り続けることは変わらないと思っています。

We're hiring!

DIGGLEでは主体性を発揮してチームで最高のプロダクトを作りたいエンジニアやデザイナーを募集しております!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

herp.careers

検証用スクリプトの実行環境として AWS Cloud9 を採用した話

こんにちは。DIGGLE エンジニアの miyakawa です。
普段はバックエンドとインフラを中心に開発しています。

今回は、前回の記事で触れたレポート機能の数値検証の「実行環境」についてお話したいと思います。

レポート機能の数値検証の内容については前回の記事をご覧ください。 diggle.engineer

数値検証の実行環境

まず結論として、現在は数値検証の実行環境として AWS の Cloud9 というサービスを利用しています。

Cloud9 はクラウドベースの統合開発環境(IDE)を提供してくれるサービスです。

docs.aws.amazon.com

IDE なので開発環境としての用途が一般的かと思いますが、弊チームでは検証用スクリプトの共有実行環境として活用しています。

次に Cloud9 を採用した経緯についてお話しします。

実行環境選定の経緯

ローカル環境での課題

当初、数値検証の実行は開発メンバーのローカル環境 (PC)上で行っていました。

数値検証には本番環境のデータベースのデータを複製したデータベース(以降、数値検証用 DB)を用いますが、セキュリティ上ネットワーク外部からの直接アクセスはできないようになっています。

そこで、ローカル環境から数値検証用 DB へアクセスするために、EC2 インスタンスによる踏み台サーバを用意し AWS Systems Manager の Session Manager を使ってセキュアにログイン、数値検証用DBまでポートフォワード接続できる環境を構築していました。

しかしながら、このローカル環境で数値検証を行う方法では以下の問題がありました。

  • 数値検証の実行完了まで数時間かかり、その間ローカル環境のリポジトリのブランチは固定かつ PC リソースも消費されるため開発作業に支障が発生する
  • 40〜60分ほどで Session Manager の接続が切れて数値検証の実行が途中で止まってしまう*1

二つ目の問題については、数値検証スクリプトに途中の段階からリトライするオプションがあるため、実行が止まるたび都度リトライすることで対処していましたがかなりの手間となっていました。

ローカル環境以外の方法の模索

ローカル環境を使う運用は難しいと判断し、それ以外の方法を検討し始めました。

ここで一度数値検証の実行環境の要件をまとめます。

  • Rake タスクを実行できる or Docker コンテナを実行できる
  • リポジトリの任意のブランチで実行することができる
  • AWS のプライベートネットワークにある数値検証用 DB にアクセスできる

これを踏まえて検討した結果、以下のような案が出ました。

  • EC2 インスタンスにローカル環境と同様の環境(git + Docker)を用意して数値検証を実行する
  • 上記に加えてジョブ管理ツール(Rundeck など)を導入して Web ベースで作業できるようにする
  • Github Actions のワークフローとして実行する*2
    • 数値検証用 DB アクセスのために EC2 インスタンスで self-hosted runners を構築する

いずれも AWS 上に EC2 インスタンスを作成するという点は同じで費用面でのコストに差はありませんでした。
後は工数との兼ね合いかと考えていたところで、AWS のソリューションアーキテクトの方と相談の機会があり本件についても相談してみました。

すると
「Cloud9 が適しているのはないか」
とのご助言が...!

それまで Cloud9 は使ったことがあってもあくまで開発環境用というイメージしかなかったため目から鱗でした。

早速 Cloud9 による環境を検証したところ、次に述べる Cloud9 の利点から、先に検討していた各案よりも優れていると判断し最終的に採用へ至りました。

Cloud9 の利点

環境作成が容易

Cloud9 の EC2 環境では EC2インスタンスをベースにクラウド IDE 環境を構築できます。
AWS コンソールからネットワーク(VPC)や接続方法等の最低限の情報を設定するだけで、すぐに開発を始められる EC2 インスタンスが作成されます。
(Cloud9 の環境作成方法の詳細については こちら を参照ください)

IDE の機能を除いたとしても、一から EC2 インスタンスをセットアップする場合と比べて多くの利点があり、セットアップの工数を減らすことができます。
例えばですが、

  • (接続方法として選択した場合)Session Manager の設定が自動で行われる
  • git、docker といった開発に必要なツール群がプレインストールされている

などが挙げられます。

IAM ユーザによる共有

Cloud9 の環境は作成時点では作成者のみが利用できる状態です。 そこに IAM ユーザを招待する形で他の開発者に環境を共有することができます。

仮に素の EC2 インスタンスへの Session Manager によるアクセスを IAM ユーザごとに可否設定する場合は各 IAM ポリシーを設定する必要が出てきますが、Cloud9 であればクラウド IDE 上で操作するだけなので非常に管理が楽になります。

また CloudTrail から証跡(いつ、どの IAM ユーザが Cloud9 を利用したか)も確認できるため、万が一の時のためにも安心です。

費用削減の機能がある

開発・検証用の環境は常に利用するわけではないため、夜間停止などの仕組みを入れたくなると思います。

EC2 インスタンスであれば AWS Systems Manager Automation を利用するなど一工夫が必要になります。

一方で Cloud9 にはセッションがなくなった後に一定時間が経過後 EC2 インスタンスを自動で停止する機能があります(停止するまでの時間は選択可能です)。
これにより、Cloud9 を使用する時のみ EC2 インスタンスが起動している状態となるので、費用の無駄を防ぐことができます。

Cloud9 環境セットアップ時の注意点

Docker Compose はインストールされていない

前節で docker がプリインストールされていると言いましたが Docker Compose はインストールされていません。

Docker Compose が必要な場合は、Docker の公式ドキュメント を参考にインストールしましょう。

ディスクサイズの拡張

Cloud9 のデフォルトのディスクサイズ*3は 10GB でその内大半はシステムで利用されており、環境作成時点でユーザが利用可能な領域は 2GB 程度です。
これでは多少大きめの Docker イメージをいくつかプルするだけであっという間にストレージが枯渇してしまいます。

そのため、環境を作成した時点でディスクサイズを拡張しておくことをおすすめします。

docs.aws.amazon.com

まとめ

今回は数値検証の実行環境として Cloud9 を採用するまでの経緯について紹介しました。

Cloud9 は他にもいろいろと活用できそうに思っているので、今後も新しいユースケースが出てきたら紹介していければと思います。

We're hiring!

DIGGLEでは最高のプロダクトを一緒に作ってくれるエンジニアを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

*1:主にポートフォワードの接続が切れるという状況。調査したところ Session Manager のセッションタイムアウト(https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-preferences-timeout.html)が原因ではないことは分かりましたが、根本の原因は分かっていません

*2:弊チームでは CI として Github Actions を利用しています

*3:Cloud9 の環境は EC2 インスタンスで構築されているので、ディスクサイズ = EBSのボリュームサイズを示します

レポート機能の計算結果が正しいことをどう保証するのか考えた話

こんにちは。DIGGLEエンジニアのhondaです。
開発メンバーの中では主にバックエンドを担当することが多いです。

今回はDIGGLEの根幹を支えるレポート機能の数値検証についてお話したいと思います。

DIGGLEのレポート機能

DIGGLEのレポート機能

DIGGLEでは取り込まれたデータを集計し予実管理に役立つレポートを自動で生成する機能があります。 データは単月、累計、部門、科目など様々な角度からリアルタイムに分析が可能になっています。
もちろん今後もDIGGLEの根幹の機能として進化を続けていく予定です。

レポート機能の検証に関する課題

そんな素晴らしいDIGGLEのレポート機能ですが、機能の検証に課題がありました。 自由度の高いレポートを作成できるがゆえに 条件の組み合わせはほぼ無限に存在し、レポートの数値の正しさを保証することが難しい状況でした。 もちろんモデルやコントローラレベルでのテストも存在していて、リリースのたびにきちんQAもおこなうのですが、
「本当にちゃんと計算できているだろうか」
「あれだけテストしたから大丈夫なはずだけど」
といった不安な気持ちが常につきまとっていました。

検証の方法を考える

検証作業の課題を洗い出すと以下のような課題が浮き彫りになりました。

  • 手動でテストをおこなうには時間がかかる
  • 条件の組み合わせが多岐に渡るため網羅的にテストすることが大変
  • 機能追加は随時おこなわれるのでテスト項目の管理に継続的にコストがかかる
  • モデルやコントローラーレベルのテストはあるが、ユーザーが直接見る数値に対する検証が薄い

これらの問題を解決するために検討したのが数値検証の自動化でした。
DIGGLEにはレポートの数値をCSVで出力する機能があります。この機能を使って出力されたCSVファイルに対する検証をおこなえば ユーザーが直接見る数値レベルの検証を自動でおこなえるので、工数削減が期待できそうです。
しかし、どうなっていれば数値が正しいと言えるのかということを考えるとなかなか難しいです。
用意したテストデータに対して期待する値が出力されることをテストするということも考えたのですが、 そのデータ自体の正しさや、データと期待する値のメンテナンスの工数などを考えると現実的ではないと判断しました。

入力データと正しい出力結果を簡単に準備する方法を考えたときに思いついたのが本番のデータを使うことでした。 本番のデータであれば多様なデータと多様なレポート形式、そして我々開発チームの血のにじむような努力によって支えられた確からしい出力結果が存在するのです。 もちろん、本番の出力結果が間違っていればテスト結果も間違えることになるのですが、完璧に正しいと言える出力結果が存在しない以上 本番の出力結果はかなり確からしい出力結果であるためメンテナンスのコストも考慮したときに妥当な選択肢と判断しました。

具体的な検証方法

以下のような手順で検証をおこないます。

  1. 数値検証用環境にリリース前のデータベースと同じデータの入ったデータベースを作成する
  2. リリース済みのブランチでこのデータベースに対して全てのレポートでCSV出力をおこなう
  3. リリース予定のブランチに切り替えてmigrationを走らせる
  4. 再度全てのレポートをCSV出力する
  5. 2と4の結果を比較する

これで既存のレポート機能に関しては様々な条件でデグレードしていないことが検証できます。 新規機能については残念ながら工数を抑えてデータの準備をおこなう方法が思いつきませんでした。(というかそんな方法はないんじゃないか?) しかし、既存分だけのテストを自動化しただけでもかなりの工数を削減できたはずなので今回はこれで良しとしました。

また、この検証をおこなうためには本番相当のデータベースの準備が必要になるのですが、 数値検証用環境は、安全のため外部からはアクセスできないようになっています。 今回は数値検証環境の説明については省略します。次回の記事で紹介予定なのでご期待ください。

データベースに保存された全てのレポートに対してCSV出力をおこなう

ようやくコードの話になります💦
やること自体は単純です。DIGGLEではレポートの条件がjson形式でデータベースに保存されています。 そのjsonをコントローラに対してpostするとcsvが返ってくるのでそれを全てのレポートに対しておこないファイルに書き出すということをおこないます。 数値検証はリリースのたびに実行する性格上Rakeタスクとして実装しました。
但し、通常のRakeタスクでは行わない以下のことをあえてやる必要があります。

  • Rakeタスクからコントローラに直接jsonをpostする
  • Deviseによる認証をスキップする

これだけ書くとやばいことをやってる感がありますがライブラリで用意されている機能を使うだけなのでご安心ください。
但し、テスト環境前提の機能なので本番環境等で使うことはおやめください

言葉で説明するよりコードを見たほうが早いと思うので実際のコードを元に簡略化したコードを載せます。 省略して書きましたが大体以下のような処理を行っています。

# wardenのテストモードを使う
# これでテスト用のヘルパーが使えるようになる
def use_warden_test_mode!
  Warden.test_mode!
  # CSRF保護設定を外す
  ApplicationController.allow_forgery_protection = false
  yield
  # rakeのプロセスに閉じているので変更しても影響は無いはずだが念の為元に戻す
  ApplicationController.allow_forgery_protection = true
  Warden.test_reset!
end

# リクエストするユーザーを偽装する
def next_request_user(user)
  Warden.on_next_request do |proxy|
    # Deviseを使っているモデルのインスタンスからスコープを取得する
    scope = Devise::Mapping.find_scope!(user)
    opts = { event: :authentication, scope: scope }
    # 認証されたユーザーを設定する
    proxy.set_user(user, opts)
  end
end

use_warden_test_mode! do
  companies.each do |company|
    # スキーマを切り替える
    company.exec_in_tenant do
      # 全てのレポートを閲覧できるユーザーでリクエストを投げる
      next_request_user(User.find_by(role: :owner))
      session = ActionDispatch::Integration::Session.new(Rails.application)
      status = session.post(url, params: parameter_hash, as: :json)
      # ファイルに書き出す
      write! session.response.body
    end
  end
end

それでは普段やらないであろう箇所について、簡単にどうやっているのかを補足します。

Rakeからコントローラを直接叩くにはActionDispatch::Integration::Sessionを使います。

api.rubyonrails.org

これでsessionを偽装し、コントローラに直接postすることができます。

session = ActionDispatch::Integration::Session.new(Rails.application)
status = session.post(url, params: parameter_hash, as: :json)
# ファイルに書き出す
write! session.response.body

Deviseの認証をスキップする方法はRakeの認証ミドルウェアであるWardenのtest_mode!を使います。

www.rubydoc.info

名前の通りminitestやrspecなどのテスト環境で使うメソッドですが、今回はRakeタスクで使います。 このメソッドを使うことでWarden.on_next_requestを始めとした様々なテスト用のヘルパーメソッドを使用することができます。 このon_next_requestを使用してリクエストを投げるユーザーを設定することで認証をスキップできます。 scopeやDevise::Mappingが何かという話はDeviseの仕様に踏み込むので今回は説明を割愛します。

# リクエストするユーザーを偽装する
def next_request_user(user)
  Warden.on_next_request do |proxy|
    # Deviseを使っているモデルのインスタンスからスコープを取得する
    scope = Devise::Mapping.find_scope!(user)
    opts = { event: :authentication, scope: scope }
    # 認証されたユーザーを設定する
    proxy.set_user(user, opts)
  end
end

これでRakeタスクを実行すれば、レポートをCSVに出力することができるようになりました🎉
この数値検証の自動化はすぐに威力を発揮し、バグの検出に役立ちました。

次回予告

今回はDIGGLEに数値検証を導入した経緯とその詳細をコードを交えてご紹介しました。 この数値検証を実行している環境がどうなっているかについては次回弊社の優秀なインフラエンジニアがご紹介いたしますのでご期待ください。

We're hiring!

今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

Rails+ApartmentにPostgreSQLのプロシージャを導入して多軸分析の集計速度を向上させた話 その5 ~apartmentへのPR編~

こんにちは。DIGGLEエンジニアのzakkyです。

遂に今回で最終回となります。レポート(多軸分析)の集計処理部分のパフォーマンス向上施策の第5弾です。

前回までの記事の紹介

diggle.engineer diggle.engineer diggle.engineer diggle.engineer

今回のお題

今回はapartmentに対してPRを出すに至ったあれやこれやをご紹介します。

書かないこと

以下の内容は含みませんので予めご了承ください

  • Rails、Apartmentの導入などの基礎的な部分
  • apartmentの説明

どれくらい速くなったのか

前回も記載しましたが、以下が今回改善した集計処理部分のLatency推移となります。 リリース後には低下していることが見て取れるかと思います。

大体3倍速程度には改善することができました。

赤線の辺りで今回の改善版のリリースを行っています

publicスキーマに対する参照があるプロシージャが動かない

ここでまた別の問題が発生しました。

以下のようなプロシージャがあるとします。(サンプルのSQLなので「User.allで取れるじゃん!」とは言わない約束です)

BEBIN
RETURN QUERY SELECT * FROM public.users;
END;

※前後のプロシージャ関連の定型文は省略しています

new_tenantテナントを作って、こちらのプロシージャを見ると…

BEBIN
RETURN QUERY SELECT * FROM new_tenant.users;
END;

と、publicであるべき箇所が、new_tenantとなってしまう問題が発生しました。

今回も、最終的な変更内容については結論に記載していますので、結果だけ知りたい方は最下部を参照ください。

原因を探る

またライブラリ(今度はApartment)を読む時間です。 偶然ですが、以前に余談として書いた部分にヒントがありました。

テナント作成時の流れを追う

重要な部分だけ抜き出してみました。 import_database_schemaはテナント新規作成時に呼び出され、その中のcopy_schema_migrationsでは、pg_dumpをした上でpatch_search_pathというfunctionを呼び出しているようです。

      def import_database_schema
        preserving_search_path do
          clone_pg_schema
          copy_schema_migrations
        end
      end

      def copy_schema_migrations
        pg_migrations_data = patch_search_path(pg_dump_schema_migrations_data)
        Apartment.connection.execute(pg_migrations_data)
      end

      def pg_dump_schema_migrations_data
        with_pg_env { `pg_dump -a --inserts -t #{default_tenant}.schema_migrations -t #{default_tenant}.ar_internal_metadata #{dbname}` }
      end

      def patch_search_path(sql)
        search_path = "SET search_path = \"#{current}\", #{default_tenant};"

        swap_schema_qualifier(sql)
          .split("\n")
          .select { |line| check_input_against_regexps(line, PSQL_DUMP_BLACKLISTED_STATEMENTS).empty? }
          .prepend(search_path)
          .join("\n")
      end

code link

patch_search_pathを追う

一体どんなことをしているのかと開けてみると、pg_dumpで取得した文字列に対してdefault_tenantに当たる部分をreplaceしていることが分かります。

      def patch_search_path(sql)
        search_path = "SET search_path = \"#{current}\", #{default_tenant};"

        swap_schema_qualifier(sql)
          .split("\n")
          .select { |line| check_input_against_regexps(line, PSQL_DUMP_BLACKLISTED_STATEMENTS).empty? }
          .prepend(search_path)
          .join("\n")
      end

      def swap_schema_qualifier(sql)
        sql.gsub(/#{default_tenant}\.\w*/) do |match|
          if Apartment.pg_excluded_names.any? { |name| match.include? name }
            match
          else
            match.gsub("#{default_tenant}.", %("#{current}".))
          end
        end
      end

code link

どうしたら良いか考えてみる

pg_dumpを行った結果を見てみると分かるのですが、create文にもschema名が記述されているため、単純にswap_schema_qualifierを行わないようにすることはできません。 かと言って、「プロシージャ内のロジックだけ置換を行わない」といった事は難しそうです。 プロシージャ内のtable名を動的に生成しEXECUTEで呼び出す方法もありますが、動的にSQL文を生成する分、性能劣化が発生するため今回は使えません。

と思いつつ、ソースを眺めていると、以下のようなコメントアウトされたロジックが目につきました。

      def pg_dump_schema
        # Skip excluded tables? :/
        # excluded_tables =
        #   collect_table_names(Apartment.excluded_models)
        #   .map! {|t| "-T #{t}"}
        #   .join(' ')

        # `pg_dump -s -x -O -n #{default_tenant} #{excluded_tables} #{dbname}`

        with_pg_env { `pg_dump -s -x -O -n #{default_tenant} #{dbname}` }
      end

code link

Apartmentの仕様をもう一度考える

Apartmentとしてどのように動くのが正しいのかを考えてみます

  • excluded_tablesに記載してあるテーブルについては、publicスキーマ上で管理したいテーブルである
  • publicスキーマ以外にもexcluded_tablesが存在するのは、差分が出ないようにするためで、必須ではない

上記であると考えると、コメント部分を復活させて微調整してあげれば解決しそうです。

解決案

以下のようにすれば解決する筈です。

  • テナント追加時に作成するスキーマにexcluded_tablesは存在する必要はないので、pg_dump時の対象から除く
  • swap_schema_qualifierで置換する際に、default_tenant + excluded_tablesの組み合わせだったときは置換させないようにする
    • 事前にpg_dumpの対象からexcluded_tablesに記載のテーブルは除外しているため、プロシージャ内のロジックでのみ出てきます

結論

モンキーパッチを書く

forkしたリポジトリを運用する方法もありますが、今後のバージョンアップに付いていくのが辛いのと、そこまで大きな改修ではないので、今回はモンキーパッチとします。

module PostgresqlSchemaFromSqlAdapterMonkeyPatch
  private

  def pg_dump_schema
    exclude_table = excluded_tables.map! { |t| "-T #{t}" }.join(' ')
    with_pg_env { `pg_dump -s -x -O -n #{default_tenant} #{dbname} #{exclude_table}` }
  end

  def swap_schema_qualifier(sql)
    sql.gsub(/#{default_tenant}\.\w*/) do |match|
      if Apartment.pg_excluded_names.any? { |name| match.include? name }
        match
      elsif excluded_tables.any?(match)
        match
      else
        match.gsub("#{default_tenant}.", %("#{current}".))
      end
    end
  end

  def excluded_tables
    Apartment.excluded_models.map do |m|
      m.constantize.table_name
    end
  end
end

require 'apartment/adapters/postgresql_adapter'
Apartment::Adapters::PostgresqlSchemaFromSqlAdapter.prepend(PostgresqlSchemaFromSqlAdapterMonkeyPatch)

PRを出してみる

今回はそこそこ修正量もあったので、せっかくなのでPull Requestを出してみようと思います。 かくいう私も初めてPull Requestを投げるのでドキドキしながら行ったのですが、以下を参考にしました。

  • Contributingの内容を確認する
    • 大前提ですのでしっかり読んでおきます
  • OSSへPull Requestを投げてみた各種サイト(「oss pull request」とかでググると色々出てきます)
    • 読みやすくまとめてくださっているサイトが色々ありますので、参考にさせていただきました
  • DeepL
    • 英語が得意ではないので、大活躍していただきました

Pull Requestの内容がニッチすぎるせいか、未だに何の反応も無いのが悲しい限りですが、OSSのライブラリに手を加えてPull Requestを投げるまでの一連の流れを体験できたので個人的に良い経験になったな。と、思っています。

最後に

全5回にわたってここまでお付き合いいただきありがとうございました。

次回からは、また別の題材で記事を書きますので(私ではない別のエンジニアになる予定)、ご期待ください。

We're hiring!

DIGGLEでは、必要に応じてライブラリのソースを読み解いて問題解決に挑む開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

Rails+ApartmentにPostgreSQLのプロシージャを導入して多軸分析の集計速度を向上させた話 その4 ~Rails + plv8編~

こんにちは。DIGGLEエンジニアのzakkyです。

今回でレポート(多軸分析)の集計処理部分のパフォーマンス向上施策の第4弾となります。そろそろゴールが見えてきました。

前回までの記事の紹介

diggle.engineer diggle.engineer diggle.engineer

今回のお題

前回はRails+ApartmentでRails Wayに乗っかりつつPL/pgSQLを導入しましたので、今回は同じ流れでRails+ApartmentでRails Wayに乗っかりつつplv8を導入しようとして一筋縄では行かなかったあれやこれやをご紹介します。

書かないこと

以下の内容は含みませんので予めご了承ください

  • Rails、Apartmentの導入などの基礎的な部分
  • PostgreSQLのプロシージャに関する説明
  • plv8に関する説明

どれくらい速くなったのか

前回も記載しましたが、以下が今回改善した集計処理部分のLatency推移となります。 リリース後には低下していることが見て取れるかと思います。

大体3倍速程度には改善することができました。

赤線の辺りで今回の改善版のリリースを行っています

plv8を導入する

いつも通り早速本題に入ります。

前回はPL/pgSQLをRails Wayで管理する所まで説明しましたので、今回はplv8でも同様にRails Wayに乗っかって管理できるようにしてあげようと思います。 最終的な変更内容については結論に記載していますので、結果だけ知りたい方は最下部を参照ください。

localでplv8を動かす

前段の話にはなりますが、そもそもlocalでplv8が入ったPostgreSQLを用意できないと辛い。ということで、Dockerイメージを用意します。

残念ながらPostgreSQLのDockerイメージにはplv8が入っていないので自前で作る必要があります。 基本的にはplv8のドキュメントを見ながらDockerfileを書くだけの簡単なお仕事ですので、そこまで難しい内容ではありません。

参考までに、私が作ったものを下記に置いておきます。

github.com

db:resetでplv8を適用する

plv8はCREATE EXTENSION plv8を実行してあげる必要があるのですが、structure.sqlに上記内容が含まれないため、db:resetを行った際にplv8が存在しないと怒られてしまいます。

Apartmentのドキュメントに沿って修正する(上手くいかない)

以下のREADMEに沿って修正しても、test側でエラーとなってしまいます。 github.com

test側でエラーになる原因を探る

pg_dumpの仕様として、--schemaオプションを付けてるとExtensionはexportされない

要約すると上記の通りとなります。

Apartmentを使っている以上、前回の記事でpublicテナントだけをダンプ対象にする方法として説明した通り、--schemaオプションを付けない道はありません。また、--schemaではなく--exclude-schemaを付ける事でも解消できるとの記載がありますが、schemaがどんどん増えるapartmentとの相性は考えるまでもなく最悪でしょう。

詳しい内容を知りたい方は下記issueが参考になるかと思います。

github.com github.com

ライブラリのソースを追いかける

上記の時点で、かなり詰んでる気がしますが、まだもう少し頑張ります。

ここからは、db:resetの仕様を把握して何とかできる糸口がないかを探していきます。

ActiveRecordのdb:resetの仕様を読み解く

ソースを読み進めていくと、大きな流れとして、db:drop -> db:create -> db:schema:loadの順に動いているようです。

  task reset: [ "db:drop", "db:setup" ]

  namespace :setup do
    task all: ["db:create", :environment, "db:schema:load", :seed]
    ...(略)...
  end

code link

db:createやdb:schema:loadを読み解く

下記に抜粋した通り、db:createやdb:schema:loadのロジックの中で、database全体に対してループしながらcreateやloadを行っていることが分かりました。今回行いたいこととしては、そのロジックの中でextensionの追加をしてあげる必要があります。

先ほどのApartmentのREADMEで指定された方法では、db:createの後にextensionの追加を行っており、この方法ではデフォルト(≒先頭)のデータベースに向けてしか実行できないことが分かります。(つまり2つ目のデータベースであるtest側のデータベースへは適用できずに今回の問題が発生する)

db:create

      def create_all
        old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
        each_local_configuration { |db_config| create(db_config) }
        if old_pool
          ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config)
        end
      end

code link

db:schema:load

    namespace :load do
      ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
        ...(略)...
      end
    end

code link

もっとソースを読む

もう少し読み進めてdb:createのロジックを見てみると、↓のcreateの中でextensionを登録できれば何とかなりそうです。

each_local_configuration { |db_config| create(db_config) }

code link

結論

最終手段ではありますが、以下のようなモンキーパッチを当ててあげることで問題を解決しました。

module PostgreSQLDatabaseTasksMonkeyPatch
  def create(*args)
    super(*args)
    ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS plv8;'
  end
end
ActiveRecord::Tasks::PostgreSQLDatabaseTasks.prepend(PostgreSQLDatabaseTasksMonkeyPatch)

We're hiring!

DIGGLEでは、ライブラリのソースを読んで必要な時にはモンキーパッチも書く開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

Rails+ApartmentにPostgreSQLのプロシージャを導入して多軸分析の集計速度を向上させた話 その3~Rails + PL/pgSQL編~

こんにちは。DIGGLEエンジニアのzakkyです。最近、長女、次女&三女(双子)がそれぞれ高校、中学校へ入学しました。可愛かったです。

いよいよレポート(多軸分析)の集計処理部分のパフォーマンス向上施策の第3弾となります。

前回までの記事の紹介

diggle.engineer diggle.engineer

今回のお題

今回はRails+ApartmentでRails Wayに乗っかりつつPL/pgSQLを導入した際のあれやこれやをご紹介します。plv8については、今回以上に大変だったので別記事として次回お話しする予定となっています。

書かないこと

以下の内容は含みませんので予めご了承ください

  • Rails、Apartmentの導入などの基礎的な部分
  • PostgreSQLのプロシージャに関する説明

どれくらい速くなったのか

前回も記載しましたが、以下が今回改善した集計処理部分のLatency推移となります。 リリース後には低下していることが見て取れるかと思います。

大体3倍速程度には改善することができました。

赤線の辺りで今回の改善版のリリースを行っています

Railsでプロシージャを呼び出すために具体的にどうしたのか

もう3回目ですので、早速本題へ行きます。 Railsからプロシージャを呼び出すためには、本当に色々なハードルがありました。

※プロシージャの導入に至った詳しい経緯を知りたい方は前回までの記事を参照いただけますと幸いです

プロシージャをRails Wayで管理する

大前提となります。今後のメンテナンス性を考え、あくまでRails Wayに乗っかった形でプロシージャを管理していく方法を考えます。

最終的な変更内容については今回やった内容のまとめに記載していますので、結果だけ知りたい方は最下部を参照ください。

プロシージャをRailsの管理下に置く

Railsガイドに記載されている通り、ダンプフォーマットをsqlへ変更することで解決できます。

railsguides.jp

publicテナントだけをダンプ対象にする

Apartmentを使っていてダンプフォーマットをsqlへ変更した場合、db:migrateを実行すると、structure.sqlに全テナント分のテーブルがダンプされてしまう問題が発生します。

こちらについてはダンプする対象をpublicにすることで回避できます。

db:resetを動くように修正する

schema_search_pathが指定されていてダンプフォーマットをsqlへ変更している場合、以下のissueにある通りdb:resetが動きません。解決策としては、issue中に記載のある通り、structure_dump_flagsを指定することで回避できます。

github.com

Apartmentの新規テナント作成時にプロシージャを適用する

以下のREADMEに記載されている通り、use_schemasとuse_sqlを適用することで新規テナント作成時にプロシージャが適用されます。

github.com

余談

上記READMEを読んでいると気になる一文があります。

this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump

ソースコードを追いかけると、確かにstructure.sqlを使わずに、pg_dumpをApartmentの中で実行しています。(次回以降にお話ししますが、ここの辺りで問題が発生してapartmentに対してPRを出すことになりました)

db:migrateでプロシージャを登録する

ここでようやくプロシージャをRailsで登録できるようになります。

シンプルに以下で実行可能です。

  def change
    execute <<~SQL
      CREATE PROCEDURE sample_procedure()
      AS $function$

      BEGIN
      -- 処理を書く
      END;

      $function$ LANGUAGE plpgsql;      
    SQL
  end

プロシージャの管理方針を考える

今回作成するプロシージャは弊社の根幹のロジックであり、今後のレポート拡充などやパフォーマンス改善で更に手を加えることが既に見えています。そして、改修の都度migrationを書いていては、どこにどのプロシージャがあるかを確認できず、管理が非常に煩雑になる未来が見えます。

上記などを考慮した結果、弊社ではプロシージャのソースコードを管理しやすいようにプロシージャ用のディレクトリを作成して、その中でプロシージャを管理するようにしました。

このようにmigrationを実行するイメージです。

def change
  execute File.read('db/procedure/sample_procedure.sql')
end

ただ、そうすると、migrationを書き忘れたりlocalのdb:migrateの実行タイミング次第では登録されるプロシージャの内容が個々人で変わったりなど、色々な危険があるのではないかと気付き、最終的には、db:migrateを実行する度にプロシージャの最新ソースを登録するようなrakeを別途作成することとしました。

lib/tasks/procedure.rake
def install_all_procedures
  Dir.glob(Rails.root.join('db/procedure/*.*')).each do |file|
    ActiveRecord::Base.connection.execute File.read(file)
  end
end

task 'db:procedure' => :environment do
  puts 'Re-install procedures..'
  install_all_procedures

  Apartment::Tenant.each do
    puts "At Tenant: #{Apartment::Tenant.current}"
    install_all_procedures
  end

  if ActiveRecord::Base.dump_schema_after_migration
    Rake::Task['db:schema:dump'].invoke
  end
end

Rake::Task['apartment:migrate'].enhance do
  Rake::Task['db:procedure'].invoke
end
rakeの中身の説明

簡単にrakeファイルの仕様を解説をします。

まず、Apartmentの仕様として、db:migrateを実行すると内部でapartment:migrateが実行されます。 今回の場合、apartmentのmigrationが終わった後にプロシージャの登録を行いたいので、Rake::Task['apartment:migrate'].enhanceとしています。

install_all_procedures周りの処理は全テナント(publicスキーマ含む)に対してdb/procedure/配下の全ファイルを実行していくだけなので割愛するとして、Rake::Task['db:schema:dump'].invokeについては、上記のプロシージャ登録をstructure.sqlの取得タイミングの後に実行しているため、再度dumpしてあげる必要があり、このような記述となっています。

結果として、db:migrateを実行するとdb/procedure/配下の全ファイルが常に実行されるようなrakeファイルとなっています。

つまり、db:migrateを行うたびに毎回procedure配下のファイルが実行されてしまうため、各プロシージャの先頭ではDROP-CREATEの形での記述をしてあげる必要があります。

DROP FUNCTION IF EXISTS sample_function;

CREATE FUNCTION sample_function()
--以下略

登録したプロシージャを呼び出す

プロシージャの呼び出し自体は非常に簡単です。

# procedure
ActiveRecord::Base.connection.execute('CALL sample_procedure()')

# function
ActiveRecord::Base.connection.execute('SELECT sample_function()')

※パラメータを渡す際にはsanitize_sqlなどを行う必要がありますので、ご注意ください。

今回やった内容のまとめ

以下のような形となりました。

application.rb
  class Application < Rails::Application
    config.active_record.schema_format = :sql
    config.active_record.dump_schemas = 'public'
    # MEMO: db:resetが動くように調整
    # NOTE: https://github.com/rails/rails/issues/38695
    ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = ['--clean', '--if-exists']
  end
apartment.rb
Apartment.configure do |config|
  config.use_schemas = true
  config.use_sql = true
end

We're hiring!

DIGGLEでは、必要に応じてライブラリのソースを読み解いて問題解決に挑む開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net

Rails+ApartmentにPostgreSQLのプロシージャを導入して多軸分析の集計速度を向上させた話 その2 ~プロシージャ導入編~

こんにちは。DIGGLEエンジニアのzakkyです。最近、長男が1歳になり、長女が中学卒業、二女&三女が小学校卒業とイベントが盛りだくさんでした。毎日家の中がにぎやかです。

さて、今回も前回から始まったレポート(多軸分析)の集計処理部分のパフォーマンス向上施策の第2弾となります。

前回までの記事の紹介

diggle.engineer

今回のお題

今回はPostgreSQLのプロシージャ導入時のあれやこれやをご紹介します。

書かないこと

以下の内容は含みませんので予めご了承ください

  • プロシージャの書き方などの基礎的な部分
  • PostgreSQLのパラメータを使ったチューニング方法

どれくらい速くなったのか

前回も記載しましたが、以下が今回改善した集計処理部分のLatency推移となります。 リリース後には低下していることが見て取れるかと思います。

大体3倍速程度には改善することができました。

f:id:zakky21:20220224115950p:plain
赤線の辺りで今回の改善版のリリースを行っています

実装方針を決める

前回のおさらいとなりますが、前回は「単純にSQLを発行するだけのような部分はPL/pgSQLで行い、計算式の値の算出のような複雑な処理はplv8を使うハイブリッド方式を採用する」所まで決定しました。

今回は、具体的にどの部分をPL/pgSQLで書いて、どの部分をplv8にするかを決めて実装していきます。

PL/pgSQLとplv8に振り分ける

実際に行う処理をいくつかに分解し、複雑度によってPL/pgSQLとplv8に振り分けていきます。

計算処理の流れを確認する

こちらも前回のおさらいとなりますが、改善箇所は「登録されているデータをレポート用に加工する」箇所の高速化となります。弊社のサービス上では大きく以下のような流れで処理が構成されています。

  1. 計算式のパース
  2. データの集計&集約
  3. 計算式部分の計算

それぞれの処理の複雑度などを考えて振り分けを行っていきます。

計算式のパース

jsのライブラリであるpegjsで生成したjsを使ってパース処理を行っていますので、複雑度云々ではなく、jsが実行可能な環境が必要となります。

plv8がnodeベースの言語となりますので、plv8で実装します。

データの集計&集約

単純にSQLによる集約ができれば良い部分となります。

シンプルにSQLを書けば良いので、PL/pgSQLで実装します。

計算式部分の計算

一番複雑な部分になります。それぞれの値の相互関係や四則演算などを表現できないといけません。また、今後計算式機能の拡充をしようとした際のメンテナンス性も重要になります。

PL/pgSQLでは荷が重いので、自由度が高いplv8で実装します。

最終的な振り分け結果

上述までの検討の結果、以下の様に振り分けを行いました。

  1. 計算式のパース ⇒ plv8
  2. データの集計&集約 ⇒ PL/pgSQL
  3. 計算式部分の計算 ⇒ plv8

プロシージャへの置き換え

方針も決まりましたので、それぞれの処理をプロシージャに置き換えて高速化を行っていきます。

計算式のパース

前述の通り、jsのライブラリであるpegjsで生成したjsを使っていますが、元々はrails上でjsを実行していました。そのため、お世辞にも処理が速いとは言えず、都度パースするのではなく、DB上にパース後の内容を保存しておくなど、極力パース処理が走らないような工夫を行っていました。

それが今回、pegjsで生成したjsをnodeベースであるplv8上で動かすことでかなり速度が向上しました。そのため、「パース処理の仕様を変えたから、全ての計算式をパースし直したい」のような時にも以下の様にSQL上からも呼び出すことで、今までは数分単位で掛かっていたものが1~2秒で終わるようになりました。

-- formula_tableの全ての計算式(formula)を再度パースしてparsed_jsonに格納する
UPDATE formula_table
SET parsed_json = parse_formula(formula);

※実際には計算式変更後の初回時のみパース⇒DBへ保存する仕様のため、最初の画像のLatencyに対する恩恵はほぼほぼ得られていません。

データの集計&集約の高速化

以前は同様の内容をRails側で取得⇒メモリ上に展開していたので、RDBとのI/Oや取得したデータの展開などのオーバーヘッドが掛かっていました。 今回はPL/pgSQLでSELECT-INSERTを用いてTEMPORARY TABLEへそのまま集計&集約しつつ投入する仕組みにしたため、オーバーヘッドを大幅にカットすることができ、大体1秒程度あればデータの集計ができるようになりました。

計算式部分の計算

今回の目玉です。

元々Railsで記載されていたものをplv8(node)へ置き換える作業となるのですが、試行錯誤の連続でした。

TEMPテーブルを活用する(失敗)

最初に実装した際には、計算式で参照する値がある度に、TEMPテーブルから都度参照する仕組みとしました。

理由としては、TEMPテーブルはオンメモリに展開されている筈で、TEMPテーブルへのSELECTは実体としてはRails上のMapなどに対する参照とコストが大差なく、そこまで大きくオーバーヘッドは掛からないだろう。という推測からでした。

しかし残念ながら結果としては、Rails時より速度低下する事態となってしまいました。

後で調べてみて分かったのですが、以下の記述があるように、どうやらTEMPテーブルもファイルへ保存しているようです。TEMPテーブルから取得したからいくらSELECTしても大丈夫!という訳ではなく、どこかしらでHDDへのI/Oが走ってしまい性能劣化へ繋がったのではないかと推測しています。

しかし一時的なリレーションでは、ファイル名はtBBB_FFFという形になります。

www.postgresql.jp

PostgreSQLのパラメータをチューニングする(失敗)

私よりDBに詳しい方が色々なサイトで纏めてくださっているので詳細は割愛しますが、以下のようなパラメータを調整してみました。

  • shared_buffer
  • temp_buffers
  • work_mem

色々な値を入れて試してみたのですが改善せず、今回は時間の都合で断念となりました😢

www.postgresql.jp

考察:plv8でSQL発行するのは良くないのではないか

上記の調査をしている中で気づいたことなのですが、体感でplv8では各種SQLの発行が遅いように感じました。 特に更新系の処理が顕著で、明らかに遅いと感じられる状況となりました。

後になって思い返してみると、計算式の計算が終わる度にTEMPテーブルへ計算結果をUPDATEする仕組みがあったため、UPDATEのコストが大きかった可能性もあります。(他の計算式から参照される可能性があるため、集計結果は保存しておく必要があるのです)

また、今回plv8で記述した内容を、頑張ってPL/pgSQLで書くようにすれば違った結果になった可能性があります。(ただし、メンテナンス性がかなり落ちるため、今回は採用しませんでした)

最終案:オンメモリに全展開する

最終的には、Railsでやっていたことと同じく、オンメモリに必要な情報を全て展開してから処理を行う。という形となりました。当たり前ですが、RailsでやっていたことをRDB上で実現しましたので速度の向上が見られました。

大まかな処理の流れとしては以下のようになります。

  • TEMPテーブルに対してSELECTを行い、集計結果をメモリ(Map)に展開する
  • 計算式で参照する値がある場合は、メモリ(Map)から参照する
  • 計算式の計算が終わったら、メモリ(Map)に結果を格納する

上記によって懸念されるのが、RDBのメモリが枯渇しないか。という点かと思います。

この問題に関しては、弊社では本番同等の環境&データを用いたパフォーマンス試験を実施する仕組みがありますので、リリース前にしっかりと本番想定の性能試験をすることが可能となっています。

そして、性能試験の結果としてはメモリ枯渇は発生せず問題なし。結果的にこちらが採用となりました。

まとめ

最終的に時間の都合で納得のいく形までは持っていけませんでしたが、3倍速程度には速くできたので、まぁ及第点かな。と。

ただ、計算式の計算部分のパフォーマンス改善や、PostgreSQLのパラメータチューニングなど、まだまだ改善の余地が残っていることは事実ですので、日を改めて再チャレンジをする予定です。

次回予告

「Rails+ApartmentでどうやってRails Wayに乗っかりつつプロシージャを管理するのか」という所を書きたいと思います。そちら側の方が個人的には大変だったので、ご期待いただければと思います。

本当は今回書きたかったのですが、長くなってしまった結果、泣く泣く分割となりましたので、来月までお待ちいただけますと幸いです。

We're hiring!

今あるものを更により良くするための方法を、我々と一緒に模索してくれる開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

open.talentio.com

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net