顯示具有 Database 標籤的文章。 顯示所有文章
顯示具有 Database 標籤的文章。 顯示所有文章

2011年11月23日 星期三

使用 Django 的雜感

換工作後大概很少會用到 Django, 在這對它做個了結吧。

先說結論

對於不熟 web 又想用 python 開發的人來說, Django 仍是首選, 主要的優勢有:

  1. 官方文件超級豐富, 網路上文件也豐富, 也有出一些書, 不過書上內容應該沒官網新, 看官網就夠了。不夠的話再看原始碼也比較方便。
  2. 社群龐大, 有許多 middleware 和 plugin 可用, 像多人合寫 web 一定會需要 database integration, South 相當好用。其它像用 Facebook / Google / Yahoo 等帳號登入, 也都有整合好的套件可用。
  3. 框架本身實作了許多 web 相關功能 (像 session、cookie、cache、傳輸 zip 後的內容), 可避免犯錯 (像是有擋 CSRF), 很多東西是新手從來沒想過的, 但框架都有考慮到並且實作好了。在使用的過程中可學到這些知識。
  4. 極佳的向下相容。官方的 roadmap 會明確提到那些功能已 deprecated, 並會在未來的那一版移掉, 有滿長的過渡期。使用者升級的負擔相當小。
  5. 提供一組規範, 切開 model、view、control, 還有 class / table 命名規則等, 減少團隊合作的問題。

當初選 Django 的主因是第一、二點, 第三點稍微有想到, 而第四、五點是使用後得到的驚喜。

說完結論後, 要開始碎碎念我的不滿, 請各位看倌記得, 即使如此, 我還是推薦不熟 web 又想用 python 開發的人使用 Django, 理由如前所述。

關於 template

Django 內建的 template 不好用, 效率也差, 甚至在 FAQ 裡有一項提到「I can't stand your template language. Do I have to use it?」。有些人可能覺得 template 的效率不是重點, 瓶頸會在 database。當我費盡心力減少 SQL、改 schema、改index、改寫 SQL 將 database 花的時間壓到極致後, 卻發現 template 怎麼縮都要 0.1s, 讓我很無力。更別提在關掉 i18n / l10n 前, template 要 0.2s。看著簡單的 template 內容, 很難理解為什麼這樣的東西要花到 0.1s。

此外, template 的語法很受限, 不過到 1.3 版後 include 多了 with 的語法方便代入子版面後, 變得好用許多, 不然有類似這種簡單需求時要另寫 templatetag, 實在是多餘又不易懂。相關心得見之前寫的文章。實在是換掉也為難, 不換也為難。

關於 ORM

先來個免責聲明, 若是需要頻繁地寫多個不同的小型網站, 用 ORM 是利多於弊, 可減少重覆的程式碼。以下的論點基於我的個人經驗, 需求是長期維護一個資料量大且有嚴苛速度需求的網站。

之前已寫過幾篇 ORM 心得, 在《Django 和 Python 操作 database 時的額外負擔》提到實測大量數據的情況有多慢, 注意我的使用情境有超過百萬筆資料, 若資料量沒那麼大, 這個負擔較無所謂。

《撰寫資料庫相關程式的心得》有提到使用 ORM 的成本。這裡要補充的是, 無論如何, 我們都需要分開「資料庫的操作」和「邏輯操作」。而在程式裡直接使用 ORM 並沒有隔離好兩者。

舉例來說, 在頁面裡使用 get_user("fcamel") 會比 User.objects.get(name="fcamel") 來得好。想想要如何針對這個頁面寫 unit test, 會發覺後者仍和 database 有很大的相依性, 不容易 mock。或著換個說法, get_user() 的抽象程度比直接使用 ORM 高。get_user() 裡是使用 ORM 還是下 raw SQL, 都和使用者無關。當需要連續幾個 ORM 操作以達成一個目的時, 另外包函式的優勢會更明顯。

若能接受上面的論點的話, 會發覺 ORM 的優勢又少了一點。所以, 我個人的看法是: 重點是必須另外包一層 API 存取資料, 供邏輯操作使用。所以, 對前端開發者來說, 是否使用 ORM, 影響不大。但對後端開發者來說, ORM 的缺點遠大於優點。ORM 最吸引人的地方是提供不錯介面隔離 database, 可以有彈性地存取資料並且不用太了解如何寫 SQL。但隨著使用經驗漸增, 會發覺這些優點並非事實。但若一直不去了解 database, 不會發覺付出的隱性成本。

題外話, 聽說 SQLAlchemy 相當強大, 不知實際用起來效果如何。我後來淡化使用 ORM 的場合, 加上需要使用 South (基於 Django ORM 的 plugin), 不方便換掉, 就沒有研究 SQLAlchemy 了。

關於 test

內建的 django.test 相關模組不太好用, 而且執行 test 要先重建全部 table, 然後在每個 test case 前 truncate 全部 table, 效率不好。若能指定只 truncate 需要的 table, 可省下許多時間。通常 test case 是愈寫愈多, 實務上執行 test case 的效率相當重要。

使用 South 後更會有 production schema 和 test schema 不同的問題, 因為 production 用 South 建 schema, 中間可能用到客制化的 SQL 改 schema (如建 multiple column indexes), 但 django.test 不會呼叫 South, 而是用 Django 原本讀 models.py 建 schema 的方式。我後來改用自己寫的模組來建 test database, 沒研究後續發展, 不知後來是否有修正。

關於 coding style

Django 違反許多 PEP 8 的規則或 Python 精神, 像是:

  • 在程式碼裡面 import 別的 module (而非開頭), 並且有 circular import。
  • 有多種方法做一件事。我很討厭這點, 像自訂 login 的重導頁面卻沒成功, 除錯時很麻煩, 要搞清楚多種規則的執行順序, 才知道問題出在那。
  • 有許多 lazy initialization。我不確定這是否違反 Python 社群的 "explicit is better than implicit", 我個人不喜歡一堆 lazy initialization, 很難掌握程式的行為。

以上這些事對使用者有什麼影響? 當行為不合預期, 文件也看不出所以然時, 讀原始碼並加 log message 是滿有效率的除錯方法。上述都是我在研究功能 (像是如何使用 cache) 或除錯時, 讀原始碼遇到的困擾。

結語

除前面一再強調的「結論」外, 再多強調一下, 一個東西愈多人罵, 表示愈多人用, 出事也愈好處理。本篇不是建議別用 Django, 也不是反串推廣, 只是之前一年多的使用心得。

2011年1月8日 星期六

撰寫資料庫相關程式的心得

我是用 MySQL + Django,處理的資料量有小有大。資料量大的情況下,通常有上萬筆,甚至會到上億筆。相關心得大概分成四類,依實務經驗記錄一下心得。

是否應該使用 ORM?

我是使用 Django ORM,以下指的 ORM 問題可能不適用全部 ORM framework,但我猜大部份應該是半斤八兩。

剛開始不熟 SQL 時,很喜歡用 ORM,ORM 有些學習門檻,不過習慣後用起來相當順,也容易閱讀程式。但是在好寫好讀的背後,卻犠牲掉極大的效率。原因有幾點:

  • 需要大概了解 ORM 產生的 SQL,才知道如何寫出有效率的操作。比方說用到 foreign key 時,可以在取物件時順便 join。若沒特別處理,預設行為是參考到關聯物件才取資料,於是取一萬個物件並讀取它們的關聯欄位,就會多下一萬次 SQL。
  • 即使了解 ORM 各項操作避免一些地雷寫法,ORM 不見得能產生最快的操作方式。明顯的缺點是讀寫 N 個物件時,很可能會轉成 N 次 SQL,而不是一次。
  • ORM 為了提供一致的抽象介面,沒有支援各家 DBMS 完整的語法,減少一些最佳化的機會。如缺少批次操作,以及使用 force index、決定 join order、技巧性地用 IN 不用 range query 等。
  • 即使 SQL 沒有問題,產生 object 的時間成本比自己執行 SQL 取資料來得高 (見這篇),資料量大的時候會變成瓶頸。

我一開始寫的專案全用 ORM。第二個寫的大部份用 ORM 但遇到一堆難解的效率問題。第三個寫的開始刻意減少 ORM 操作。最後則是全面禁用 ORM。原因很簡單,弄懂 ORM 操作並做最佳化的時間,比直接寫函式封裝 SQL 操作多,而且最後達成的效率又較差。除此之外,複雜的 ORM 操作可能有 bug 或是令人誤會,導致取出不對的資料,看 ORM 產生的 SQL 才明白問題出在那。

愈懂 MySQL 後,愈覺得 ORM 不順手,最後就改成寫模組封裝 SQL 操作。結論是,若有意願硬啃 DBMS 相關知識的話,將時間投資在所用的 DBMS 上,會比學習 ORM 操作和理解背後運作方式划算。

也許有人會質疑不用 ORM 會增加換 DBMS 的成本,我沒這樣的經驗不清楚用了 ORM 能省下多少成本,相較於前述的問題,整體來說是否划算。至少我會選擇先專精一個 DBMS,還有自己寫模組隔離應用層邏輯和資料庫操作,減低轉換 DBMS 的成本。

Database migration tool

雖然我上面將 Django ORM 說得很慘,但是用 Django ORM 搭配 South 到是滿不錯的。South 是 Django 的 migration tool,提供一個框架維護資料庫的變動,並且可以偵測 Django model 的變化,產生對應改變 schema 的操作。在說明 South 的優點前,要先談談為何需要用 database migration tool。

使用 database migration tool 有兩個好處:

  • 記錄目前這版程式用的 database schema。既然程式碼需要版本記錄,database schema 當然也要一併記錄,才能確保每版都能正常運作。
  • 方便其他組員更新資料庫。更新程式碼後執行 database migration,就能擁有和其他人同步的資料庫。

當然,有很多方式可以達成以上目的,像是每次更新 schema 就 dump schema,並存成一個 SQL 檔存在 VCS 裡。我沒這樣做過,不知會有什麼大問題。目前只想到幾個小問題:不方便多人同時修改 schema,之後要 merge schema 可能會比較麻煩,特別是改到同一 table 時。不方便追踪 schema 各步的轉換,像是加入 table A、B、C 以支援功能 X。但是回頭翻 VCS log 似乎也能滿足這個需求。唯一無解的大概是有些情境拆開 schema 執行會比較有效率。像是先建好 table、填完實體資料後,再建 covering index。

若改成維護多個 SQL 檔,第一個起始 SQL 產生基本 table,後面的 SQL 都是「schema diff」,則方便多人同時開發。但要人工產生 schema diff 有點辛苦,沒記好每個操作,手動改完 table 後,要回頭比對差別才能寫出 schema diff,容易出錯並增加確認的成本。

除了滿足基本需求外,South 另外提供下列功能:

  • 提供單線前進的 migration 方式。能在各版本之間前進、後退。
  • migration 分成 schema migration 和 data migration。並且提供偵測 Django model 變化自動產生 schema migration 的程式碼。data migration 只是空殼,由工程師自己填程式碼。
  • 提供修改 schema 的 API,像是加減欄位、加減 index。

使用 South 的額外好處是,可以避開 Django model 的限制,像是不支援多欄 index、不支援使用不同的 MySQL Engine。用 South 的話,只要自己在 schema migration 裡用 alter table 修改即可。

不清楚別家 database migration tool 怎麼運做的,感覺這方面的工具有很大的發揮空間,值得了解一下各家工具提供的功能。目前遇到的最大困擾是,無法明確看出那些 migration 有相依關係,更新資料或程式時,不方便只執行有影響到的範圍,若更新在很前期的 migration,就得回溯到前面再重跑。

包工具箱

將資料庫操作和應用層邏輯分離的好處應該不用多說,使用統一的介面有其它好處,目前覺得最實用的是可以寫 try catch 自動記下所有出錯的 SQL,再依參數決定要吃掉 exception 或丟回應用層。由於出錯的 SQL 都有被 log,程式出錯時可以馬上找到有問題的 SQL,縮短除錯時間。

單元測試

我原本是用 Django 內建的方式重建測試資料庫,但是最近開始用 multiple database 後,遇到一些問題。由於我在 South 裡做了一些不合 Django 規定的操作,不想花時間理解 Django model 和 test 詳細的運作方式,最後決定自己寫簡單的模組來建置測試環境,速度也會比較快。還在小規模的試用中,看看之後能不能投多點時間打穩這塊,再來寫心得。

2007年7月20日 星期五

MySQL偽memory leak問題

Problem

我用的server是Debian 64bit (AMD64), DB server是 5.0.32,有8G memory,但MySQL使用一陣子後,用top和free會看到8G的memory都被吃掉,但沒有任何process用到這麼多memory,而且swap沒有被用到,關掉MySQL daemon後memory的用量也沒改變。

Solution

查到兩篇類似的苦主:

從第二篇裡找到解法:

Despite the fact that “top”, “/proc/meminfor”, “free” show me that 98% of all memory on server is used that memory is not really used by MySQL nor kernel (as I can see from /proc/slabinfo).

I wrote simple C app. which tries to allocate as much memory as possible calling malloc() in a loop I found how much free memory _really_ available. Looks like it is Linux reporting problem, but same time this _only_ happen to me with memory previously used by MySQL server. I can see memory returned back to free memory pool as soon as other applications exit.

上面說是top和free報錯數據,MySQL query結束後,query用掉的memory有還回去,但top和free的統計數據裡卻沒有減少。

我也寫了個 C app來試,如他所言一般,程式結束後memory回來了,但查了半天找不到單一process的maximum memory usage定義在那,有人說在limits.h裡,INT_MAX就是maximum memory,但顯然是錯的,我設到比它大的值,anyway,下面是我「要回」memory的 code,因為設到15*會超出上限被killed,只好設12*並同時跑多個,但想想這個數字也不太對,它比8G大,但跑起來大概只用了2G吧,不知道process到底怎麼跑的

#include <stdio.h> #include <stdlib.h> enum { MAX = 12000000000 }; char s[MAX]; int main(void) { int i; for (i=0; i<MAX; i++) s[i] = i; sleep(10); return 0; }

另外聽小豬說在Debian的MySQL 5.1上沒這問題,也許是已修正的bug吧。

備註:query cache相關指令

http://dev.mysql.com/doc/refman/5.0/en/flush.html
http://dev.mysql.com/doc/refman/5.0/en/query-cache-status-and-maintenance.html

The RESET QUERY CACHE statement removes all query results from the query cache. The FLUSH TABLES statement also does this.

還有可以用SHOW VARIABLES查環境設定,像是myisam_max_sort_file_size、max_binlog_cache_size。

2006年12月26日 星期二

PHP用mysql_connect() or mysql_pconnect()?

最近發現pconnect在high loading時可能因pconnection不夠而無法取資料,出現這樣的錯誤訊息:

Warning: mysql_pconnect(): Too many connections

但用connect的寫法,要保留打開的$connection做為sql query使用,coding上較麻煩,雖說可以寫成class,將$connection變數存在class內,避免這個麻煩。還有connect可能要自己free query result(如果keep connection較久的話),而pconnect不用處理free(但不見得沒問題 XD ),看了WordPress和SMF的code,它們都用connect,沒有用簡易的pconnect。這麼說來,大規模的PHP program,還是用connect自己管較好囉? :( 不清楚PEARPECL怎麼處理的。

偷懶的小東西還是繼續用pconnect吧 XD ,也可以透過tube DB or web server的方式避免out of pconnection,比方增加MySQL pconnection數,或降低web alive connection,在Apache裡是設MaxKeepAliveRequests和KeepAliveTimeout吧。