Drupal 皮、Web 骨:我如何為鉅額效能耗損止血並重新愛上 Drupal?1. Drupal 皮、 Web 骨:
我如何為鉅額效能耗損止血
並重新愛上 Drupal ?
DrupalCamp?Taipei?2013
Huei-Horng Yo 游輝宏
中央研究院 生物多樣性研究中心
hiroshi@gate.sinica.edu.tw
5. 本日案例
● 台 灣 生 物 多 樣 性 資 訊 入 口 網 【 開 發 版 】
http://dev.taibif.tw
● 讀比寫多
● 多種資料來源,衍生複雜的資料查詢、呈現要求
5
8. 原本的如意算盤計畫
● 用 Drupal 7 Entity & Fields 的彈性規劃欄位
● 用 Views 選出資料
● 用別人已經寫好的模組喔拉喔拉喔拉把站台功能
拼搭出來
● 用 Panels 把資料輸出到版面
● 最後把自主開發的成果寫成 Drupal 模組
8
10. Entity & Fields
● Fields 要先定義,定義後再實體化 (instancelize) ,
與指定的 Entity Type 組合成一個 Bundle
10
11. Entity & Fields
● Entity 負責描述這份 Content Type 有什麼行為,比
如 Taxonomy 有上下位階層關係
● Fields 負責資料儲存的欄位樣貌(清單、日期、
文字欄位…)
11
12. Entity & Fields
● 例 如 : 我 們 需 要 一 個 描 述 生 物 物 種 分 類 的
Taxonomy Vocabulary 稱為 Checklist
– 首先需要定義有哪些欄位
– 再把欄位嵌到這個 Vocabulary 後,最後才產生這個特
別用途的 Checklist 分類系統
● 在沒有 Entity 機制之前,得用很髒的方法來達成
同樣的需求
12
13. Entity & Fields
● 以物件導向程式設計 (OOP) 的觀念來講, Entity
像是在宣告 Class 與 Methods
● Fields 則像是在宣告 Properties, Attributes
● 一切看起來非常合理且優美,符合軟體工程
13
16. 使徒襲來
● 直到…
– 我們為了「彈性」、「模組化」切開的 Views ,背後
的查詢幾乎一樣,僅輸出以及加料處理不同
● hook_views_pre_build()
● hook_views_pre_render()
● hook_views_post_render()
– 因此,重複的查詢、複雜的加工處理,讓整個系統慢
了下來
16
20. 拉效能
● Cache ,快娶快取
– PHP APC https://drupal.org/project/apc
– Boost https://drupal.org/project/boost
– Drupal 與 Views 本身內建的 Cache 機制
● 在這個案例裡都是捨本逐末,沒有打到靶心
● 但是對於一般正式上線的 Drupal 網站來說,其實
都是效能調校的基本功
20
27. Drupal 不快,但是 Drupal 很好用
● 進可攻:
– 要做大案子, Drupal 也有設計得不錯的 API 和 coding
框架可遵守
– PHP 是個可以寫得太過自由奔放的網頁開發語言,對
於團隊專案的程式碼品質來說,是個隱憂
– Drupal 不只是 CMS ,亦是 framework
27
33. Use EntityFieldQuery!
● Why EntityFieldQuery?
– 如字面上的意思,泛用的 Entity 查詢工具
– 取效能與可維護性的折衷
– 效能:實際跑的 SQL 較 Views 簡潔
– 可維護性:物件導向式用法,好寫、好讀
33
34. Use EntityFieldQuery!
● 常用的方法
– entityCondition() 給定選擇 Entity 的查詢條件
– propertyCondition() 給定針對 Entity 屬性的查詢條件
– fieldCondition() 給定針對 Fields 的查詢條件
– count() 只想知道符合查詢條件的筆數
– range() 只要符合指定筆數範圍的資料
34
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);
????/* ... */
??}
}
35
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);
????/* ... */
??}
}
36
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);
????/* ... */
??}
}
37
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);
????/* ... */
??}
}
38
39. 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
42. Don't use Entity, use Web Services
● 我們的資料來源太多太雜,一股腦地收納進 Drupal
不見得是好作法
● 逆向思考,將既有的資料來源用 Sinatra 包成 API ,
讓 Drupal 透過 Web Services 取得 JSON 格式資料
● 從「程式模組」轉而採取 Open Data 達成起初的
「成果資源共享」目的
● 開發行動 App 等需求因此也變方便
42
43. 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
45. Less is More, More is Less
5226.24 ms → 79.11 ms
? 66x Speedup!
※ 皆在已清除快取資料下量測
45