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

47
Drupal 皮、 Web 骨: 我如何為鉅額效能耗損止血 並重新愛上 Drupal ? DrupalCamp Taipei 2013 Huei-Horng Yo 游輝宏 中央研究院 生物多樣性研究中心 [email protected]

Upload: huei-horng-yo

Post on 28-Jul-2015

2.039 views

Category:

Technology


4 download

TRANSCRIPT

Drupal 皮、 Web 骨:

我如何為鉅額效能耗損止血並重新愛上 Drupal ?

DrupalCamp Taipei 2013

Huei-Horng Yo 游輝宏中央研究院 生物多樣性研究中心

[email protected]

本簡報檔案內容,除引用材料另依其個別授權規定,歡迎諸方大德依 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

感恩吶,我講完了

又到了大家喜聞樂見的發問時間

好怕被問倒…