texta.fm - 2. The Power of Constraints を聴いたぞ
メモ(きになったところだけ)
- composed_ofだとfreezeされる
- immutableなオブジェクトのデメリットはメモリ消費が多いこと
- これは処理系レベルで解決されるべき問題
- コンテナ数をスケールアウトすることが容易になってきたので、1コンテナの中でのプロセス数を増やすことは(少なくとも一般的なwebアプリケーションでは)目指さなくてよいのでは
- (契約による設計において)assertion errorとexceptionは異なる
- assertion errorは、ありえないこと、以下のときに投げる
- 仲間内の利用者が約束を守っていなかったとき
- exceptionは、呼び出し側が約束を守っていたとしても発生するかもしれないこと
- 外部(APIとか)の利用者が約束を守っていなかったとき
- 利用者は約束を守っているけど問題が発生したとき
- assertion errorは、ありえないこと、以下のときに投げる
- assertionはそんなにいっぱい書かなくていいんじゃないの、という話
- お互いに全てを疑ってガード節を並べるのではなく、約束を設けて、呼び出し側のことを信頼する
- コントローラで不適切な入力はちゃんと弾いて、適切な引数で呼んでくれているのだろうから、モデルではいちいちガード節は書かない、と。
- そうすると全体最適になる
- VOはコンストラクタでvalidationを書く
- そもそもinvalidなオブジェクトは存在しえないようにする
- これはinvalidなオブジェクトが存在しうるRailsのモデルとは大きく異なる
- なぜRailsではinvalidなオブジェクトの存在を許しているのか
おもったこと
VOはimmutableな方がいいよね、というだけで、mutableでもありうるよな
JavaのStringはimmutableらしいけど、Rubyは。
str1 = "foo" str1.object_id # => 180 str2 = "foo" str2.object_id # => 200 # VOである str1 == str2 # => true # mutableである str1.concat("bar") # => "foobar" str1 # => "foobar" str1.object_id # => 180
しかしmutableにすることのメリットがいまいちわからん。 メモリ消費の抑制というのはそこまでの理由ではない気がする。 なぜmutableにしたのだろうか?
正規表現で直前/直後の文字をフィルタしたいときは行頭/行末の可能性を明示する
きわめて初歩的ながら仕様を知らずに検索漏れが発生しそうになってしまったのでメモする。
たとえばいまcool
というメソッドの呼び出し箇所を調べていたとする。
/cool/
だとこうだ。
cool # <= hit cool.strong # <= hit cool? # <= hit
cool?
という別のメソッドもひっかかるわけだが、これは除外したいとする。
そこで/cool[^\?]/
とすると、こうだ。
cool # <= not hit cool.strong # <= hit cool? # <= not hit
一行目はhitするものかとおもっていたら、一行目もhitしなくなってしまった。
仕様を確認する。
Use regular expressions - Visual Studio | Microsoft Docs
Match any character that is not in a given set of characters.
ふむ、/[^abc]/
はcharacterに一致するのであって、改行とか行末には一致しないのか。
ということで必要だった表現は/cool($|[^\?])/
ということみたいです。
cool # <= hit cool.strong # <= hit cool? # <= not hit
省略するが行頭でもまったく同じ話なので、例えば/(^|[^_])cool/
のように書く必要がある。
react-dndでドラッグアンドドロップ
HTML5のDrag and Drop APIを直接操作することも可能そうだが、ブラウザ間のinconsistencyで苦しむ羽目になりそうだ。 そこらへんをよしなに抽象化して差を吸収してくれるreact-dndというライブラリがあるということで触ってみた。
https://codesandbox.io/s/adoring-butterfly-r82k4
(ファイルアップロードのやつはsandboxのiframeだと機能しないけど別タブで開くと機能する。...?)
useMemoとかuseCallbackとか
そういえばこのhooksつかったことないなとおもって、どういうやつなのか調べて試してみた。 パフォーマンス改善系のhooksだったのだな。
useMemo
useCallback
『Linuxのしくみ』読書メモ
ざっくり
CPU/メモリ/ストレージデバイスという構成とか、プロセススケジューリングとかスワッピングとかのトピック自体は知っているものも多かったけど、全体的に解像度がぐっと上がった気がしてよかった。
書いてあって知ったこと
CPUのモード
各種コマンド
- sar(system admin reporter)でいろいろ
- freeでメモリ
- df(disk free)でディスク
プロセスにnice(2)で優先度をつけると、プロセススケジューリングにおいてより多くの/少ないCPU時間を割り当てることができる
仮想メモリと物理メモリがある
- プロセス(カーネルの外、ユーザーの世界)から物理メモリに直接アクセスする方法はない
- 仮想メモリと物理メモリの紐付けはページテーブルによって管理される
- 仮想メモリと物理メモリを分離することのメリットは以下
- 物理メモリ上で断片化せざるを得なかったとしても、仮想アドレス上では連続しているので大きなひとかたまりのデータをもつことが可能
- 「各プロセスは、そのプロセスのページテーブルに記載された物理アドレスにしかアクセスできない」というルールを導入して、各プロセスが自身と関係ない物理メモリにアクセスすることを防げる
- プログラムファイルはコード領域とデータ領域をメモリ上のどこにどれだけ確保するかという情報をもつ。物理メモリしかない場合、同じプログラムを複数のプロセスで実行すると、物理メモリ上のある領域を複数のプロセスがそれぞれ勝手につかおうとして困ってしまう。仮想メモリを挟むことによって、物理メモリとしては異なる領域を参照させ、干渉を避けることができる。
- 仮想メモリの割当はmmap(2)によって行い、割り当てた時点では物理メモリは割り当てられない。メモリへのアクセスが発生して「対応する物理メモリないじゃん!」でページフォールトが起きたら、カーネルのページフォールトハンドラが物理メモリの割当(とページテーブルの書き換え)を行う。
- ページテーブルをコンパクトにするため以下の仕組みがある。
- 階層化して、対応する物理アドレスがない範囲については、その範囲に対応する下層のテーブルをそもそも作成しない
- 仮想/物理メモリの対応関係が複数ページにわたって連続している場合、それらページをまとめて一単位(ヒュージページ)として対応関係を保持することでテーブルサイズを小さくする
キャッシュメモリにも階層がある
キャッシュメモリとページキャッシュは「書き込みがあったらキャッシュを書き換えてダーティー状態にしといて、後でライトバック」とかの点で同型
ハイパースレッドは以下の仕組み
- CPUと(キャッシュ)メモリとの間でのデータ転送待ち時間の分CPUをアイドルにしないために
- 1つの物理CPUに、プロセスAのデータ転送待ちの間にプロセスBを処理させる
- これを「論理CPU XがプロセスAを、論理CPU YがプロセスBを引き受けている」状態として表現する
デバイスはファイルとして表現され(
/dev/
配下など)、以下の2種類があるブロックデバイスのIOパフォーマンスを良くするために、カーネル内のブロックデバイス層が以下の機能を提供する
- 先読み: ある領域にアクセスするとつづけて後続の領域が必要になることがおおいので、言われなくてもついでに後続の領域を読み込んでおく
- IOスケジューラ: IOの指示をちょっとためて、アクセスする領域についてソートしたり連続部分をマージしたりしてからまとめてアクセスする
ブロックデバイスのIOパフォーマンスをよくするには
- HDDでもSSDでも、シーケンシャルアクセスの場合は先読みでかなりパフォーマンスが上げられるので、なるべくシーケンシャルアクセスしよう
- (どうせその領域全体が必要になるなら)細切れではなくなるべくまとめてアクセスしよう
書いてなくて知ったこと
次は
ここらへん読みたいな
*1:この要約には違法性はないと考える。https://www.bengo4.com/c_1015/c_17/c_1263/b_341980/
ActiveRecordでnew => build => save! するとどうなる
関連付けのあるモデルにおいて、親レコードをnew => 子レコードをbuild => 親レコードをsave! したときの挙動が複雑な気がしたのでメモ。
(理解力が不足しているだけかもしれない。)
検証環境: ActiveRecord 6.0.2.1
追記
さんざん書いてから気づいたのだけど、has_oneにもhas_manyにもvalidateというオプションが存在する。 これのデフォルトがhas_oneだとfalse, has_manyだとtrueになっているため本記事の挙動となっていただけで、ここを指定すればいいだけの話だった...。
結論を言葉で
- has_oneでは親レコードと子レコードのvalidityは独立している。
- has_manyでは子レコードが1つでもinvalidなら親レコードもinvalidになる。
親レコードをsave!したとき、
- 親レコードがinvalidならraiseする
- 親レコードも子レコードもvalidな場合、insertがtransactionで囲まれて実行される
- 親レコードがvalidで子レコードがinvalidな場合(上記の性質によりhas_oneでのみありうる)、親レコードのinsert処理のみが実行される
実験
サンプルは以下。
migrations:
class CreateTables < ActiveRecord::Migration[6.0] def change create_table :customers do |t| end create_table :banks do |t| end create_table :merchants do |t| end create_table :bank_accounts do |t| t.references :customer, foreign_key: true, null: false t.references :bank, foreign_key: true, null: false t.string :account_number, null: false end create_table :orders do |t| t.references :customer, foreign_key: true, null: false t.references :merchant, foreign_key: true, null: false t.integer :price, null: false end end end
models:
class Customer < ApplicationRecord has_one :bank_account has_many :orders end class Bank < ApplicationRecord end class Merchant < ApplicationRecord end class BankAccount < ApplicationRecord belongs_to :customer validates :account_number, presence: true end class Order < ApplicationRecord belongs_to :customer validates :price, presence: true end
実験するケースは以下。
- 1 has_one
- 1-1 invalid
- 1-2 valid & insertable
- 1-3 valid & uninsertable
- 2 has_many
- 2-1 all invalid
- 2-2 all valid & insertable
- 2-3 all valid & uninsertable
- 2-4 valid&savable + valid & uninsertable
- 2-5 invalid + valid & uninsertable
- 2-6 invalid + valid & insertable
1-1 invalid
irb(main):118:0> customer = Customer.new irb(main):119:0> customer.build_bank_account => #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: nil, created_at: nil, updated_at: nil> irb(main):120:0> customer.valid? => true irb(main):121:0> customer.bank_account.valid? => false irb(main):122:0> customer.bank_account.errors.full_messages => ["Account number can't be blank"] irb(main):123:0> customer.save! => true irb(main):125:0> customer.persisted? => true irb(main):126:0> customer.bank_account.persisted? => false
1-2 valid & insertable
irb(main):156:0> customer = Customer.new irb(main):157:0> customer.build_bank_account(account_number: "1234", bank_id: 1) => #<BankAccount id: nil, customer_id: nil, bank_id: 1, account_number: "1234", created_at: nil, updated_at: nil> irb(main):158:0> customer.valid? => true irb(main):159:0> customer.bank_account.valid? => true irb(main):160:0> customer.save! => true irb(main):161:0> customer.persisted? => true irb(main):162:0> customer.bank_account.persisted? => true
1-3 valid & uninsertable
irb(main):138:0> customer = Customer.new irb(main):139:0> customer.build_bank_account(account_number: "1234") => #<BankAccount id: nil, customer_id: nil, bank_id: nil, account_number: "1234", created_at: nil, updated_at: nil> irb(main):140:0> customer.valid? => true irb(main):141:0> customer.bank_account.valid? => true irb(main):142:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'bank_id' doesn't have a default value) irb(main):143:0> customer.persisted? => false irb(main):144:0> customer.bank_account.persisted? => false
2-1 all invalid
irb(main):176:0> customer = Customer.new irb(main):177:0> customer.orders.build => #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):178:0> customer.orders.build => #<Order id: nil, customer_id: nil, bank_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):187:0> customer.valid? => false irb(main):188:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):191:0> customer.orders.map {|order| order.valid? } => [false, false] irb(main):193:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], ["Price can't be blank"]] irb(main):196:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
2-2 all valid & insertable
irb(main):001:0> customer = Customer.new irb(main):002:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):004:0> customer.orders.build(price: 200, merchant_id: 2) => #<Order id: nil, customer_id: nil, merchant_id: 2, price: 200, created_at: nil, updated_at: nil> irb(main):005:0> customer.valid? => true irb(main):006:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):007:0> customer.save! => true irb(main):008:0> customer.persisted? => true irb(main):009:0> customer.orders.map {|order| order.persisted? } => [true, true]
2-3 all valid & uninsertable
irb(main):016:0> customer = Customer.new irb(main):017:0> customer.orders.build(price: 100) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil> irb(main):018:0> customer.orders.build(price: 200) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil> irb(main):019:0> customer.valid? => true irb(main):020:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):021:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value) irb(main):024:0> customer.persisted? => false irb(main):025:0> customer.orders.map {|order| order.persisted? } => [false, false]
2-4 valid&savable + valid & uninsertable
irb(main):035:0> customer = Customer.new irb(main):036:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):037:0> customer.orders.build(price: 200) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 200, created_at: nil, updated_at: nil> irb(main):038:0> customer.valid? => true irb(main):039:0> customer.orders.map {|order| order.valid? } => [true, true] irb(main):040:0> customer.save! ActiveRecord::NotNullViolation (Mysql2::Error: Field 'merchant_id' doesn't have a default value) irb(main):041:0> customer.persisted? => false irb(main):042:0> customer.orders.map {|order| order.persisted? } => [false, false]
2-5 invalid + valid & uninsertable
irb(main):044:0> customer = Customer.new irb(main):045:0> customer.orders.build => #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):046:0> customer.orders.build(price: 100) => #<Order id: nil, customer_id: nil, merchant_id: nil, price: 100, created_at: nil, updated_at: nil> irb(main):047:0> customer.valid? => false irb(main):048:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):049:0> customer.orders.map {|order| order.valid? } => [false, true] irb(main):050:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], []] irb(main):051:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)
2-6 invalid + valid & insertable
irb(main):059:0> customer = Customer.new irb(main):060:0> customer.orders.build => #<Order id: nil, customer_id: nil, merchant_id: nil, price: nil, created_at: nil, updated_at: nil> irb(main):061:0> customer.orders.build(price: 100, merchant_id: 1) => #<Order id: nil, customer_id: nil, merchant_id: 1, price: 100, created_at: nil, updated_at: nil> irb(main):062:0> customer.valid? => false irb(main):063:0> customer.errors.full_messages => ["Orders is invalid"] irb(main):064:0> customer.orders.map {|order| order.valid? } => [false, true] irb(main):065:0> customer.orders.map {|order| order.errors.full_messages } => [["Price can't be blank"], []] irb(main):066:0> customer.save! ActiveRecord::RecordInvalid (Validation failed: Orders is invalid)