STORES でエンジニアをしている片桐です。
先日、「Goで作られたシステムをRuby on Railsに移植しています」という記事を投稿させていただきました。
ベース部分の実装について別ブログで紹介したいと書かせていただきましたが、今回はその中から「GraphQLの移行基盤としてのGraphQL Stitchingの導入」について紹介させていただきます。
GraphQL Stitching の導入
GraphQL Stitchingの実現には、すでに弊社内で採用実績のある graphql-stitching
gemを利用します。
導入後、graphql-stitching
gemのREADME.md
のQuick Startを参考に、graphql-ruby
で作成されるgraphql_controller
のschema実行部分を書き換えてみます。
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController def execute # (略) - result = Schema.execute(query, variables:, context:, operation_name:) + client = ::GraphQL::Stitching::Client.new(locations: { + other: { + # 移植元システムの提供するGraphQLのschema。移植先システムのリポジトリにあらかじめ含めておく。 + schema: ::GraphQL::Schema.from_definition(::Rails.root.join("app/models/other_system/schema.graphql")), + executable: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), + }, + main: { + schema: ::Schema, + }, + }) + result = client.execute(query, variables:, context:, operation_name:) render json: result rescue StandardError => e raise e unless Rails.env.development? handle_error_in_development(e) end # (略) end
Production Readyな状態にする
上記の変更で一応動作はするようになるのですが、実際に本番で利用するためにはさらに追加で設定すべき部分があります。また、今回の移植特有の設定も追加で必要です。この記事では今回の移植において行なった設定や変更をご紹介します。
本番環境と開発環境でStitchingClientの生成方法を切り替える
graphql-stitchingのドキュメントを読むと、本番環境ではschemaのマージ(composition)はせず、事前にマージ済みのschemaからStichingClientを生成すべきと記載されています。
このドキュメントを素直に実装すると、以下のようになります。
lib/tasks/dump_schema.rake
マージ済みのschemaを生成するrake task
task dump_schema: :environment do client = GraphQL::Stitching::Client.new(locations: { other: { schema: GraphQL::Schema.from_definition(Rails.root.join("app/models/other_system/schema.graphql")), executable: GraphQL::Stitching::HttpExecutable.new(url: Rails.configuration.x.other_graphql_endpoint), }, main: { schema: Schema, }, }) Rails.root.join("app/models/other_system/schema.graphql").write(client.supergraph.to_definition) end
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController def execute # (略) - client = GraphQL::Stitching::Client.new(locations: { - other: { - schema: ::GraphQL::Schema.from_definition(::Rails.root.join("app/models/other_system/schema.graphql")), - executable: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), - }, - main: { - schema: ::Schema, - }, - }) + client = if ::Rails.env.development? + ::GraphQL::Stitching::Client.new(locations: { + other: { + schema: ::GraphQL::Schema.from_definition(::Rails.root.join("app/models/other_system/schema.graphql")), + executable: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), + }, + main: { + schema: ::Schema, + }, + }) + else + ::GraphQL::Stitching::Client.from_definition(::GraphQL::Schema.from_definition("app/graphql/schema.graphql"), executables: { + other: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), + main: ::Schema, + }) + end result = client.execute(query, variables:, context:, operation_name:) render json: result rescue StandardError => e raise e unless Rails.env.development? handle_error_in_development(e) end # (略) end
しかし、さすがにこれは好ましくありません。同じような定義が何度も重複してしまっています。そこでGraphQL::Stitching::Client
を継承したクラスを定義してそこにまとめることにしました。
まずは、マージされたschemaの配置場所を定数として定義します。
※ なぜ「GraphQL::Stitching::Client
を継承したクラス」のクラスメソッドとして定義しないのか? と思うかもしれませんが、後ほど説明するので一旦気にしないでください。
app/graphql/supergraph_path.rb
SupergraphPath = ::Pathname.new(__dir__).join("schema.graphql")
次に、GraphQL::Stitching::Client
を継承したクラスを定義してみます。
app/graphql/stitching_client.rb
class StitchingClient < ::GraphQL::Stitching::Client SUPERGRAPH_SCHEMA = ::GraphQL::Schema.from_definition(::SupergraphPath.to_s) private_constant :SUPERGRAPH_SCHEMA class << self def load_schema_from_locations? = ::Rails.env.development? def locations { other: { schema: ::GraphQL::Schema.from_definition(::Rails.root.join("app/models/other_system/schema.graphql").to_s), }, main: { schema: ::Schema } } end def composer_options = {} def build_supergraph = ::GraphQL::Stitching::Composer.new(**composer_options).perform(locations) end def initialize(executables:) if self.class.load_schema_from_locations? locations = self.class.locations.to_h do |name, definition| [name, {**definition, executable: executables.fetch(name)}] end super(locations:, composer_options: self.class.composer_options) else supergraph = ::GraphQL::Stitching::Supergraph.from_definition(SUPERGRAPH_SCHEMA, executables:) super(supergraph:) end end end
これで、controller側からは、以下のように使用できます。
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController def execute # (略) - client = if ::Rails.env.development? - ::GraphQL::Stitching::Client.new(locations: { - other: { - schema: ::GraphQL::Schema.from_definition(::Rails.root.join("app/models/other_system/schema.graphql")), - executable: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), - }, - main: { - schema: ::Schema, - }, - }) - else - ::GraphQL::Stitching::Client.from_definition(::GraphQL::Schema.from_definition("app/graphql/schema.graphql"), executables: { - other: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), - main: ::Schema, - }) - end + client = ::StitchingClient.new(executables: { + other: ::GraphQL::Stitching::HttpExecutable.new(url: ::Rails.configuration.x.other_graphql_endpoint), + main: ::Schema + }) result = client.execute(query, variables:, context:, operation_name:) render json: result rescue StandardError => e raise e unless Rails.env.development? handle_error_in_development(e) end # (略) end
rake taskも以下のようにかけます。
lib/tasks/dump_schema.rake
task dump_schema: :environment do - client = GraphQL::Stitching::Client.new(locations: { - other: { - schema: GraphQL::Schema.from_definition(Rails.root.join("app/models/other_system/schema.graphql")), - executable: GraphQL::Stitching::HttpExecutable.new(url: Rails.configuration.x.other_graphql_endpoint), - }, - main: { - schema: Schema, - }, - }) - Rails.root.join("app/models/other_system/schema.graphql").write(client.supergraph.to_definition) + Rails.root.join("app/models/other_system/schema.graphql").write(StitchingClient.build_supergraph.to_definition) end
ちなみにrake taskについてはgraphql
(GraphQL Ruby)の方がGraphQL::RakeTask
というschemaをファイルに書き出すためのtask群を用意してくれています。ですので、統合されたスキーマを書き出す処理もこれに乗せるのが良いでしょう。
lib/tasks/graphql.rake
require "graphql/rake_task" require_relative "../../app/graphql/supergraph_path" GraphQL::RakeTask.new( directory: SupergraphPath.dirname, schema_name: "Schema", load_schema: ->(_) { StitchingClient.build_supergraph.schema } )
ちなみにこのrake taskが読み込まれるタイミングではまだRailsは初期化されていません。そのため、クラスのオートロードは効きませんし、Railsの機能に依存した部分を定義に含んだファイルは読み込みエラーになってしまいます1。ここに掲載したStitchingClient
はRailsの機能には依存していないので、単にrequire_relative
で読み込むだけで問題ありません。しかし、このrake task定義のためだけにこれらの制約が課されるのは好ましくありません。そのため、このtaskの定義のタイミングで必要なSupergraphPath
だけを個別のファイルに切り出し、それだけをrequire_relative
で読み込むようにしました。
部分的なエラーの取り扱い
GraphQLは、仕様上「部分的なエラー」というものをサポートしています。レスポンスでerrors
にエラーを返しつつ、エラーになっていない部分をdata
に返すことができます。そのため、GraphQL Stitchingも「stitching先でエラーが発生した場合やそもそもstitching先に接続できなかった場合、このサーバーで解決できた部分と発生したエラーをマージして反す」という挙動になっています。
しかし、移植元システムは「部分的なエラー」という状態をサポートしていません。移植元システムは弊社でGraphQLを採用し始めた初期に開発されたサーバーであり、当時は社内のGraphQLに対する知見が不足していました。部分的なエラーを標準的なerrors
フィールドだけで扱おうとすると難易度が高い点は当時から各所で指摘されており、解決策もデファクトと言えるものはなく、複数の方法が提案されていました。そのため、当時は部分的なエラーのサポートは見送り、すべて成功/すべてエラーのどちらかにする方針で実装を進めました。
ちなみに後発のサーバーでは、その後の知見の蓄積に伴い部分的なエラーをサポートするようになっています。この辺りについては以前にこのブログで紹介しているの興味があればぜひご一読ください。
話を戻します。
弊社全体としては部分的なエラーをサポート方針になってきていますが、これを今回の移植に適用すると明確にAPIの挙動が変わってしまいます。ただでさえ一定リスクのある移行作業を行っている中、APIの挙動をこのタイミングで合わせて変更するのはさすがに避けるべきです。そのため、以下のような場合にはGraphQL全体をエラーにする必要があります。
- stitching先でエラーが発生した場合。
- そもそもstitching先に接続できなかった場合。
「そもそもstitching先に接続できなかった場合」はいわゆる通信エラーなのでhttp 500など2の、HTTPレイヤーのエラーとしてしまえば問題ありません。
一方、stitching先でエラーが発生した場合は、そのエラーをレスポンスとして返す必要があります。デフォルトでは、このとき移植先サーバー側で解決できた結果も一緒に返ってしまいますが、これをstitching先のエラーだけを返すようにする必要があります。
これを実現するために、GraphQL::Stitching::HttpExecutable
を継承したクラスを定義してそこで挙動をカスタマイズしていきます。
まずが、課題となっていた2パターンでエラーを発生させるようにしました。
app/graphql/custom_stitching_http_executable.rb
class CustomStitchingHttpExecutable < ::GraphQL::Stitching::HttpExecutable class HTTPError < ::StandardError attr_reader :response def initialize(response) super("code: #{response.code} body: #{response.body}") @response = response end end class RemoteGraphQLError < ::StandardError attr_reader :remote_errors def initialize(remote_errors) super("Remote GraphQL returns errors: #{remote_errors.to_json}") @remote_errors = remote_errors end end def call(...) super.tap do |response_json| # GraphQL Stitchingは通信先がエラーを返した場合、bongoで成功した部分と失敗した部分をマージして、 # 「部分的に成功、部分的に失敗」という形のレスポンスを返す # しかし、majaではそのような部分的なエラーは存在しなかったので、 # それに合わせて一部でもエラーが起きたら全体をエラーとして落とす raise RemoteGraphQLError, response_json["errors"] if response_json["errors"].present? end end def send(...) super.tap do |response| # GraphQL StitchingはOK以外のresponsenの場合でもGraphQLレスポンスとして解釈してマージしようとする # しかし、OK以外のstatus code以外は想定外なので、例外を出して処理を止める raise HTTPError, response unless response.is_a?(::Net::HTTPSuccess) end end end
しかし、これだけですとcontroller側GraphQL::Stitching::Client#execute
では例外が発生しません。GraphQL Stitchingでは、デフォルトではエラーが発生しても例外を発生させないようになっているためです。
この挙動に対する対応は、今回は部分的なエラーをサポートしないこともあり特に難しいことはありません。エラー発生時に例外を発生させるように設定するだけです。
app/graphql/stitching_client.rb
class StitchingClient < ::GraphQL::Stitching::Client # (略) def initialize(executables:) if self.class.load_schema_from_locations? locations = self.class.locations.to_h do |name, definition| [name, {**definition, executable: executables.fetch(name)}] end super(locations:, composer_options: self.class.composer_options) else supergraph = ::GraphQL::Stitching::Supergraph.from_definition(SUPERGRAPH_SCHEMA, executables:) super(supergraph:) end + + on_error { |_request, error| raise error } end end
最後に、controller側に「stitching先でエラーが発生した場合」の処理を追加します。今回の変更では、この場合に「CustomStitchingHttpExecutable::RemoteGraphQLError
」を発生させるようにしているので、このエラーをハンドリングします。
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController def execute # (略) result = client.execute(query, variables:, context:, operation_name:) render json: result + rescue ::CustomStitchingHttpExecutable::RemoteGraphQLError => e + render json: {errors: e.remote_errors} rescue StandardError => e # (略) end # (略) end
ログ出力
internal server error相当のエラー
実際に本番環境でシステムを運用していく上で、ログは非常に重要です。GraphQL Rubyでは、resolverでエラーが発生した場合はそのままcontrollerからその例外が投げられ500エラーになります。そのため、既存の仕組みによりログ出力やエラートラッカーへの通知などが行われます。
一方GraphQL Stitchingでは、上のセクションでも軽く触れましたがデフォルトではエラーが発生してもGraphQL::Stitching::Client#execute
は例外を出しません。例外をハンドリングするには、#on_error
にエラーハンドラーを設定する必要があります。
今回はすでにon_error
を追加しているので特に追加の対応は不要です。
これで GraphQL Ruby と同様にcontrollerから例外が投げられるようになっており、既存の仕組みでエラーが処理されるようになります。
bad request相当のエラー
bad request相当のエラーは、基本的にクライアント側のリクエストに問題があり、サーバー側には非が無いもの(= 準正常系)になります。しかしながら、ユーザー視点で見ればこれもエラーであり、原因や解決策についてお問い合わせをいただく場合があります。また、実装の不具合により本来有効なリクエストを誤って不正と判断してしまっていることも往々にしてあります。このような場合に、エラーが発生したという事実がログに残っていないと問題の調査・検知が困難になってしまいます。
今回は部分的なエラーをサポートしないこともあり、シンプルに「レスポンスのerrors
に値が存在するとき」に追加のログを出すようにしました。
app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController def execute # (略) result = client.execute(query, variables:, context:, operation_name:) + + if json["errors"].present? + logger.warn "client error occurred on #{request.path}: #{json.fetch("errors")}" + end + render json: result rescue ::CustomStitchingHttpExecutable::RemoteGraphQLError => e + logger.warn "client error occurred on #{request.path}: #{e.remote_errors}" render json: {errors: e.remote_errors} rescue StandardError => e # (略) end # (略) end
GraphQL stitching により生成されるquery
GraphQL Stitching は、要求されたqueryにを基にstitching先に投げるべきqueryを生成します。このとき、要求されたfieldがどのサーバーで解決されるかは下記のように複雑に入んでいる場合があります。この場合、stitching先に投げられるqueryがどのようになるかは自明ではありません。もし問題が発生した場合の調査を考えると、どのようなqueryがstitching先に投げられたかもログに出ていると便利です。
query($itemId: UUID!) { item(itemId: $itemId) { # ここはstitching先で解決される label compexValue # ここはこのサーバーで解決される price employee { # ここはこのサーバーで解決される employeeId displayName shop { # ここはstitching先で解決される shopId displayName } } } }
そこで、stitching先にリクエストを投げる処理に以下のようにログを追加しました。
app/graphql/custom_stitching_http_executable.rb
class CustomStitchingHttpExecutable < ::GraphQL::Stitching::HttpExecutable # (略) - def send(...) + def send(request, document, variables) + ::Rails.logger.info("GraphQL Stitching request. url: #{@url}, query: #{document}") + # (略) end end
ちなみにリクエストパラメータは variables
に分離されるので、query文字列(上記のコードではdocument
引数に入っています)にセンシティブな情報が含まれる心配はありません。もしより情報を充実させるために variables
や HTTP Header
の情報を出力させる場合は、ActiveSupport::ParameterFilter
などを用いてセンシティブな情報がログに出力されないようにしましょう。
HTTP Headerをproxyする
HTTPのリクエストにおいては、HTTP Headerも重要な情報です。特に認証などの部分に大きく関わってきています。今回のようにあるサーバーの裏に別のサーバーがある構成の場合、認証は一番クライアントに近い部分でのみ行い、内部の通信は内部用の認証を用いたり、internalな通信を用いて認証を不要にするなどの方法を取ることが多いです。しかし、今回裏側にいるサーバー(= 移植元のサーバー)は元々クライアントから直接リクエストを受け付けていたサーバーなので、すでに認証の仕組みが入っています。
今回の目的は移植期間中に双方のGraphQLをシームレスにマージして提供することであり、この構成は一時的なものです。また、双方のGraphQL APIは共に同じ認証の形式を採用しています。そのため、移植元サーバーとのやり取りに専用の認証機構などは導入せず、シンプルに移植先サーバーへのリクエスト時の認証情報を、そのまま移植元サーバーへproxyすることにしました。
双方のサーバーの認証は、HTTPのAuthorization
headerによるBearer token認証です。現状の挙動を可能な限り踏襲することを考えると、そのほかのHTTP Headerも含めて丸ごとproxyするのが良いかなと考えました。
早速実装に取り掛かりたいのですが、皆さんはRuby on Railsでrequest headerをすべて取得するにはどうすれば良いかご存じでしょうか?request.headers
を思い浮かべる方が多いのかなと思うのですが、実はこれでは不適切です。なぜかというと、このメソッドが返すのはRack Environment
というもので、request headerの一覧ではないからです。このHashは、request header以外の情報も多く含んでいる上に、request headerのキー名もリクエスト時の値とは異なるものに変換されてしまっています。そのため、以下のようなロジックでhttp headerを抜き出して変換する必要があります。
requested_headers = request .headers .filter { |k, _| k.starts_with?("HTTP_") } .to_h .transform_keys { it.sub("HTTP_", "").titleize.tr(" ", "-") }
また、HTTP headerの中にはHost
というリクエスト先のhost名を設定するHeaderがあります。これはさすがに移植先へのリクエスト時の値では不適切なので、引き継がないようにしました。
はまりどころ
これでstitching先と通信できるかと思ったのですが、なぜかエラーが発生してしまいました。確認してみると移植元サーバーからのレスポンスがbinary形式となっていました。1つ1つHTTP headerを消したり戻したりしながらデバッグを進めたところ、これは Accept-Encoding
ヘッダーが原因で発生していることが判明しました。Accept-Encoding: gzip, deflate, br, zstd
のような値が設定されていたために、gzipで圧縮されたデータ = binaryデータが返ってきてしまっていました。
冷静に考え直すと、Accept系のヘッダーは「clientと、clientと直接通信するサーバー(移植先サーバー)間の通信に関する指定」であって、移植先サーバーとstitching先のサーバーとの通信には必ずしもそのまま適用すべきものではありません。そのため、この問題に関しては「Accept
で始まるキーのHTTP headerを除外する」ことで対処しました。
他にも除外すべきHTTP headerはあるのかもしれないですが、この設定で問題なく動くことが確認できたため、今回の移植ではこの設定でproxyさせることにしました。
補足: Net::HTTP
におけるgzip圧縮の扱いについて
ここまでの内容を見ると、rubyのNet::HTTP
がgzip圧縮をサポートしていないかのように見えてしまいますが、実はNet::HTTP
は普通にgzip圧縮をサポートしています。では何が問題だったのかというと、「Accept-Encoding
を明示的に指定してしまっていた」ことです。
Net::HTTP
は、デフォルトでAccept-Encoding
headerが設定され、@decode_content
というフラグにtrue
が設定されるようになっています。
しかし、Accept-Encoding
が外部から指定された場合は、@decode_content
がtrue
にならないようになっています。
- https://github.com/ruby/net-http/blob/d8fd39c589279b1aaec85a7c8de9b3e199c72efe/lib/net/http/generic_request.rb#L41-L43
- https://github.com/ruby/net-http/blob/d8fd39c589279b1aaec85a7c8de9b3e199c72efe/lib/net/http/generic_request.rb#L110
今回の実装は「Accept-Encoding
が外部から指定された場合」に当てはまってしまっており、Net::HTTP
のgzipのdecode処理が無効になってしまっていたのでした。
keep-alive
を利用してHTTP connectionを使い回す
今回の用途では、特に移植が進んでいない時期には非常に高い頻度でstitchingリクエストが実行されます。その度にHTTP connectionを確立する処理を行うのは非効率です。これを改善するための仕組みとして、HTTPにはkeep-alive
という「一度はったconnectionを複数のリクエストで使い回す仕組み」があります。rubyでは、net-http-persistent
というgemでkeep-aliveを簡単に実現可能なので、これを導入します。
変更を入れるファイルはCustomStitchingHttpExecutable
です。
app/graphql/custom_stitching_http_executable.rb
class CustomStitchingHttpExecutable < ::GraphQL::Stitching::HttpExecutable # (略) + CONNECTION = ::Net::HTTP::Persistent.new + private_constant :CONNECTION + + class << self + def shutdown = CONNECTION.shutdown + end # (略) def send(request, document, variables) # (略) + parsed_url = ::URI.parse(@url) + + net_http_request = ::Net::HTTP::Post.new(parsed_url.path, @headers).tap do |req| + req.body = {query: document, variables:}.to_json + end + - super.tap do |response| + CONNECTION.request(parsed_url, net_http_request).tap do |response| # GraphQL StitchingはOK以外のresponsenの場合でもGraphQLレスポンスとして解釈してマージしようとする # しかし、OK以外のstatus code以外は想定外なので、例外を出して処理を止める raise HTTPError, response unless response.is_a?(::Net::HTTPSuccess) end end end
ここで.shutdown
というメソッドを定義していますが、これがどこから呼ばれるのかと疑問に思った方もいるでしょう。このメソッドは、Rackサーバーであるpitchfork
のbefore_fork
で呼ばれます。
config/pitchform.rb
before_fork do |server| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! + + CustomStitchingHttpExecutable::HttpExecutable.shutdown end
pitchforkは「reforking」特殊な戦略を採用しており、pitchforkで動くアプリケーションは「fork安全」である必要があります3。「fork」という処理においては、fork時点のメモリ状態がそのままfork先にコピーされます4。そのため、fork時点で各種コネクションやファイルディスクリプタなどが開かれていた場合、fork元とfork先が同じものをさしている状態となってしまいます。これらのリソースは複数のプロセスに同じものが割り当てられることを想定していないので、この状態は深刻な不具合につながります。通常のfork serverであれば、これらのリソースが開かれる前にforkが行われるので問題ありません。しかしpitchforkでは、サーバーの起動後、一定数のリクエストを受け付けた後にforkを行う「reforking」が発生します。そのため、fork前に開かれているコネクションやファイルディスクリプタをcloseし、fork安全な状態にする必要があるのです。
この辺りの詳細は、pitchforkの作者自身が解説されています。この辺りの実装を行う少し前に、ちょうどこの解説を読んでいたため、before_fork
でのconnectionのclose(.shutdown
)が必要なことに思い至ることができました。
この解説は非常に面白い内容となっているので、ぜひ読んでみることをお勧めします。
日本語訳
接続先の切り替え
ここまで紹介させていただいたものを中心に、その他細かい調整をGraphQL stitchingの設定を経て、移植先のサーバーは移植元と同等のサーバーとして振る舞える状態になりました。最後に、移植元サーバーを参照していた各プロダクト群のリクエスト先を移植先サーバーに切り替えていきます。
ここについては特段目あたらしい部分はなく、シンプルにリクエスト数や影響数の少ないところから順次リクエスト先のURLを切り替えていきました。この段階では特段大きなトラブルもなく無事すべてのリクエストの切り替えが完了しました。
まとめ
3記事にわたってサービス統合の詳細についてご紹介させていただきました。統合の方針を考えていた時点でも「想定できていない実装レベルのハマりどころはあるだろう」とは考えていましたが、実際その通りで、現実に進めていくには中々気合がいる作業だなと実感しました。とはいえ「なんとか実現できるだろう」とは考えていましたし、実際形にできたので私としては嬉しい限りです。また、今回の実装を経て得られた知見はもちろん、作業の中で出てくる課題を1つ1つ解決して形にしていくのはエンジニアとしての地力が鍛えられるなと感じました。
今回の取り組みを見て、「面白そう」とか「自分もやってみたい」と思われた方はいらっしゃいますでしょうか? 弊社にはこのような仕様から模索していくような仕事がまだまだたくさん転がっています。もし興味を持っていただけた方がいましたら、ぜひ採用サイトも覗いてみていただけると嬉しいです。
-
load_schema
に渡しているlambdaはtask実行時に評価されるので、Railsに依存した処理を書いて問題ありません。↩ -
502 Bad Gateway
等のエラーコードが適切かもしれませんが、stitchingをしているのは移行期のみであり、複数システム構成であることをあまり意識したくないので今回はシンプルに500 Internal Server Error
としました。↩ - 現時点では移植先サーバーのreforkingはまだ有効化されていないので、この設定は無しでも動きます。ただし、将来的にreforkingを有効化する際のハマりどころになってしまうので、現時点でこの設定を有効にしています。↩
- fork時のメモリのコピーはCopy on Write戦略に基づき行われるので、実際にはこのタイミングでコピーが行われるわけではありません。↩