drupal 皮、web 骨:我如何為鉅額效能耗損止血並重新愛上 drupal?
TRANSCRIPT
Drupal 皮、 Web 骨:
我如何為鉅額效能耗損止血並重新愛上 Drupal ?
DrupalCamp Taipei 2013
Huei-Horng Yo 游輝宏中央研究院 生物多樣性研究中心
本簡報檔案內容,除引用材料另依其個別授權規定,歡迎諸方大德依 CC BY-SA 條款自由取用。
● The Druplicon image is licensed under the GPL License.● Any trademarks herein are the property of their respective owners. 2
原本的如意算盤計畫
● 用 Drupal 7 Entity & Fields 的彈性規劃欄位
● 用 Views 選出資料
● 用別人已經寫好的模組喔拉喔拉喔拉把站台功能拼搭出來
● 用 Panels 把資料輸出到版面
● 最後把自主開發的成果寫成 Drupal 模組
8
Entity & Fields
● Entity 負責描述這份 Content Type 有什麼行為,比如 Taxonomy 有上下位階層關係
● Fields 負責資料儲存的欄位樣貌(清單、日期、文字欄位…)
11
Entity & Fields
● 例如:我們需要一個描述生物物種分類的Taxonomy Vocabulary稱為 Checklist
– 首先需要定義有哪些欄位
– 再把欄位嵌到這個 Vocabulary 後,最後才產生這個特別用途的 Checklist分類系統
● 在沒有 Entity機制之前,得用很髒的方法來達成同樣的需求
12
Entity & Fields
● 以物件導向程式設計 (OOP) 的觀念來講, Entity像是在宣告 Class 與 Methods
● Fields則像是在宣告 Properties, Attributes
● 一切看起來非常合理且優美,符合軟體工程
13
使徒襲來
● 直到…
– 我們並不需要特別儲存 language 欄位
– 我們並不需要 revision 來做版本管理
– 有很多東西我們發現實務上並不需要
– 但是 Views都會過於好心幫我們一起撈出來
14
使徒襲來
● 直到…
– 我們為了「彈性」、「模組化」切開的 Views ,背後的查詢幾乎一樣,僅輸出以及加料處理不同
● hook_views_pre_build()
● hook_views_pre_render()
● hook_views_post_render()
– 因此,重複的查詢、複雜的加工處理,讓整個系統慢了下來
16
觀察效能瓶頸
● 伺服器端: https://drupal.org/project/devel
● 瀏覽器端:內建效能分析工具
● 找出是哪一個環節慢
– 先查伺服器端,再查瀏覽器端
– 伺服器端,根據經驗都是慢在資料庫這邊, PHP 本身則隨著版本改進愈來愈快,通常不會是瓶頸
17
拉效能
● Cache ,快娶快取
– PHP APC https://drupal.org/project/apc
– Boost https://drupal.org/project/boost
– Drupal 與 Views 本身內建的 Cache機制
● 在這個案例裡都是捨本逐末,沒有打到靶心
● 但是對於一般正式上線的 Drupal 網站來說,其實都是效能調校的基本功
20
拉效能
● 問題癥結應在資料庫
– 先天不良:絕大部分預裝好的 MySQL 配置,都不是針對做大事業的配置
– 請愛用 MySQL Performance Tuning Primer Script
http://www.day32.com/MySQL/
– 後天又太操勞:做了太多不必要的查詢
21
Drupal 不快,但是 Drupal 很好用
● 進可攻:
– 要做大案子, Drupal也有設計得不錯的 API和 coding框架可遵守
– PHP 是個可以寫得太過自由奔放的網頁開發語言,對於團隊專案的程式碼品質來說,是個隱憂
– Drupal 不只是 CMS ,亦是 framework
27
Use EntityFieldQuery!
● Why not SQL?
– 雖然 Drupal 有 db_query() 和 db_query_range() 這兩個做參數化查詢的函式
– 但是 SQL 還是不易維護
32
Use EntityFieldQuery!
● Why EntityFieldQuery?
– 如字面上的意思,泛用的 Entity 查詢工具
– 取效能與可維護性的折衷
– 效能:實際跑的 SQL較 Views 簡潔
– 可維護性:物件導向式用法,好寫、好讀
33
Use EntityFieldQuery!
● 常用的方法
– entityCondition() 給定選擇 Entity 的查詢條件
– propertyCondition() 給定針對 Entity屬性的查詢條件
– fieldCondition() 給定針對 Fields 的查詢條件
– count() 只想知道符合查詢條件的筆數
– range() 只要符合指定筆數範圍的資料
34
Use EntityFieldQuery!
$query = new EntityFieldQuery();$query->entityCondition('entity_type', 'taxonomy_term') ->propertyCondition('vid', taxonomy_vocabulary_machine_name_load('checklist')->vid) ->fieldCondition('taxon_unique_id', 'value', $unique_id) ->range(0, 1); $terms = $query->execute(); foreach($terms as $result => &$object) { foreach($object as &$term) { $term_entity = taxonomy_term_load($term->tid); /* ... */ }}
35
Use EntityFieldQuery!
$query = new EntityFieldQuery();$query->entityCondition('entity_type', 'taxonomy_term') ->propertyCondition('vid', taxonomy_vocabulary_machine_name_load('checklist')->vid) ->fieldCondition('taxon_unique_id', 'value', $unique_id) ->range(0, 1); $terms = $query->execute(); foreach($terms as $result => &$object) { foreach($object as &$term) { $term_entity = taxonomy_term_load($term->tid); /* ... */ }}
36
Use EntityFieldQuery!
$query = new EntityFieldQuery();$query->entityCondition('entity_type', 'taxonomy_term') ->propertyCondition('vid', taxonomy_vocabulary_machine_name_load('checklist')->vid) ->fieldCondition('taxon_unique_id', 'value', $unique_id) ->range(0, 1); $terms = $query->execute(); foreach($terms as $result => &$object) { foreach($object as &$term) { $term_entity = taxonomy_term_load($term->tid); /* ... */ }}
37
Use EntityFieldQuery!
$query = new EntityFieldQuery();$query->entityCondition('entity_type', 'taxonomy_term') ->propertyCondition('vid', taxonomy_vocabulary_machine_name_load('checklist')->vid) ->fieldCondition('taxon_unique_id', 'value', $unique_id) ->range(0, 1); $terms = $query->execute(); foreach($terms as $result => &$object) { foreach($object as &$term) { $term_entity = taxonomy_term_load($term->tid); /* ... */ }}
38
Use EntityFieldQuery!
$query = new EntityFieldQuery();$query->entityCondition('entity_type', 'taxonomy_term') ->propertyCondition('vid', taxonomy_vocabulary_machine_name_load('checklist')->vid) ->fieldCondition('taxon_unique_id', 'value', $unique_id) ->range(0, 1); $terms = $query->execute(); foreach($terms as $result => &$object) { foreach($object as &$term) { $term_entity = taxonomy_term_load($term->tid); /* ... */ }}
39
Use EntityFieldQuery!
● 盡可能以簡單的查詢條件把符合的 Entities 選出來
● 盡可能查出來的東西,放在變數裡,直到程式結束前都可重複利用,不需要再反覆多做工
● 妥善利用 MySQL本身的快取機制
– 別忘了前面提過的,很多為了 Drupal安裝的 MySQL 都沒有最佳化
– 查詢愈簡單,查詢愈快,建立快取同時也愈有效率
40
【友情宣傳】
● 請大家多多支持隔天的「六小時 View 一生:從入門到精通」練功坊
● 別被我們這個案例嚇到,事實上 Views處理絕大部分資料查詢場合依然很好用
– 所以我們仍用在新知動態列表之類的地方
41
Don't use Entity, use Web Services
● 我們的資料來源太多太雜,一股腦地收納進 Drupal不見得是好作法
● 逆向思考,將既有的資料來源用 Sinatra包成 API,讓 Drupal透過 Web Services 取得 JSON格式資料
● 從「程式模組」轉而採取 Open Data 達成起初的「成果資源共享」目的
● 開發行動 App等需求因此也變方便
42
Layout, Frontend
● 即使手刻程式,也要 follow Drupal Way
● 使用 theme_*()函數將資料所得結果輸出為HTML
https://api.drupal.org/api/drupal/includes!theme.inc/7
– theme_image()
– theme_item_list()
– theme_link()
– theme_html_tag()
– …
43