解決 ASP.NET 網站重新編譯導致反應龜速的問題

5/30/2008
這篇的標題可能不是很恰當,因為最初只是想紀錄一下 ASP.NET 網站效能調校的一個小技巧,結果後來想起以前的一些效能調校心得,就一併整理進來了,所以後面的內容可能有點雜。如果你碰到類似的效能問題,可以看看第一段和第二段,後面的則大可跳過。

1. 問題

我們碰到的問題是,ASP.NET 網站的程式檔案數量很龐大,造成每次有共用的類別檔案(例如 App_Code 底下的檔案)更新時,「享用」此更新版本的第一個使用者就會因為整個網站重新編譯而等個好幾分鐘,網頁才出得來(其實看起來就像網站當掉了)。

網站的程式檔案數量有多大呢?我把圖抓下來:



22,714 個檔案,扣掉一些圖片、CSS、靜態網頁等程式檔案,保守估計也有兩萬個程式檔案。由於這個專案還挺大的(對某些人來說,這樣的規模或許還算普通吧),裡面很多子系統都還在陸續開發中,因此經常會有 user 火線作業時必須立即更新程式檔案的情況,例如:緊急 bug 修正。

結果,每當更新程式時,整個 ASP.NET 網站就像掛掉一樣,完全無法回應。用工作管理員觀察,可以看出是 ASP.NET runtime 正在編譯網站。雖然我們的系統有九台機器分擔負載,可是九台都是一起更新,也就是每一台伺服器都同時在重新編譯網站,因此當使用者正在火線作業時,會突然發現網站好像掛掉,持續大約五、六分鐘以後才恢復正常的反應速度。這樣的狀況我想沒有任何一個使用者可以接受,無論你怎麼解釋(如:第一個存取網站的人會比較慢)都很難平息他們的怒火。

2. 解法

結果是在 web.config 裡面加上這段就解決了:

<compilation debug="false" batch="true" maxBatchGeneratedFileSize="10000"
maxBatchSize="10000">

這樣調整之後速度差多少呢?現在每次更新檔案之後,第一個幸運的使用者只會感覺大約有三十秒左右的延遲時間(原本是五、六分鐘),之後又開始變快了。碰到這種情形,他們通常會認為只是網路短暫的不穩而已(該不會是被我們洗腦了 @_@)。

就這麼一行,速度差別竟然這麼大!怎麼回事?

當然我們也有調整別的地方,但是影響速度最大的還是前面那行設定。這主要和 ASP.NET 網站的編譯模型有關;由於我們的網站檔案數量龐大,因此每次網站重新編譯時,都會產生為數眾多的 DLL 檔。你可以參考 Dino Esposito 在 MSDN Magazine 的 Cutting Edge 專欄發表的文章:The Server Side of ASP.NET Pages。從這篇文章你可以了解 ASP.NET 編譯網站時會產生哪些暫存檔案。

文章裡面提到一個跟編譯模型有關的 web.config 元素: <compilation> 的 batch 屬性。順著這條線索,可以到 MSDN 網站上查到其他相關的屬性。你可以看到,batch 屬性預設已經是 true 了,所以關鍵處在於 maxBatchGeneratedFileSize 和 maxBatchSize 屬性。簡單地說,這兩個屬性會影響 ASP.NET 網站編譯時,要將多少個網頁編譯到同一個 DLL 裡面。也就是說,當你指定的 DLL 檔案 size 愈大,一個 DLL 就能容納愈多編譯的網頁類別,所以編譯時產生的 DLL 數量就會變少,這就是速度變快的主要原因(太多零碎的小檔案會造成頻繁的 I/O,以致於拖跨效能)。

當然囉,DLL 變大了,在記憶體的運用方面就沒有零碎檔案這麼靈活,但我們的機器有很充足的記憶體,用空間換取時間,很划算。

3. 小小心得

就像大部分的效能調校過程一樣,我們並不是第一次就找到癥結點。我們曾懷疑:
  • 硬體等級太差(這點很快就排除了)
  • 某些元件採用 J# 撰寫,造成 CLR 的負擔
  • 可能是 production 環境裡面包含許多 Subversion 版本控制檔案的緣故,增加不少編譯時間
但因為整個網站的檔案數量成長速度太快了,而且都是更新檔案時才出現反應龜速的情形(憤怒的使用者:「根本就是完全不動!」),因此很快就把嫌疑犯指向檔案更新時的網站重新編譯動作;而要解決重新編譯 ASP.NET 網站造成的速度延遲,自然就得從編譯模型下手了。

在我參與過的 multi-tier 架構的專案裡(不多,十個指頭數得出來),大部分都曾出現過效能問題。開發人員碰到這類問題時,往往不知從何下手,常見的反應是來個亂槍打鳥:這邊調一下 SQL、那邊資料表加個索引等等(大部分還真管用),如果都沒效,就擴充 CPU 和 RAM,先撐個一陣子再說。

效能調教方面的知識和技能,我覺得有點像保險,經常是沒碰到的時候嫌太多,需要的時候嫌太少。如果學習新技術時能夠盡量往底層挖,打好了硬底子,將來碰到效能問題時,會有更多 idea 和工具,知道有哪些面向要考量,定出效能調校的方向和策略。方向抓對了,問題可以說已經解決一半了。

4. 當年勇

在處理效能問題時,我通常是採取排除嫌疑犯的方式。

資料庫應用程式的效能不好,我會先想辦法排除掉硬體因素(也就是說,硬體都夠力)和網路通訊的因素。比如說,用一個簡單的程式丟到測試或正式環境中跑跑看,如果一個查詢五萬筆資料的 SQL 指令可以在 N 秒內完成,就可以大致確認整個網路和硬體環境沒問題。

說到這個,我想到以前開發過的一個專案,用 Java/JSP 寫的;客戶在台中,開發小組在台北。系統上線後,使用者跳腳:「按鈕按下去竟然要等四、五分鐘才跑出結果!」我聽了很訝異,在公司的測試環境怎麼測都只要幾十秒就完成了啊。由於客戶在台中,我也不可能隨時想到什麼 idea 就跑去客戶端測試,得採取決勝千里外的作法才行。因此,我在程式裡埋了一些測試效能的機制,說穿了很簡單:前端網頁按鈕按下時,用 JavaScript 在網頁的隱藏欄位裡記錄 request 發送的時間(request_send_time),後端 Java servlet 則會紀錄收到 request 的時間(request_receive_time),即將傳回 response 之前也會紀錄一次時間(response_return_time),這些時間變數都會一併傳回用戶端頁面,待用戶端收到 response 時,在前端網頁的 onLoad 事件中紀錄一次時間(response_reseive_time)。最後,在一個獨立的除錯頁框中顯示這幾個時間,就可以清楚看出幾個關鍵的時間區間:
  1. 從前端發出 request 開始到 server 收到的過程中花了多少時間。如果這段時間很長,那麼問題很可能出在網路傳輸。
  2. 後端 server 的處理時間。如果這段時間很長,再朝向資料庫設計、調 SQL、調程式碼的方向進一步追查。
  3. 後端 server 傳回 request 開始到前端網頁載入完畢所花的時間。如果這段時間很長,那麼問題很可能出在網路傳輸。
結果根據當時使用者回報的數據,整個執行時間主要都花在第 1 項和第 3 項,答案呼之欲出。使用者自己心裡也有底了,主動找自己的網管檢查,告訴我們是防火牆設定的問題。

那個計算處理時間的程式並不難寫,可是當我後來看到 ASP.NET 的網頁只要把 trace 功能打開就能自動計算每一次 request 在伺服器端花了多少處理時間,還是小小感動了一下。至於計算用戶端頁面傳送 request 至 server 端的傳輸時間,若不想自己動手,也可以試試其他工具,例如 Fiddler,它會攔截網頁的發出的任何 request(一張 gif 圖片也是一個 request),並顯示每一次 request 的效能統計資訊。

小結

談笑間,系統風馳電掣--這大概每個效能調校人員的夢想吧。不過,如果沒有平日不斷學習技術和累積經驗,每次碰到效能問題時恐怕還是容易流於亂槍打鳥。

《如何閱讀一本書》筆記

5/16/2008
書名:如何閱讀一本書
作者:Mortimer J. Adler and Charles Van Doren
譯者:郝明義

這本書的主旨,就如書名所揭示的,是在告訴讀者應該怎麼樣閱讀一本書,才能理解、吸收作者所要表達的東西,並且在獲得知識的同時,增進自我的閱讀與理解能力。

作者首先說明閱讀的四個層次:

  1. 基礎閱讀
  2. 檢視閱讀
  3. 分析閱讀
  4. 主題閱讀

然後分章節討論各種層次的閱讀應該注意的事項。在這些討論當中,作者不僅整理出關鍵的規則,同時提供許多閱讀活動的細微觀察與建議,而這些動作往往是我們在閱讀時未曾留意過的,既沒想過為什麼會這樣,也沒注意到自己是否有需要改正的地方;就像把閱讀的行為以慢動作播放,逐一檢視、分析每個動作。在閱讀這本書的同時,我們不僅能學到閱讀的方法與技巧,同時也對閱讀這個活動本身有了更深入的認知及敏感度。平常在看書時,往往只是跟隨作者的安排將字句逐一輸入腦袋,幸運的話,偶爾得到一些想法或靈感,讀過這本書之後,將更知道如何將一本書系統化地「納為己有」,以及如何分辨哪些書該細嚼慢嚥,哪些該囫圇吞棗。

作者在第一章便強調一個有效閱讀的基本要件:讀者必須採取主動。而主動閱讀的核心,則是在閱讀時要提出問題,並嘗試自己解答這些問題。對一本書至少要提出四個主要的問題,包括:

  1. 整體來說,這本書到底在談些什麼?
  2. 作者細部說了什麼,怎麼說的?
  3. 這本書說得有道理嗎?是全部有道理,還是部分有道理?
  4. 這本書跟你有什麼關係?如果這本書給了你一些資訊,你一定要問問這些資訊有什麼意義。為什麼這位作者會認為知道這件事很重要?你真的有必要去瞭解嗎?如果這本書不只提供了資訊,還啟發了你,就更有必要找出其他相關的、更深的含意或建議,以獲得更多的啟示。

而分析閱讀的規則與技巧,則圍繞著上述問題。當你確實完成了分析閱讀,也就能夠回答上面四個問題。

以下整理分析閱讀的重點,並摘錄部份內容。

分析閱讀的三個階段,十五項規則

第一階段:掌握結構大綱

規則 1: 確認書籍的種類,且要愈早知道愈好,最好早在開始閱讀之前就知道。

  • 光憑書名不見得能夠判斷書籍類型。
  • 區分理論與實用的書籍:凡是指南類的,告訴你該做什麼、怎麼做的,都是實用書籍。

規則 2: 使用一個單一的句子,或最多幾句話(一小段文字)來敘述整本書的內容。

規則 3: 將書中的重要篇章列舉出來,說明它們如何按照順序組成一個整體的架構。也就是抓出全書的綱要,再將各細部綱要逐一列出。

規則 4: 找出作者要問的問題,或者想要解決的問題。

這四項規則主要是幫助你回答前述的第一個問題:這本書大體上來說是在談些什麼?

第二階段:詮釋作品

規則 5: 找出重要單字,透過它們與作者達成共識。

  • 從一個讀者的角度來看,最重要的字就是那些讓你頭痛的字。
  • 利用上下文自己已經瞭解的所有字句,來推敲出你所不瞭解的那個字的意義。
  • 如果你不能自己努力去瞭解這些字,那就不可能學會我們所談的這種閱讀方法。你也不可能做到自己閱讀一本書的時候,從不太瞭解進展到逐漸瞭解的境界。

規則 6: 將一本書中最重要的句子圈出來,找出其中的主旨。

  • 從一個讀者的觀點來看,對你重要的句子就是一些需要花一點努力來詮釋的句子,因為你第一眼看到這些句子時並不能完全理解。
  • 閱讀的一部分本質就是被困惑,而且知道自己被困惑。懷疑是智慧的開始,從書本上學習跟從大自然學習是一樣的。如果你對一篇文章連一個問題也提不出來,那麼你就不可能期望一本書能給你一些你原本就沒有的視野。
  • 許多人認為他們知道如何閱讀,因為他們能用不同的速度來閱讀。但是他們經常在錯誤的地方暫停,慢慢閱讀。他們會為了一個自己感興趣的句子而暫停,卻不會為了感到困擾的句子而暫停。
  • 「用你自己的話來說」,是測驗你懂不懂一個句子的主旨的最佳方法。如果要求你針對作者所寫的某個句子作解釋,而你只會重複他的話,或在前後順序上作一些小小的改變,你最好懷疑自己是否真的瞭解了這句話。
  • 如果你沒法就這個主旨舉任何例子或作任何說明,你可能要懷疑自己其實並不懂這個句子在說些什麼。

規則 7: 從相關文句的關聯中,設法架構出一本書的基本論述。

  • 如果可以,找出書中說明重要論述的段落。但是,如果這個論述並沒有這樣表達出來,你就要去架構出來。你要從這一段或那一段中挑選句子出來,然後整理出前後順序的主旨,以及其組成的論述。
  • 一本好書在論述進行時會隨時作摘要整理。如果作者在一章的結尾為你作摘要整理,或是摘在某個精心設計的部分,你就要回顧一下剛才看的文章,找出他作摘要的句子是什麼。
  • 如果你先找到結論,就去看看理由是什麼。如果你先看到理由,就找找看這些理由帶引你到什麼樣的結論上。
  • 區別一段論述是用歸納法還是演繹法陳述。
  • 找出作者認為哪些事情是假設,哪些是能證實或有根據的,以及哪些是無需證實的自明之理。

規則 8: 找出作者的解答。

以上四項規則可幫助你回答前面所說的第二個問題:這本書詳細的內容是什麼?

第三階段:批評式閱讀

在這個階段中有七項規則,都與評論有關,其中規則 9~11 是評論的基本禮節,規則 12~15 為批評觀點的特別標準。這個部份提到不少關於有效溝通的技巧,我覺得特別重要,因此也摘錄比較多內容。

規則 9: 在你說出「我同意」,「我不同意」,或「我暫緩評論」之前,你一定要能肯定地說:「我瞭解了。」

每位作者都有被瞎批評的痛苦經驗。這些批評者並不覺得在批評之前應該要做好前面的兩個閱讀步驟。通常這些批評者會認為自己不需要閱讀,只需要評論就可以了。演講的人,都會碰上一些批評者其實根本不瞭解他在說的是什麼,就提出尖銳問題的經驗。你自己就可能記得這樣的例子:一個人在台上講話,台下的人一口氣或最多兩口氣就冒出來:「我不知道你在說什麼,但我想你錯了。」

對於這樣的批評,根本不知從何答起。你惟一能做的是有禮貌地請他們重述你的論點,再說明他們對你的非難之處。如果他們做不到,或是不能用他們自己的話重述你的觀點,你就知道他們其實並不瞭解你在說什麼。這時你不理會他們的批評是絕對有道理的。 (p.145)

在以下的兩種狀況中,你要特別注意閱讀的規則。如果一本書你只讀了一部分,就更難確定自己是不是瞭解了這本書,在這時候你的批評也就要更小心。還有時候,一本書跟作者其他的書有關,必須看了那本書之後才能完全理解。在這種情況中,你要更小心說出「我懂了」這句話,也要更慢慢地舉起你評論的長矛。 (p.146)

規則 10: 當你不同意作者的觀點時,要理性地表達自己的意見,不要無理地辯駁或爭論。

在柏拉圖的《會飲篇》(Symposium)中,有一段對話:

「我不能反駁你,蘇格拉底,」阿加頓說:「讓我們假設你說的都對好了。」
「阿加頓,你該說你不能反駁真理,因為蘇格拉底是很容易被反駁的。」(p.147)

把談話當作是戰爭的人,要贏得戰爭就得為反對而反對,不論自己對錯,都要反對成功。抱持著這種心態來閱讀的人,只是想在書中找出反對的地方而已。這些好辯的人專門愛在雞蛋裡挑骨頭,對自己的心態是否偏差,則完全置之不顧。 (p.148)

當必須同意作者的觀點,而不是反對的,也不要有難過的感覺。如果有這樣的感覺,他就是個積習已深的好辯者。就這第二個規則而言,這樣的讀者是情緒化的,而不是理性的。 (p.148)

一個人在與別人對話時,就算有不同的意見,最後還是有希望達成共識。他應該準備好改變自己的想法,才能改變別人的想法。他永遠要先想到自己可能誤解了,或是在某一個問題上有盲點。在爭論之中,一個人絕不能忘了這是教導別人,也是自己受教的一個機會。 (p.150)

規則 11: 尊重知識與個人觀點的不同,在作任何評斷之前,都要找出理論基礎。

  • 一個讀者如果不能區別出知識的理論說明與個人觀點的闡述,那他就無法從閱讀中學到東西。
  • 除了表達贊成或反對的意見之外,讀者還要作更多的努力。他必須為自己的觀點找出理由來。當然,如果他贊同作者的觀點,就是他與作者分享同樣的理論。但是如果他不贊同,他一定要有這麼做的理論基礎。否則他就只是把知識當作個人觀點來看待了。

規則 12: 證明作者的知識不足。

規則 13: 證明作者的知識錯誤。

規則 14: 證明作者的論點不合邏輯。

規則 15: 證明作者的分析與理由不完整。

注意:關於最後這四點,前三點是表示不同意見的準則,如果你無法提出相關的佐證,就必須同意作者的說法,或至少一部分說法。你只能因為最後一點理由,對這本書暫緩評論。

要瞭解的是,以上分析閱讀的規則是理想化的閱讀方法,能做到什麼程度,因人、因書而異。如書中所言:「許多書都值得精讀,但有更多的書只要瀏覽一下就行了。要成為一個好讀者,就要懂得依照一本書的特質,運用不同的閱讀技巧來閱讀。」

第十一章末尾有一段話我覺得很有道理:「一個讀得很廣泛,卻讀不精的人,與其值得讚美,不如值得同情。就像霍布斯所說:「如果我像一般人一樣讀那麼多書,我就跟他們一樣愚蠢了。」我們常聽到有人建議應該要大量閱讀,然而為了達到所謂的「大量」,往往會陷入為讀書而讀書的陷阱,讀書時囫圇吞棗,食不知味,而讀書的目的也容易流於吸收資訊以提供與朋友同事聊天的材料,充其量只是拾人牙慧,給人飽讀詩書的表面印象罷了。真正能帶給自己實質助益,提升自我的學識,還是得靠專精的閱讀與研究工夫。警惕自己!

第十二章是一些輔助閱讀的建議。包括:
  • 閱讀一本書時,另一隻手上還拿著一本字典,其實是個壞主意。當然這並不是說你在碰到生字時也不可以查字典。同樣地,一本書困擾住你時,我們也不會建議你去閱讀評論這本書的文章。整體來說,在你找尋外力幫助之前,最好能自己一個人閱讀。如果你經常這麼做,最後你會發現越來越不需要外界的助力了。
  • 一般人總是抱著熱忱想要閱讀巨著,但是當他絕望地感覺到自己無法理解這本書時,熱忱很快便消退了。其中一個原因,當然是因為一般人根本不知道要如何好好地閱讀一本書。但還不只如此,還有另一個原因:他們認為自己應該能夠讀懂自己所挑選的第一本書,用不著再讀其他相關的著作。
  • 除非你看完了一本書,否則不要看某個人的導讀。這個規則尤其適用於一些學者或評論家的導言。要正確地運用這些導讀,必須先盡力讀完一本書,然後還有些問題在干擾著你時,你才運用這些導讀來解答問題。如果你先讀了這些導讀,可能會讓你對這本書產生曲解。你會只想看那些學者或批評家提出的重點,而無法看到可能同樣重要的其他論點。
  • 我們一再強調我們反對——特別是第一次閱讀一本難讀的書時——一手拿著書,另一手拿著字典。如果一開始閱讀你就要查很多生字的話,你一定會跟不上整本書的條理。字典的基本用途是在你碰到一個專業術語,或完全不認識的字時,才需要使用上。即使如此,在你第一次閱讀一本好書時,也不要急著使用字典,除非是那個字與作者的主旨有很大的關聯,才可以查證一下。
  • 不要囫圇吞棗地將字典背下來。不要為了想立即增進字彙能力,就將一連串的生字背下來,那些字義跟你的實際生活經驗一點關聯也沒有。
  • 任何人不善讀一本字典開頭時所作的解釋以及所列的縮寫符號,那用不好字典就只能怪他自己了。
後面還有九章,分別介紹各種類型的書籍應該怎麼閱讀,例如:想像文學、實用書籍、科學、歷史等;最後一章談的是閱讀的終極目標。我想,這本書的筆記我就先做到這裡(大約一半),將來若有時間再寫後半部吧。

到博客來網路書店購買本書

UpdatePanel 與 Sys.WebForms.PageRequestManagerParserErrorException

5/09/2008
朋友問:「為什麼程式加了 ScriptManager 和 UpdatePanel 之後,原本沒問題的程式,卻出現 Sys.WebForms.PageRequestManagerParserErrorException?」

Eilon Lipton 有篇文章提供了很詳細的說明,包括該錯誤的意義、發生原因、以及解決方法:Sys.WebForms.PageRequestManagerParserErrorException - what it is and how to avoid it

常見的原因包括:
  • 在程式中呼叫 Response.Write 輸出資料至用戶端頁面。這會導致 UpdatePanel 無法對這些資料編碼。
  • 網站有使用 response filter。
  • 網站有使用 HTTP modules。
  • 開啟網頁的 trace 功能。
  • 在程式中呼叫 Server.Transfer。
那位朋友碰到的情況,是程式中使用 Server.Transfer 的方式轉址。改成 Response.Redirect 就解決了。

中文的「進行」式

5/07/2008
偶爾會在電視新聞中聽到主播用「進行」來表達某個動作,例如:

「消防隊正在進行滅火。」 (標準的現在進行式?)
「內政部長前往醫院探視受傷民眾,並進行安撫家屬的動作。」

有些文章也可以找到同樣的例子:

「信號必須解碼成分量形式,然後轉換成RGB格式,以便在監視器上進行顯示。」
「......因其發生於午餐用餐之前,且中毒學生均未進行用餐,其與午餐食物中毒之可能關聯性低。」

試比較修改後的版本:

「消防隊正在滅火。」
「內政部長前往醫院探視受傷民眾,並安撫其家屬。」
「信號必須解碼成分量形式,然後轉換成RGB格式,以便在監視器上顯示。」
「......因其發生於午餐用餐之前,且中毒學生均未用餐,其與午餐食物中毒之可能關聯性低。」

對於即時的演說及談話場合,在話中安插「進行」,似乎是一種相當保險的作法,不管後面怎麼接,乍聽之下都還可以理解,而且可以讓腦袋有多一點時間反應,思考後面的話要怎麼接。只是,看新聞的時候容易忽略小瑕疵,過耳就忘,文字卻可以反覆咀嚼、細細品味,如果文章裡有太多贅字,恐怕令人難以下嚥。

If I could teach one key to great writing, it would be this: Make every word count.
-- Stephen Wilbers, Keys To Great Writing

詩是簡鍊文字的極致表現,寫一般文章雖然不用像詩那樣精簡,但用字遣詞還是要稍微斟酌一下,以免寫出像「進行用餐」這樣畫蛇添足的句子。有些情況的確適合用「進行」,例如:進行雙邊經貿談判、某某活動正如火如荼地進行......等;不過,我們通常還有其他選擇,例如:實施、展開、執行、給予、予以等等,不見得什麼動作都得來「進行」一下。

也許,就像「的的不休」和「被被不絕」(註1),這種「進行」式語法已經不是新聞記者的專利,而逐漸成為一般人講話和寫文章時的習慣(通病)了。

忘了在哪裡看過這句話:「看太多新聞有損語文能力。」似乎有其道理。


註1:「的的不休」出自余光中〈論的的不休〉一文,旨在討論句子裡面使用太多「的」的現象;金聖華在《齊向譯道行》則有一篇文章討論中文翻譯的另一個通病:被動語句,她稱之為「被被不絕」。
技術提供:Blogger.