狠狠撸

狠狠撸Share a Scribd company logo
Drupal 皮、 Web 骨:
我如何為鉅額效能耗損止血
並重新愛上 Drupal ?
DrupalCamp?Taipei?2013
Huei-Horng Yo 游輝宏
中央研究院 生物多樣性研究中心
hiroshi@gate.sinica.edu.tw
本簡報檔案內容,除引用材料另依其個別授權規定,
歡迎諸方大德依 CC BY-SA 條款自由取用。
● The Druplicon image is licensed under the GPL License.
● Any trademarks herein are the property of their respective owners. 2
3
Drupal 不快?
現場調查
4
Drupal 不快,
但是 Drupal 很好用?
現場調查
本日案例
● 台 灣 生 物 多 樣 性 資 訊 入 口 網 【 開 發 版 】
http://dev.taibif.tw
● 讀比寫多
● 多種資料來源,衍生複雜的資料查詢、呈現要求
5
頭頭的熱烈請求
6
我們最終的開發成果,
需要可以和生物多樣性
社群共享!
開發者的燒腦運動
共享?那要做成模組?
用 Views 讓人自己選擇需要的欄位?
用 Entity 讓人彈性增減欄位?
便當還沒來嗎?
7
原本的如意算盤計畫
● 用 Drupal 7 Entity & Fields 的彈性規劃欄位
● 用 Views 選出資料
● 用別人已經寫好的模組喔拉喔拉喔拉把站台功能
拼搭出來
● 用 Panels 把資料輸出到版面
● 最後把自主開發的成果寫成 Drupal 模組
8
9
那我們上中二路的攻擊,
可說是毫無破綻嘛!
樂觀、開朗、朝著朝陽奔跑的開發者
喵。
Entity & Fields
● Fields 要先定義,定義後再實體化 (instancelize) ,
與指定的 Entity Type 組合成一個 Bundle
10
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
使徒襲來
● 直到…
– Fields 存在各自的資料表,欄位愈多, Views 背後跑
的 SQL 要 JOIN 的東西愈多,效能降幅愈恐怖
15
使徒襲來
● 直到…
– 我們為了「彈性」、「模組化」切開的 Views ,背後
的查詢幾乎一樣,僅輸出以及加料處理不同
● hook_views_pre_build()
● hook_views_pre_render()
● hook_views_post_render()
– 因此,重複的查詢、複雜的加工處理,讓整個系統慢
了下來
16
觀察效能瓶頸
● 伺服器端: https://drupal.org/project/devel
● 瀏覽器端:內建效能分析工具
● 找出是哪一個環節慢
– 先查伺服器端,再查瀏覽器端
– 伺服器端,根據經驗都是慢在資料庫這邊, PHP 本身
則隨著版本改進愈來愈快,通常不會是瓶頸
17
單是資料庫查詢就花了五秒多
眼花撩亂的 JOIN
18
頭頭的熱烈關切
19
太慢了。
嗚…
拉效能
● 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
頭頭再度熱烈關切
22
還是太慢了。
沒辦法,太多重複、複雜的
資料庫操作了…
啟用各種快取手段後…
頭頭再度熱烈關切
23
你要不要考慮砍掉重練、
不要用 Drupal 了?
……
等價交換
● 人不付出犧牲,便得不到任何回報;如果要得到
什麼,就必須付出同等代價。這就是鍊金術的基
本原則──等價交換。當時我們深信這就是世界
的真理。
(摘自《鋼の錬金術師》片頭)
24
25
等價交換
如果不要堅持做成模組「共享」,
效能問題可以用程式寫死來改善。
你好大我好怕,
這樣幹嘛還要用 Drupal 呢?
Drupal 不快,但是 Drupal 很好用
● 退可守:
– 許多現成、經過多年考驗的機制(會員管理、內容管
理、多國語言…)
– 小網站專案可以不用寫任何一行程式
26
Drupal 不快,但是 Drupal 很好用
● 進可攻:
– 要做大案子, Drupal 也有設計得不錯的 API 和 coding
框架可遵守
– PHP 是個可以寫得太過自由奔放的網頁開發語言,對
於團隊專案的程式碼品質來說,是個隱憂
– Drupal 不只是 CMS ,亦是 framework
27
28
開發者的決定
砍掉重練?不!
好啦,你搞得定就好…(抖)
問題妍希研析
● 「做了太多不必要的查詢」
29
對策
● 精簡查詢
● 且要維持程式的可維護性
30
幾乎拋掉 Views 不用
● 不適合本案例這種過於複雜的場合
● 改用 EntityFieldQuery 來查詢資料
31
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
Layout, Frontend
● 利用 Drupal 提供的函式,配合版型引擎,將程式
邏輯與結果輸出,分而治之
● 整個網站還是很優雅,不因為我們回歸手刻 PHP
而變得亂糟糟
44
Less is More, More is Less
5226.24 ms → 79.11 ms
? 66x Speedup!
※ 皆在已清除快取資料下量測
45
閉場黑
● 打到痛處,對症下藥
● 多寫幾行程式,您可以不用砍掉重練
● Drupal 進可攻、退可守,敬請愛用
46
感恩吶,我講完了
又到了大家喜聞樂見的發問時間
好怕被問倒…

More Related Content

Drupal 皮、Web 骨:我如何為鉅額效能耗損止血並重新愛上 Drupal?