2022年8月21日 DBのパフォーマンス考慮などのメモ (byやの)
内容と経緯
実務にてパフォーマンスに関する相談があり、その中で話題になった知識や相談に乗る上(パフォーマンスを考慮する上)で意識すると良い点などを走り書きのメモにはなりますが、こちらに書き起こしておきます。
パフォーマンス
- 遅延処理(ジョブキュー)
- レコードを大量に発行するような重たい処理はsidekiqなどderayed jobなどのライブラリを使用するのが一般的
- ジョブ(処理の内容)、キュー(ジョブを管理する場所)、ワーカー(キューを監視し、キューにジョブが保存されたら、キューからジョブを取り出し、そのジョブを実行)
- ジョブ、キュー、ワーカーは昔は自前で実装していたが現在ではsidekiqなどのライブラリが出てきたためよしなに管理が出来る。
- レコードを大量に発行するような重たい処理はsidekiqなどderayed jobなどのライブラリを使用するのが一般的
- DB
- indexを貼る
- keyをメモリに設定することで高速検索ができる
- 数千〜数万はインデックスを貼っておけばパフォーマンスは大丈夫(数秒)
- 数百万レコードなどになると色々な条件によるが数十秒かかったりする(UX的には致命的)
- ローカルではレコードが少ないので早いのは当たり前
- サーバーを増やす・不要なレコードを削除するなどの対応がある(基本)
- 基本的には正解はなく出来ることを全部やることでパフォーマンスを強化する
- ヒアリングが重要
- 不要なデータがあるか
- 最適解はヒヤリングして組み合わせてオリジナルで提案する
- ログ化が適切かなども視野に入れてみる
- ログ化
- Amazon RDS
- Amazon Aurora (クラウドのために再設計されたデータベース)にするとパフォーマンスが上がる
- 冗長構成する(横展開)
- 値段が高い
- Amazon Aurora (クラウドのために再設計されたデータベース)にするとパフォーマンスが上がる
- SQL
- 並び替え処理などをデータベースで行うとレコードをすべて並び替えするなどしてしまうので重くなる
- データベース内のレコード全体に影響を与えてしまうことがあるため、取得したあとにcontrollerで処理を加えるなどを考慮する書き方が出来るようになると良い
- indexを貼る
Web関連技術
2022年7月10日 instanceofよりも安全にインスタンスかどうかの確認ができる、プライベートフィールドのin演算子
instanceof と inの違い
instanceof
object instanceof constructor
instanceof 演算子は、object のプロトタイプチェーンに constructor.prototype が存在することを検査します。
in
prop in object
in 演算子は、指定されたプロパティが指定されたオブジェクト(またはプロトタイプチェーン)にある場合に true を返します。
in演算子のpropの部分には、プロパティ名を書きます。
プロパティ名または配列のインデックスを表す文字列式またはシンボルです(シンボルではない場合は、文字列に強制変換されます)。
propは基本的に文字列で指定します。
今回学んだprivate宣言できる#
のような特殊文字を使用した場合の挙動に着目して検証してみました
class MyClass { #brand static isMyClass(object) { return '#brand' in object; } } console.log(MyClass.isMyClass(new MyClass())); // false
#brandはMyClassのプロパティに存在しているのでtrueなのですが、特殊文字である#
は特殊文字として扱いたいため、文字列リテラルで囲ってはいけないのです。
では、publicとしての挙動をみてみます。
class MyClass { brand static isMyClass(object) { return 'brand' in object; } } console.log(MyClass.isMyClass(new MyClass())); //true
- brandを文字列リテラルで囲わなければ、brandは未定義のエラーになります。
- 基本的にはin演算子のpropはこのように文字列を指定しないといけません。
- 特殊文字を指定するときに注意が必要で基本は文字列リテラルを使用します。
instanceofとinの違いのまとめ
instanceofは、オブジェクトのプロトタイプチェーンにconstructor.prototypeが存在するかを判定します。inはプロパティがオブジェクト(またはプロトタイプチェーン)に存在するかを判定します。
参考
2022年7月2日 JavaScriptの最新仕様ES2022を深ぼる
1. クラスフィールド宣言ができるようになった
そもそもクラスフィールドとは
フィールドはクラスの中でデータの値を保管するために使用するものです。
メリット
- 今までのClass構文ではconstrutor()でフィールドを持たせる必要がありましたが、ES2022からはクラスに直接フィールドを書くことで、インスタンスにフィールドを持たせられるようになりました。
従来のクラスフィールド宣言の場合
class Human { constructor() { this.name = 'yano'; } } const human = new Human(); console.log(human.name); // yano
ES2022のクラスフィールド宣言の場合
class Human { name = 'yano'; } const human = new Human(); console.log(human.name); // yano
実際にブラウザのコンソールで実行すると、期待通りの出力結果が得られました。
従来の書き方とES2022の書き方は共存できるのか気になったので検証してみました
class Human { constructor() { this.name = 'yano'; } name = 'yukiHaga' } const human = new Human(); console.log(human.name); // 'yano'
- この場合は共存することはできずconstructorの中の定義が優先されます。
スタティックなクラスフィールド宣言
class Human { static category = "human" } console.log(Human.category); // human
こちらも従来の書き方とES2022の書き方は共存できるのか気になったので検証してみました
class Human { constructor() { this.name = 'yano'; } static name = 'yukiHaga' } console.log(Human.name); // 'yukihaga' const human = new Human(); console.log(human.name); // 'yano'
- staticなクラスフィールド宣言はconstructorは共存できます。
2. プライベートなフィールドとメソッドが使えるようになった
メリッド
フィールドとメソッドをプライベートにすることで、クラス外からの値の上書きを防ぐことができます。(普通にフィールドとメソッドを定義すると、パブリックとして定義されます。)
#を使ってプライベートなフィールドを宣言する
ES2022でのprivateの宣言は#
を使用します。
class MyClass { // プライベートなフィールド #name; constructor(name) { this.#name = name } hello() { console.log(`こんにちは${this.#name}さん!`) } } const foo = new MyClass("yano"); foo.hello(); // 「こんにちはyanoさん!」と出力される
- 従来は
_
をつける事でprivateな書き換えをしないメソッドだと慣習的に決まっていただけで、ただのルールであり仕様ではありませんでした。 - 今回の
#
を使うことによって、書き換えをしようとするとエラーになるようになり、きちんとした仕様になりました。
エラーを出すコード例
class MyClass { // プライベートなフィールド #name; constructor(name) { this.#name = name } #hello() { console.log(`こんにちは${this.#name}さん!`) } } const foo = new MyClass("田中"); // 以下の行でエラーが発生する foo.#hello = () => console.log('アイウエオ') foo.#hello()
エラー内容
syntaxError: Private field '#hello' must be declared in an enclosing class (プライベートフィールド '#hello' は、包含するクラスで宣言する必要があります。)
#
の付いているhelloメソッドに対して、console.log('アイウエオ')
に書き換えを行なっているのでエラーとなっています。
まとめ
- 従来のJSは慣習でprivateを_をつけることで定義していただけで仕様ではなかったため、全てがpublicでした。
- ES2022からprivateを
#
で定義して、書き換えを行なった際にエラーが出力されるようになった事で、言語の仕様としてprivateを持たせられるようになりました。
クラスフィールドとprivateメソッドは組み合わせられる
class MyClass { // public field foo = "public field: 大部屋"; // private field #bar = "private field: 個室"; // public static field static cafeteria = "public static field: 食堂"; // private static field static #japaneseStyleRestaurant = "private static field: 料亭"; // public method orange() { return console.log("public method: みかん"); } // private method #grape() { return console.log("private method: ぶどう"); } // public static method static staticOrange() { return console.log("public static method: みかん"); } // private static method static #staticGrape() { return console.log("private static method: ぶどう"); } }
- 冒頭で学んだクラスフィールドとpublic privateそれぞれの定義です。
- publicのものは書き換え可能ですが、privateのものは書き換えが不可です。
TypeScriptのprivateとES2022の#
の違い
トランスパイル後の状態が違います。
- TypeScriptのprivateはVSCode上でのコンパイルエラーとしてエラーを出力してくれるので実装上ではprivateが機能しているが、トランスパイル後にJSのコードに変換されたときには、publicなものになっている。
- ES2022の
#
ではトランスパイル後も#
となっており、privateなものとして読み込まれる。
これは、どちらでも問題がないため、チームの方針に従うべきと考えます。
参考
2022年5月29日 アルゴリズム問題をTypeScriptやRubyで解く (by やの)
問題
https://algo-method.com/tasks/213
◇ 問題の意図
配列の中から一番大きな数字を取り出してほしいようです。
Rubyで解いてみる
◇ 解く過程
- 問題を解くためのメソッドを検索
- 『ruby 配列 大きい要素』検索でヒットした記事 - Ruby リファレンス 3.1
◇ 提出したコード
n = gets.to_i
a = gets.split.map(&:to_i)
puts a.max
TypeScriptで解いてみる
◇ 解く過程
- 一瞬命名に悩みこちらを参考にしました。
- 問題を解くための関数を検索
- 『JavaScript 配列 大きい要素』検索でヒットした記事 - MDN
- Math.max()の引数には配列を渡すとNaNが返されるため、スプレッド構文で展開する必要がありそうです。
◇ 提出したコード
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n, v] = nv.split(" ").map(Number) const a = alist.split(" ").map(Number) type SelectMaxNumber = { (array: number[]): number; } const selectMaxNumber: SelectMaxNumber = (array) => { return Math.max(...array); } console.log(selectMaxNumber(a));
コメント
Rubyは超シンプルになりましたねー。笑
TypeScriptは書いていて楽しいです!
頭の体操になりました〜! 今日も一日頑張りま〜〜す♪
2022年5月21日 番外編アルゴ式
今回の問題
- 配列の要素と右隣の数字を比較して右隣が大きいケースの回数をカウントするという内容です。
解答
yano's answer
- Array.prototype.reduce()を使用しました。
- countを出す・配列のindexを使って操作する点などでreduceが妥当と判断しました。
//(入力受け取り 省略) type CountUpNumberInArray = { (numArray: number[]): number } const countUpNumberInArray: CountUpNumberInArray = (numArray) => numArray.reduce((accumulator, currentValue, index, array) => { return array[index] < array[index + 1] ? accumulator += 1 : accumulator += 0 }, 0); console.log(countUpNumberInArray(a));
- 関数式の後にreturnを書いた方がわかりやすいかもしれません。
- reduceの第二引数は使用しないため、アンダースコアで省略した方がいいです。
修正
const countUpNumberInArray: CountUpNumberInArray = (numArray) => { return numArray.reduce((accumulator, _, index, array) => { return array[index] < array[index + 1] ? accumulator += 1 : accumulator += 0 }, 0); }
- 命名ももう少し拘れると良さそうです。
Rubyでやってみる
- for in文パターン
n = gets.to_i a = gets.split.map(&:to_i) count = 0 for i in 0..n do count += 1 if a[i].to_i < a[i + 1].to_i end puts count
- each_with_indexパターン
- to_iを書くことでnilが発生した際に0に変換することでエラーを防ぐことができます。
- https://docs.ruby-lang.org/ja/latest/method/NilClass/i/to_i.html
n = gets.to_i a = gets.split.map(&:to_i) count = 0 a.each_with_index do |value, index| count += 1 if a[index] < a[index +1].to_i if index == a.size - 1 puts count end end
yuki's answer
- 素早く命名するのが苦手だと感じました。今後、改善していきます。(現時点の命名も本当に分かりやすいか疑問)
- reduceを使えば、配列の2つの要素を比べられるので、reduceを使えば良かったです。関数型プログラミングをなるべく自分のコードに取り入れたいと思いました。
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n, ..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) const countRightThanLeft = (list: number[]): number => { let count = 0; for (let i = 1; i < list.length; i++) { if (list[i-1] < list[i]) { count++; } } return count; } console.log(countRightThanLeft(list));
yui's answer
import * as fs from 'fs' const input = fs.readFileSync("/dev/stdin", "utf8") const [nv, alist] = input.split("\n") const [n,v,..._] = nv.split(" ").map(Number) const list = alist.split(" ").map(Number) type ArrayType = { (previousValue: number, currentValue: number, currentIndex: number, array: number[]): number; } const reducer: ArrayType = (previousValue, _, currentIndex, array) => { const count = (array[currentIndex] < array[currentIndex + 1]) ? 1 : 0; const returnNumber = previousValue + count; return returnNumber; } console.log(list.reduce(reducer, 0));
- 今回は、2つの要素の値を比較したいので
reduce()
を使いました。 reduceメソッドは、配列のそれぞれの要素に対して、順番通りに、コールバック関数を呼び出します。コールバック関数の引数には、
累積値, 要素, インデックス, 配列
を渡すことが出来ます。配列のすべての要素に対してコールバック関数を実行した結果が戻り値となります。関数の引数として、アンダーバー
_
が使われている記述があります。これは、使われない引数であることを明示しています。使われない値であるが、単純に引数として書く必要があるため、アンダーバーを使用しています。
参考
2022年5月20日 投稿機能を作成
今回実装した機能
仕様
userとpostを紐付ける
new Postリンクをクリックすると、投稿フォームが出る
投稿に成功したら、投稿一覧に遷移する
投稿に成功したら、「投稿に成功しました」というフラッシュメッセージを出す
実装を通した気づき
Yano
- 外部キーの途中追加・・・rails g migration add_user_id_to_posts
- migrationファイルに追記
class AddUserIdToPosts < ActiveRecord::Migration[6.0] def change add_reference :posts, :user, foreign_key: true end end
- ログインしているユーザーの持つpostを新規作成
- saveに成功した場合はユーザーの投稿一覧ページに遷移させる
- user_posts_path(current_user)とする。
- save!とすると投稿に失敗した場合に例外を返す。falseの処理がある場合はsaveを使用する。
def create @post = current_user.posts.build(post_params) if @post.save redirect_to user_posts_path(current_user) else render 'new' end end
Yui
- current_userは、sorceryのメソッドで現在のユーザーを取得できます。
- ルーティングの設計方法が完全に理解できていないことに気づきました。
- 今回投稿はユーザーモデルに紐づくためネストして記述しました。しかし、それだけだと投稿一覧ページに遷移出来ずエラーが発生しました。
- そこで、ネストしないユーザーに紐づかないルーティングを追加するとうまく遷移できるようになりました。
Rails.application.routes.draw do resources :users do resources :posts end resources :posts end
Yuki
# POSTリクエストで以下のURLにアクセスした場合、 # アクセスしたユーザーのポストを作成する /users/user_id/posts
- ログインした時に、投稿一覧ページになっていた方が良いので、redirect_toのパスを投稿一覧ページにしました。
redirect_back_or_to user_posts_path(@user), notice: 'Login successful'
参考
2022年5月19日 ログイン機能 ・サインアップ機能を作成
テーブル構成
◆ usersテーブル
name:string email: string crypted_password: string salt: string
◆ postsテーブル
title:string body: text user_id:bigint(references)
◆ commentsテーブル
body: text text: string
実装
ログイン機能・サインアップ機能をsorceryで実装しました。
Simple Password Authentication
実装を通した気づき
Yano
- debuggerで値の中身を確認することが出来ます。
- "@user"やparamsと叩くなどして、どういったパラメータを受け取っているかなどでミスを発見しました。
[2, 11] in /Users/yanokouhei/workspace/rails-free/free/app/controllers/user_sessions_controller.rb 2: skip_before_action :require_login, only: [:new, :create] 3: 4: def create 5: @user = login(params[:email], params[:password]) 6: debugger => 7: if @user 8: redirect_back_or_to(:users, notice: 'Login successful') 9: else 10: flash.now[:alert] = 'Login failed' 11: render action: 'new' (byebug) @user #<User id: 3, email: "yano2@yano", crypted_password: "$2a$10$4TIvRfrA3S2NlbfGzkrs/.zr2hjnzjh2UYKfGMl3VFe...", salt: "_PMqGaxonoZgeF8M5k1z", created_at: "2022-05-19 00:33:14", updated_at: "2022-05-19 00:33:14">
コロンの位置などは注意した方が良いです。
has_many :posts, dependent: :destroy
Yui
$ rails d model
コマンドで作成したモデルやマイグレーションファイルを削除できます。- ユーザー登録機能を実装中に
Webpacker::Manifest::MissingEntryError
エラーが発生しました。- Webpackをインストール後、コンパイルを実施したところ解決できました。詳しくは、以下を参照してください。
- Webpacker::Manifest::MissingEntryErrorと出た時の対処法
Yuki
- scaffoldで生成したファイルに処理内容が自動的に書かれているのがすごいと感じました。
- strongパラメータにちゃんと値が渡っているか気をつけるべきです。
def user_params params.require(:user).permit(:email, :password, :password_confirmation) end
明日やること
ユーザーに紐付けた投稿機能やります!