Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

成長するユーザーテーブルを飼いならす方法

クライアントはどのようにしてそこにたどり着きましたか?

詳細に入る前に、アプリがこの状態になる可能性があることを理解してみましょう。簡単なusersから始めます テーブル。数週間後、最後のサインイン時刻を特定できるようにする必要があるため、users.last_sign_in_atを追加します。 。次に、ユーザーの名前を知る必要があります。 first_nameを追加します およびlast_name 。 Twitterのハンドル?別の列。 GitHubプロファイル?電話番号?数か月後、テーブルは気が遠くなるようなものになります。

これの何が問題になっていますか?

大きな表はいくつかの問題を示しています:

  1. User 複数の無関係な責任があります。これにより、理解、変更、テストがより困難になります。
  2. アプリとデータベース間でデータを交換するには、追加の帯域幅が必要です。
  3. かさばるモデルを保存するには、アプリにさらに多くのメモリが必要です。

アプリがUserを取得しました 認証と承認の目的ですべてのリクエストに対応しますが、通常はほんの一握りの列しか使用しませんでした。問題を修正すると、設計とパフォーマンスの両方が向上します。

テーブルの抽出

めったに使用されない列を新しいテーブルに抽出することで問題を解決できます 。たとえば、プロファイル情報を抽出できます(first_name など)をprofilesに追加します 次の手順で:

  1. profilesを作成する usersのプロファイル関連の列を複製する列 。
  2. profile_idを追加します Userへ 。 NULLに設定します 今のところ。
  3. Userの各行 、profilesに行を挿入します プロファイル関連の列を複製します。
  4. ポイントprofile_id usersの対応する行の 3に挿入された行に。
  5. しないでください users.profile_idを作成します NULL以外 。アプリはまだその存在を認識していないため、壊れてしまいます。

users.first_nameへの参照を置き換える必要があります profiles.first_nameを使用 等々。少数の参照を含む少数の列のみを抽出する場合は、手動で行うことをお勧めします。しかし、私たちが「ああ、いや、これは史上最悪の仕事だ!」と考えていることに気づいたらすぐに 代替案を探す必要があります。

問題をおろそかにしないでください。 誰もが避けているコードの一部はさらに劣化し、さらに大きな不注意に苦しむでしょう 。悪循環を断ち切る最も簡単な方法は、小さく始めることです。

クライアントが問題をどのように解決したかを知りたい場合は、読んでください。

一度に1行ずつコードを修正する

最も段階的なアプローチは、一度に1つの古い列への参照を修正することです。 first_nameの移動に焦点を当てましょう usersから profilesへ 。

まず、Profileを作成します と:

rails generate model Profile first_name:string

次に、usersからの参照を追加します profilesusers.first_nameをコピーします profilesへ :

class ExtractUsersFirstNameToProfiles < ActiveRecord::Migration
  # Redefine the models to break dependency on production code. We need
  # vanilla models without callbacks, etc. Also, removing a model in the future
  # might break the migration.
  class User < ActiveRecord::Base; end
  class Profile < ActiveRecord::Base; end
 
  def up
    add_reference :users, :profile, index: true, unique: true, foreign_key: true
 
    User.find_each do |user|
      profile = Profile.create!(first_name: user.first_name)
      user.update!(profile_id: profile.id)
    end
 
    change_column_null :users, :profile_id, false
  end
 
  def down
    remove_reference :users, :profile
  end
end

各ユーザーに1つのプロファイルを強制するため、usersからの参照 profilesへ 反対の参照よりも望ましいです。

データベース構造が整ったら、first_nameを委任できます Userから profilesへ 。私のクライアントにはいくつかの要件がありました:

  1. アクセサは、関連するprofilesを使用する必要があります 。また、非推奨のアクセサーが呼び出された場所もログに記録する必要があります。
  2. Userを保存する profilesを自動的に保存する必要があります 非推奨のアクセサーを使用してコードを壊さないようにするため。
  3. User#first_name_changed? およびその他のActiveModel::Dirty メソッドは引き続き機能するはずです。

これはUserを意味します 次のようになります:

class User < ActiveRecord::Base
  # We need autosave as the client code might be unaware of
  # Profile#first_name and still reference User#first_name.
  belongs_to :profile, autosave: true
 
  def first_name
    log_backtrace(:first_name)
    profile.first_name
  end
 
  def first_name=(new_first_name)
    log_backtrace(:first_name)
 
    # Call super so that User#first_name_changed? and similar still work as
    # expected.
    super
 
    profile.first_name = new_first_name
  end
 
  private
 
  def log_backtrace(name)
    filtered_backtrace = caller.select do |item|
      item.start_with?(Rails.root.to_s)
    end
    Rails.logger.warn(<<-END)
A reference to an obsolete attribute #{name} at:
#{filtered_backtrace.join("\n")}
END
  end
end

これらの変更後、アプリは同じように動作しますが、Profileへの追加の参照のため、少し遅くなる可能性があります (パフォーマンスが問題になる場合は、AppSignalなどのツールを使用してください)。コードは、複製できない属性も含め、レガシー属性へのすべての参照をログに記録します(例:user[attr] = ... またはuser.send("#{attr}=", ...) )したがって、grepの場合でも、それらすべてを見つけることができます。 役に立たない。

このインフラストラクチャが整ったら、users.first_nameへの1つの参照を修正することを約束できます。 定期的なスケジュールで、例えば毎朝(クイックウィンで一日を始めるため)または正午頃(集中した朝の後にもっと簡単なことに取り組むため)。 私たちの目標は問題を解決するための精神的な障壁を減らすことであるため、この取り組みは不可欠です 。上記のコードを何もせずにそのままにしておくと、アプリがさらに貧弱になります。

非推奨の参照をすべて削除した後(およびgrepで確認した後 およびログ)最終的にusers.first_nameを削除できます :

class RemoveUsersFirstName < ActiveRecord::Migration
  def change
    remove_column :users, :first_name, :string
  end
end

Userに追加されたコードも削除する必要があります 不要になったので。

制限

この方法はあなたのケースに当てはまるかもしれませんが、その制限のいくつかに注意してください:

  • User.update_allのような一括クエリは処理しません 。
  • 生のSQLクエリは処理しません。
  • モンキーパッチが壊れる可能性があります(依存関係によってもモンキーパッチが導入される可能性があることに注意してください)。
  • User およびprofiles profiles.first_nameの場合、同期がとれていない可能性があります 更新されますが、users.first_name そうではありません。

あなたはそれらのいくつかを克服することができるかもしれません。たとえば、モデルをサービスオブジェクトまたはProfileのコールバックと同期させておくことができます。 。または、PostgreSQLを使用している場合は、暫定的にマテリアライズドビューを使用することを検討してください。

以上です!

この記事の最も重要な教訓は、においのするコードを避けず、代わりに真正面から取り組むことです。 。タスクが圧倒的である場合は、定期的なスケジュールで繰り返し作業します。提示された記事a テーブルを抽出するときに考慮する方法は困難です。あなたがそれを適用することができないならば、それから何か他のものを探してください。方法がわからない場合は、私に連絡してください。お手伝いさせていただきます。ビットを腐らせないでください。


  1. Windows 10 PC を高速化する方法

    Windows 10 コンピューターを高速化する方法を探しているなら、あなたは正しい場所にいます。今日に至るまで、動作が遅く、新しい PC でも高速であると思われる Windows 10 コンピューターをいくつか見て、修正してきました。私の経験によると、システムのパフォーマンスに影響を与えるいくつかの理由により、Windows の動作が遅くなる場合があります。 Windows (すべてのバージョン) の動作が遅くなる最も一般的な理由は次のとおりです: マルウェア感染。 Windows の起動時に実行される大量のプログラム ページング ファイル (仮想メモリ) の設定が無効です。 破損

  2. Facebook はどのように信頼を取り戻そうとしていますか?

    Facebook と Mark Zuckerberg は、最近の大失敗により、最近ニュースになっています。すべては、ケンブリッジ アナリティカのデータ プライバシー スキャンダルから始まり、彼の辞任の憶測につながりました。噂によると、Facebook を辞めた後、マークはスキンケア ラインを推奨する予定ですが、それはエイプリル フールのジョークであることが判明しました。 しかし問題は、マークが彼の将来のために何をしようとしているかではなく、ソーシャル メディアの巨人である Facebook.Inc がユーザーの失った信頼をどのように取り戻すかです。状況について憶測や異なる意見を聞くことはよく