Heroku: Ruby 社群的雲端平台

一直想要寫一篇來介紹 Heroku,而這篇簡單的介紹堆在 draft 好一陣子了。雖然比不上 @deduce 上週寫的 Ruby語言的雲端運算平台:Heroku 來的詳細,但既然都寫了就貼出來吧 XD

常常有人在羨慕 Python 跟 Java 有 Google App Engine 可以用,然後 Ruby 沒有。雖然說 Ruby 可以透過 JRuby 跑在 GAE 上(方案1, 方案2),但是好像不太多人這麼做,為什麼呢? 這是因為 Ruby 社群大多用 Heroku 啊。

Heroku 這家公司提供了與 GAE 類似的 PaaS 雲端服務,讓任何符合 Rack 介面的 Ruby 應用程式可以跑在上頭(所以不只是 Rails,像是 Sinatra 也可以跑在上面),以及提供 PostgreSQL 這套關聯式資料庫和其他許多的外掛服務。它的底層硬體資源是採用了可擴展的 Amazon EC2,透過 Heroku 的 dynos (它的資源單位) 只要拉上拉下就可以增加伺服器負載了,非常厲害,請看 Rapportive 的故事:

想要擁有一個可隨時延展的架構(scalable architecture),你只要選對廠商(hosting provider)就可以做到。如果Rapportive一開始隨便選了一個便宜的VPS,流量一衝進來的時候可能就會像全面啟動電影中Cobb的混沌世界(Limbo)一樣崩潰。Rapportive的服務是放在知名廠商Heroku上,對於突然湧進的流量只需要增加Dynos的數量(Heroku提供服務的基本單位),基本上你是不需要修改你的程式的;當然,程式的優化、調整可以在同樣能耐的硬體等級上容納更多人。

使用Heroku、不需要調整程式、只需要增加Dyno數量?真的有這麼美好嗎?事實上Rapportive就是這麼辦到的,在來自全世界的流量突然湧進時,Rahul Vohra手邊沒有電腦,於是他隨即拿起iPhone並且利用Nezumi這個設計來管理Heroku的應用程式,將Rapportive的Dynos增加到20個,就這麼簡單,可能不到一分鐘吧?!系統的能耐馬上就提昇了。

它的佈署方式採用 Git,所以如果你熟悉 Git,那麼使用 Heroku 真是超級簡單的:

安裝步驟:

gem install heroku
heroku create your_app_name

佈署:

git push heroku master (當然,你的專案必須是 Git repository)
接著打開瀏覽器 your_app_name.heroku.com 就可以看到了。

只要一個指令就可以搞定佈署,它會自動幫你調整 database.yml 檔案。可參考 Heroku Quickstart Guide。根據最新的實地測試,Rails3 也可以正確跑起來。

總而言之,它的優點 1. 支援所有 Rack 應用程式 2. 佈署容易 3. 擴充方便外掛多。相較於 GAE,除了免費方案沒 G 社大方之外,其他毫不遜色啊。

Ruby 1.9.2 和 Rails 3.0.0 正式版發佈

歷經了兩年的開發,這個月 Ruby 社群有了兩個重大的世代交替消息:

關於 Ruby 1.9,可以參考我之前的演講:Ruby 1.9 投影片,最重要的部分就是 M17N Encoding 了。至於安裝請服用 RVM

關於 Rails3,可以參考我之前整理的:

如果是 Rails 初學者,可以直接看我正在撰寫的 Ruby on Rails 實戰手冊 一書。

BTW,JRuby 1.5.2 也是這個月發佈,而且 Rails3 也說支援 JRuby 1.5.2+,這個月真熱鬧。有人說今天就差 TextMate 2 發佈了 XDXD

Rails3 實戰手冊

Update(2011/4): 預計延到 2011 年中出版。
Update(2014/1): 無限延期…

Rails3 實戰手冊,這是我最近一次上課的講義,也是預計年底出版的內容 (書名未定)。也因為會要出版的關係,這份手冊的內容是 Copyright 的,而不是我一般慣用的 CC 授權 (除了投影片仍用 CC 授權),所以請不要轉載內文,更何況我會一直修訂啊。

之前應 OSSF 工作坊開了 Ruby/Rails 課程好幾次,所以陸陸續續開始寫投影片跟教材,加上這禮拜趁去台北市教師研習中心開課又重新好好整理了一次。目前狀況前幾章的完成度較高,會繼續努力修改成完整一本書的樣子,預計年底明年由 PcUSER 電腦人出版。

希望這本書能讓想入門 Rails 的朋友,可以實際應用在工作上無礙。有任何意見(書名有建議嗎?),歡迎告訴我。

Rails3 RC 發佈: 重點導覽

RailsConf 結束之後就嚷嚷要發佈的 Rails 3 Release Candidate 候選版本終於出了(好吧,是三個禮拜前的事情了,這篇拖稿了 XD),如果你還沒進場,又是一個好時機。從我上次(疑,竟然是半年前了!!)整理 Rails3 Beta 發佈: 重點導覽 以來,又有了一些不錯的文件,經過筆者過濾挑選,重新整理 Rails3 的八大重點:

1. Bundler
2. ActiveRecord 的新 Query API
3. ActiveRecord 的新 Validation API
4. Views: XSS, Block Helper, UJS
5. I18n
6. 新的 Routing API
7. 新的 ActionMailer
8. Metal

如果喜歡看 screencast,官方也錄了 rubyonrails.org/screencasts/rails3

安裝

升級到 ruby 1.8.7 或 1.9.2,以及 rubygems 要 1.3.6 以上


gem update --system
gem install rails --pre
gem install mysql2

開始改用 mysql2 gem 吧,這一套修正了 multithreaded 及 Ruby 1.9 的 Encoding 問題

升級

如果是升級,重點 Bundler 和 Views 部分必須先搞懂,不然完全動不了。其他 API 變更的部份(Routing, ActiveRecord, ActionMailer) 在 Rails 3.1 之前暫時是可以 works。當然有空還是趕快研究一下囉。以下是兩篇有關升級的新文章:

* Upgrading a Rails 2 App to Rails 3
* The Road to Rails 3: make your Rails 2.3 project more Rails 3 oriented

Rails 本身升級是沒有問題,怕的是周邊的 Plugin 沒能跟上。railsplugins.org 有整理一些,可以找找 github 上有沒有 branch,而 Rails3 透過 Bundle 安裝還蠻方便的。

1. Bundler

* Bundler: Rails3 用來管理 Gem dependencies 的神器

2. ActiveRecord Query Interface

Rails2 的 finders, named_scope, with_scope 等用法,在 Rails3 統一為一種 Relation 用法,可以更容易做 composable(chainable)、Decoupled from SQL 以及 Lazy Loading。另外如果是有傳參數的 scope,也偏好定義成 class method,而不是 scope + lambda 了。以下是一些不錯的文章:

* Active Record Query Interface 3.0 基本用法介紹
* ActiveRelation: ActiveRecord Gets a Facelift 基本用法介紹
* Active Record Queries in Rails 3 基本用法介紹
* Advanced Queries in Rails 3 進階用法介紹
* The Skinny on Scopes (Formerly named_scope) 進階用法介紹,跨 Model 的 Scope 太厲害了。
* New Active Record scoping syntax 如果你在 Rails2 有使用 with_scope 或 with_exclusive_scope( :find => x, :create => y ) 請讀這篇。新的 Rails3 建議你改用 scoping, unscoped, create_with 等新用法,因為舊的用法行為有差異。

3. ActiveRecord Validation

* AR Validation 提供了新的 API 只要一行就可以整合同一個屬性的多個驗證,可以減少行數。
* 可以客製 EachValidator 整合進上述的新 API 之中
* 可以客製 Validator 包裝一大堆 validations,例如 validates_with ReallyComplexValidator
* save(false) 終於拿掉了,要跳過驗證儲存請 save(:validate => false)

* Validations in Rails 3
* Improved validations in Rails 3
* validates :rails_3, :awesome => true
* Independent Model Validators

另外就是驗證錯誤的 API 也改了:

* Errors#on 跟 on_base 拿掉了,請改用 Errors#[]
* Errors#add_to_base(msg) 拿掉了,請改用 Errors#[:base] << msg * Views 裡面常用的 error_messages_for 跟 error_message_on Helper 被拿掉了,請自己寫過(你可以參考 scafffold 產生出來的程式)或裝回 legacy plugin

4. View

* Rails3 如何換使用 jQuery
* Unobtrusive JavaScript in Rails 3
* 所有有傳 code block 的 Helper 都改成 <%= 的形式,
請參考 Rails3 如何寫 Helper 可以傳 Block 參數 一文
* Rails3 處理 XSS: Rails 3 Security Strategy,HTML 預設都會逸出了,不要逸出請用 raw Helper 或字串的 html_safe! 方法。

5. I18n

* 詞彙檔中的變數替換由 {{}} 變成 %{} 了。這有點惱人,我還碰到 redmine 的問題(redmine 使用 Rails2,但是卻因為裝了 Rails3 所以 i18n 也升級到 0.4.1 版本就爆炸了)
* 提供更多 backends,例如 ActiveRecord
* 加速功能!! Run, I18n, run!

6. Routing

* Rails3 重點導覽:新的 Routes
* The Lowdown on Routes in Rails 3 很詳盡的用法介紹
* The Powerful New Rails Router 進階用法
* Rack in Rails 3 結合 Rack 端點的應用

7. ActionMailer

底層由 TMail 換 Mail gem,一整個砍掉重練 API 大改。原本動態的 Notifiter.deliver_welcome_email 寫法變成 Notifiter.welcome_email.deliver,其中的 Notifiter.welcome_email 是一個 Mail::Message 物件。

* A Whole New ActionMailer
* Action Mailer in Rails 3
* 一個範例
* Inline Attachments for ActionMailer

8. Metal

* Rails3: 新的 Metal 機制

Ruby Taiwan 近況及 Ruby Tuesday #13

Update(2010/8/17): 投影片請見 Rails3 RC 發佈: 重點導覽

最近花了些時間弄了兩個 Ruby Taiwan 子站台。一個是 找文件,主要是翻譯文件,也包括了 Ruby 中文官網。另一個則是 找工作,一個免費的 Job Board。請大家多多支持。

另外就是 Ruby Tuesday 聚會第十三場確定了,因應 Rails3 RC 發佈,這一次的主題為 Rails3,包含三場小演講:

* ihower: Rails3 changesets
* tsechingho: Rails 3 專案產生器 (generator 實例應用)
* xdite: Upgrading to Rails3

時間: 2010/8/17(週二)晚上七點到九點半。
地點: 台北市 果子咖啡
報名網頁: registrano.com/events/ruby-tuesday-13

還有,我開了一個唯讀的 Mailing list 在 groups.google.com/group/ruby-taiwan,方便大家第一時間收到 Ruby Taiwan 的重要訊息,例如有什麼活動之類的。

Enterprise Rails: 資料庫篇

稿子是 2009 年底寫的,本來想一口氣連 SOA 的部份一起整理 (對 SOA 有興趣的話,可以看我之前的投影片 91 頁 ~ 119 頁),不過一直拖到現在。雖然內容還是有點些雜亂,而且現在流行 NoSQL (?),不過還是就貼出來吧。

這本 2009 年底的書,趁著轉職的空檔終於一口氣讀完了。有些重要的背景知識,如果你一直追所謂的最新技術,反而是學不到。這本書其實講 Rails 不多,前一大半都是在講 RDBMS 關聯式資料庫,後半則是 SOA 架構。

像 RDBMS 裡面的 referential integrity、composite key、DB View、triggers、materialized View 這些東西,因為身為 MySQL 使用者很少用到、Rails 也沒有內建支援,實際寫 code 好像也都在 application layer 應用層處理掉了,所以到底要幹嘛用的呢? 他們被發明一定有原因吧? 是在什麼情境上使用呢?

第四章 Database As a Fortress

作者非常強調資料庫的重要,一家企業最重要的資產是資料,而不是員工 (資料不見就全完了,員工沒了再找就有了)。Framework 會變、程式會有 bug、可能也不會只有 Rails 會去存取 DB (所以只用 ActiveRecord Validation 並不可靠),總之只有你的 RDBMS 可以保障資料的正確性,data integrity 長存。

PostgreSQL 也比 MySQL 適合企業應用,因為很多 SQL 標準 MySQL 並不支援 (很多直到 5.0 後才支援)。PostgreSQL 會輸掉市佔率的原因是 1. MySQL 背後有一家商業公司支持 2. PostgreSQL 有很長一段時間不像 MySQL 有提供 Windows 安裝包 (話說這本書對 MySQL 其實還蠻揶揄的)。

作者也不建議使用 migration,因為它沒有支援所有的 DDL (Data Definition Language)。反正只有跑一次,不像 DML (Data Manipulation Language) 會一直用,用 ORM 比較方便。

作者持續強調 referential integrity 對企業層級的上線應用程式非常重要。會說不重要的,一定是該死的 MySQL 使用者(died-hand MySQL users),千萬別因為你的工具(i.e. MySQL, Rails)沒有這個功能,就說不重要。

隨著資料越來越多,你的老闆會想要知道一些數據分析報表,這裡作者點出一個重要的議題:你的報表會害了你的網站!! 你越常去產生你的報表,你的網站就會越來越慢。”Report are killing your site!!” 為什麼呢? 因為去 DB 撈大量的資料做報表,是非常龐大複雜的 SQL 操作,你的 DB 的效能會被拖慢,整個網站的效能就跟著 down 下來。該怎麼解呢? 撈 report 的 queries 不要對 production database 做就沒事了吧?

這裡作者又對 MySQL users 揶揄了:因為大多數的 MySQL users 第一個想到的方案就是使用 master/slave 來解決,也就是讓 query report 對 slave DB 做,但是即時如此不會影響到 production 運作,也沒辦法解決 query 查詢很慢的問題。

這裡觀念上要區分的是 OLTP (Online Transaction Processing) 和 OLAP (Online Analytical Processing 這兩種類型的 query,前者是一般的讀/寫/更新,這種是會讓使用者可以馬上等結果,大部分的前台網站就是屬於這種。後者則是一個 query 就須需要收集上百萬的資料去分析,例如:有多少客戶買了產品A,又買了產品 B,依照地點跟時間。

因為 OLTP 和 OLAP 是如此不同,沒道理 database 的設計也相同。OLAP 要快,就必須使用 denormalized 非正規化的方式來存放資料。但是將正規化資料和逆正規化資料混雜在同一個 DB,則非常容易造成資料的混亂不同步,寫出 buggy 有臭虫的程式。

要解決這個問題的領域是使用獨立的 data warehouse 存放 denormalized 的資料。(書就到此打住了,作者推薦了 The Data Warehouse Toolkit: The Complete Guide to dimensional Modeling by Ralph Kimball 一書)

第五章 Building a Solid Data Model

Data layer 層級的 constraint 才保證一定正確,因為應用層的 Model validation 可以跳過,放在 code 裡也容易被改掉。作者也示範了這兩者都可以寫單元測試。

referial constraint 如果只用 Model 做,destroy 時就會失效。而 Model 要做 referial constraint 只能用 has_many :depentent 但是如果誤用了 :delete 就 orz 了。因此最保證的作法還是 data layer 做。

資料庫記得加 index 在 1. foreign key 2. 任何有 SQL where 條件的地方

第六章 Refactoring to Third Normal Form

3NF (三階正規化) 能做到 DB 資料不重複:只要不是 primary key、不是 foreign key、不是 intrinsic data(eg. name)、不是 measured value (例如 time, temperature),而是一個 literally bound data,都應該正規化出來新建一個 table。不先做,後來要加會十分痛苦,尤其在一個已經有資料的 production db 上。

範例中的有很多 table 都有相同的地址欄位定義:Postgres 支援 multipie inheritance,可以處理重複的 DDL,再搭配上 Rails 可用 plugin mixin 處理重複的 code。

這裡留下一個第八章才回答的問題,只有一個 primary key 沒辦法一次撈出 has_many 的 has_many 資料。多保留一個 foreign key 又可能造成 direct 和 in-direct 的資料不一定一致。

第七章 Domain Data

Domain data (指網站預先就有的必要初始資料,又叫做 seed data) 也應該使用 table 存,因為 1. 保持 referential integrity 2. 維護 3ND 跟擴充彈性。實作上則可以做成常數形式。

strategy patterns with domain tables 這招示範了將 Order PAYMENT_TYPE 變成 domain tables 來做,除了變成 constant object,情境是如果不同 payment_type 會有不同的 validation。首先變成 constant 後,可以順利將 validation code 都從 order 搬走。而再進一步 rails single table inheritance 和 template method 將 validation 分散到個別的 domain model。總之,作者將 domain data 的彈性做了非常好的示範。

雖然正規化會導致大量的 table 和 model,但是也因為如此每個都很小非常容易測試,bugs 也就容易集中在小區域容易找到。

第八章 Composite Keys and Domain Key/Normal Form

這章討論 composite key 的優缺,以及如何使用。

ID column 系統的優點 1. Rails 內建 2. 簡單,除了 primary key 之外皆可修改,物件的 primary key 一定不變 3. 提供與真實資料的間接性,因此也不需有修改 primary key 的機會。 4. unique key 好做

composite key 的優點就沒這麼顯而易見,也不一定用的到。他的用途在於提供一種特別的 data integrity。table 中不一定可以發現有 natural composite key, 但是如果有而你忽略他,可能會有大問題。這個情境就是:

只有一個 primary key 沒辦法一次撈出 has_many 的 has_many 資料,如果只是加上一個 reference key, 可能導致 refential integrity hole. 這時 composite key 才是唯一解決之道。

DFNF 比 3NF 更近一步保證 referential integrity 在複雜的 relationship 中。不能因為工具沒有,就覺得不重要。因為 Rails 沒有內建,導致很多人不知道 DK/NF or natural keys 等這些已經發展成熟的資料庫基礎理論,在設計 schema design 上而有很大的缺陷。

先來檢討 single column ID 是否應該用 Rails 內建的數字,首先找有沒有別的 unique column,再來覺得它是否不會 或 不常變更,特別是連編輯介面都沒有的 domain data 特別符合這個條件,如果是,則可以用 set_primary_key 換掉,移除不必要重複 id column。不過,如果不是 domain data, 我們就必須產生這個 primary key,用法是寫在 before_create 裡,另外要注意還是使用 self.id。一個額外的好處是,這些可能被當做 foreign_key 的 nature key,也是有用的資訊,不像本來的數字 id 一定還要去本來的 table 查。

這裡我有個疑問是 Is there a REAL performance difference between INT and VARCHAR primary keys? ,在我碰到的例子是,很多人擔心非 integer 當 key 會影響效能 :/ (題外話,很多人愛用 type code,但都被我建議改成 string constant ) 但是我想這差距的微乎其微,尤其在你沒有上百萬的資料列。重點還是,你選的 nature key 不需要有被修改的可能 :>

Rails 要支援 composite keys 有兩個方法,一個是使用 Dr. Nic Williams 的 plugin,一個是本書作者的 Rails-DK/NF hybrid 法:

具體的作法是,保留 ID column 欄位,但是 DB 還是加上 composite key 的 foreign key referential integrity 限制。好處在 Rails 裡面不方便改 primary key, 當有修改的需求時,使用 hybird 法就不錯簡單(不需要裝plugin)。但是回過頭想,如果你需要修改 nature key,可以先想想是不是最好的作法應該是刪掉舊的,插入新的。

一個小技巧是附寫掉本來的 writor,這樣就不需要手動設定 composite key 的值了。如果要改已經被 reference constraint 限制的 composite keys 怎麼辦? 直接改會爆,這時候需要使用 deferrable constraints 的機制和 transaction。

不過 hybrid 法的缺點是 1. 因為維護兩套 index key 的關係, 新增修改刪除的 index cost 比較高。 2. 需要多寫上述的 code 才能省掉手動設計的麻煩。

書沒寫哪個最好,看起來是如果非得有修改 nature key 的需求才只用 hybrid 法。

第九章 Guaranteeing Complex Relationships with Triggers

stored procedure 和 triggers
使用 PL/pgSQL 做例子。

第十章 Multiple Table Inheritance

Rails 的 polymorphic associations 功能讓你可以定義兩個 table 的關係,不需要事前知道是哪一個 table。

不過,polymorphic associations 違反了 referential integrity !! 原因很簡單,既然不知道 _id 會指到哪個 table,自然也就沒辦法在 DB layer 加上 foreign key constraint

什麼是多型?

要達成多型的方式,實作上要考慮的是 STI 或 MTI:前者 Rails 有內建,後者 Rails 有用了 polymorphic 方式來達成 XOR relationships,也算是一種簡易的 MTI。

這本書用了 logical 和 physical models 來分別描述概念上和實際上的切法

STI 適用於 subclass 共用很多 data。如果共用的不多,除了浪費 table, model 也會被 getter 和 setter 污染。另外因為 class name 寫在 table 了,如果要修改 class name 會很麻煩。

這裡提的 MTI 作法是,還是為了要用到的 foreign key 開欄位,並且也為每個關係加上 belongs_to,但是允許 null 值,因為要做 XOR on columns: 要用 boolean 做 XOR,兩個還好做,超過三個就複雜了。這裡的作法很聰明,轉成數字再相加,只要檢查是不是等於 1,用這樣的方法做 database constrant check。

都加了 belongs_to,要怎麼做到多型。這裡作者使用了 reflection 和 inheritance relationship 的技巧,在 parent object 上實作了 getter 和 setter method,厲害。我們也可以實作一個 Factory method 在 parent 上,來建構適合的 sub-class。

第11章 View-Backed Models

有些複雜的 join 和 conditions 單靠 active record 寫不出來(畢竟簡單好用的東西,畢竟是犧牲一些不常使用的功能),而必須寫一些很醜的 SQL 混雜 active record,但是這樣就失去使用 active record 的意義(與DB-independent)。如果要漂亮,只能先盡可能先撈出來,然後再從 application layer 層過濾資料,但是這樣又失去效能,畢竟撈資料是 DB 的專長。

解法是 view-backed model,materialized view。

database view 有兩個定義 1. named subquery 2. a table that is defined by an algorithm
後者的實作是,我們先用 SQL 定義 DB view,然後就可以用 ActiveRecord 代表那個 view/table,更 cool 的是,還可以加上 association!! 當然,這是 read-only 的,記得不要加 destroy。

subquery 的定義也表示,你不能對 view/ view-backed models 操作 insert/update/delete/reference/constraint/index。
references 也不需要,如果你要 reference,就表示你應該加到 view 裡,constraint 也是。

indexing 也無,畢竟是 just-in-time 撈出來的資料。但是本來的 index 欄位當然還是有作用。別氣餒,畢竟本來使用 View 的目的不是效能,而是為了讓 AR 更簡潔清楚。如果真的需要效能,下一章的 materialized view。

第12章 Materialized Views

materialized view 就是 cached 的 view。既然是 cache,就一樣有 cache 的議題: 資料的更新,何時由誰 expire?

materialized view 是 cache-complete copy of view, 也是全部都有留資料,不像 memcached 會把不常用或超過記憶體的資料移除。

幾個重點: 1. materialized view 既然是實體的 table,也就可以加 index 2. 要加上 refresh function 當偵測到 base table 有修改 3. 或是 invalidation function 如果不想馬上更新 4. 在 base table 加上 triggers 以偵測修改 5. auxiliart view, reconciler view 隱藏實作細節

不過看完還是覺得使用 materialized view 真是挺難的!! XD