前陣子與公司的其他夥伴一起找出了偶發的 CORS 原因,紀錄一下

如果一個網站內有包含對同一個 URL 的載入,但是有兩種方式,一種是從 HTML document 載入,另一種是用 xhr 存取,並且 Chome 有開啟 cache,就有可能會遇到。

重現問題

例如下面這個簡單的範例,用兩種方式從 AWS cloudfont 抓取圖片,CDN 的來源是 AWS S3

  1. 重新整理
  2. 在 xhr 發出請求前把 Disable cache 取消勾選


因為想要用 includes preload polymorphic associations,但是不同 model 後面的 association 對象不一樣時不能直接用。後來在網路上找到可以用 ActiveRecord::Associations::Preloader preload 已經初始化後的 records 來達到目的。

備註:Rails 6 已經支援 preload polymorphic associations 了Ref

假設有這樣的 polymorphic associations:

picture.imageable

MySQL 使用 JOIN + update_all 時,若遇到 LIMIT 則會轉為 subquery,需注意效能問題。

Rails 在使用 update_all 時,如果有用到 JOIN,會改用 subquery 的形式改寫,但若 Adapter 為 MySQL 時就會維持,因為 MySQL 語法本身支援 JOIN + UPDATE。但若是包含 LIMIT 時,由於 MySQL 也不支援,所以 Rails 會使用預設的 subquery 改寫。而在 MySQL 使用 update_all + subquery 時則可能遇到效能問題,可參考之前的文章:

https://github.com/rails/rails/blob/v6.1.2.1/activerecord/lib/arel/visitors/mysql.rb#L61-L70

https://dev.mysql.com/doc/refman/8.0/en/update.html

For the multiple-table syntax, UPDATE updates rows in each table named in table_references that satisfy the conditions. Each matching row is updated once, even if it matches the conditions…


Ruby 3.0.0 在去年聖誕節推出,release note 提到了一些 concurrency 相關的新功能,一邊研究一邊做個紀錄

Ruby 3.0.0 Released 提到了 Ruby 針對 Concurrency 加入了 Fiber#scheduler 的新功能,讓 Fiber 可以實作出 Non-blocking 的操作。

這個新功能的主要貢獻者 Samuel Williams 大大,之前就致力於 async gem,async 是一個基於 Fiber 實作出 event-driven IO 的 gem。Samuel 也做了好幾個基於 async 的各種實作,像是 async-ioasync-http。並利用這些當作基底實作出一個 rack-compatible HTTP server: Falcon

關於 Ruby Concurrency 相關的介紹,有找到一篇文章寫得很好:https://www.honeybadger.io/blog/ruby-concurrency-parallelism

關於 Fiber 的知識可以參考蒼時弦也大大的系列文章:https://blog.frost.tw/tags/Concurrency

關於新的 Fiber Scheduler 可以參考這位大大的回顧,以及他的 Fiber Scheduler 實作:
https://coderemixer.com/2020/07/26/whats-new-in-ruby-3-fiber/
https://coderemixer.com/2020/12/22/ruby-3-fiber-scheduler-evt-dev-log/


終於有時間來研究這件事要怎麼做到,後來研究出最簡單的解法是這樣

簡單來說就是想簡單做到下面這件事:

後來發現只要把目前被拿來切換全螢幕的快捷鍵改成用別的鍵就好了。

System Preferences>Keyboard>Shortcuts>Mission Control


偶然發現,Arel 好像是用 visitor pattern,好奇心驅使之下,就來研究看看。

翻翻歷史

翻了一下 Arel 的 repo,發現它其實一開始並不是用 visitor pattern 的,是在 2.0.0 之後才重寫,如這個 History 所述。

稍微研究了一下 1.0.1 跟 目前的結構的差別,目前是將各種節點都整理在 nodes 模組下,並將 to_sql 相關的模組完全抽離自節點,放到 visitors 模組。

題外話:為了知道 Arel 在 1.0.1 的運作邏輯,我還弄了一個 Rails 3.0.0 + Arel 1.0.1 的環境,放在這裏,雖然最後也看不出什麼鬼,反正就紀錄一下 XD 要跑個 Rails 3.0.0 還真是不容易(汗

Arel#to_sql 的運作模式

先產生一段簡單的 relation,並拿到 ar …


團隊最近遇到一個 subquery + update_all 會鎖整張表的問題,花了一點時間研究

注意:以下為 MySQL 5.6 的實驗探討,在 MySQL 8.0.21 的這個機制已經被優化了

問題

假設有一份 schema 長這樣,有 users 和 posts 兩張表。

假設今天使用了一個 subquery 做 update_all

User.where(id: Post.where(id: [1,2]).select(:user_id)).update_all(name: 'Momo')
UPDATE `users` SET `users`.`name` = 'Momo' WHERE `users`.`id` IN (SELECT `posts`.`user_id` FROM `posts` WHERE `posts`.`id` IN (1, 2))

發現竟然掃描了整張 users 表,是怎麼回事呢。

MySQL 的 Subqueries 的 Optimization 策略

根據官方文件說明, 對於 IN subquery 有三種方式去優化,分別是 Semijoin、Materialization、EXISTS strategy。但是在下面有的附註寫到:

A limitation on UPDATE and DELETE statements that use a subquery to modify a single table is that the optimizer does not use semijoin…


串接 Rollbar 的好處是可以方便監控伺服器噴錯。但是在使用 whenever 排程時執行程式時,有辦法在噴錯時也寫到 Rollbar 嗎?

如果是使用 sidekiq,無論是使用 include Sidekiq::Worker 產生的 worker,或是使用 sidekiq 當成 queue_adapterActiveJob,Rollbar 都有串接。另外像是 rake 也是有串接的。詳細可以參考 plugins 原始碼以及文件

config/schedule.rb 寫起來會像是這樣,這三種都是噴錯時會寫到 Rollbar 的:

可以的,Rollbar 提供了一個 rollbar-rails-runner 可以用,如果要寫進 config/schedule.rb 看起來會長這樣,需要自己定義 job_type

References


之前想要寫有條件的 left join 都會寫成 raw SQL query 的字串,但是其實也可以用 Arel 寫出比較好看的程式碼。

假設有個 DB 的 schema 長這樣:

SELECT users.name, posts.title FROM "users" LEFT OUTER JOIN "posts" ON "users"."id" = "posts"."user_id" AND "posts"."active" = 1

References


記錄一下照著官方教學設定開發 rails core repository 的環境

以下步驟取自 https://github.com/rails/rails-dev-box

  1. 安裝 Vagrant
  2. clone rails/rails-dev-box repository
  3. 到 rails-dev-box 目錄,跑 vagrant up
  4. fork rails 的 repository 到自己 Github 帳號
  5. 在 rails-dev-box 目錄 clone 自己帳號的 rails repository 下來
  6. vagrant ssh 進虛擬機
  7. 找個範例跑看看測試

Shih-Ying Chen

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store