發表文章

目前顯示的是 十二月, 2010的文章

自制彈性的 bash prompt + 解決問題的流程

最近開了幾個 branch 要切來切去, 每次還要下「hg branch」確認狀態, 有點麻煩。就找了一下有沒有人提供啥方案, 這種常見的痛苦, 應該早有人解掉了。結果找到 Prompt Extension, 完全符合我的需求, 官網文件很清楚, 設定也簡單。

滿足需求後想知道它怎麼做的, 關鍵在於如何每次執行 hg 指令就檢查並更新 prompt。只有用 bash 提供的 API 才能正確地更新 prompt, 其它手段應該都是無效的。翻了一下 Prompt Extension 原始碼, 發覺它並沒有 hook 掉 hg 的各個指令。於是我猜是 hg update 本來就會換 prompt, 改看 mercurial 原始碼。看了一陣子覺得不像, 於是回頭交叉比對看看, 是誰改了 prompt。

試了關掉 prompt / 打開 prompt / 不同 hg 指令, 最後發覺....... bash 每執行完一個指令就會重設 prompt 啊!! 從 Prompt Extension 學到的是, 設 PS1 時可以搭配單引號內加函式, 這樣每次更新 PS1 時, 可用函式視情況顯示不同的訊息。之前都用雙引號, 所以只有一次的效果。

換句話說, Prompt Extension 做的事是:
提供設 PS1 的範例。提供一個 hg 指令來 parse 格式化字串, 藉以顯示不同的 repository 訊息。總結此次解題的成果:
判斷問題有解: 成功。背景知識: 知道只有 bash 提供 API 才有機會, 算是成功。猜測解決的大概方向: 失敗。明明有了正確的知識, 結果卻先猜 Prompt Extension 解的。第一次修正猜測方向: 失敗。一廂情願地猜到 mercurial 身上, 急著跳下去解, 卻沒先確認這個假設。回頭驗證假設: 成功。搭配背景知識, 找到正確的原因。一開始猜的方向錯誤, 繞了一圈才找到做法。幸好我有提早回頭確認一開始的假設, 省了一些冤枉路。愈來愈熟悉「假設 -> 驗證」的樹狀解題流程, 漸漸抓到根本的解題方法, 再來就是增加經驗, 累積背景知識了。這種系統化的解題流程應該是大學時要學會的事, 結果到 26 歲才開竅, 唉呀呀。

mysql 取出最後 insert row 的 id

官網所言, 有設 auto-increment 的情況, 在 insert 完後, 可用「SELECT LAST_INSERT_ID();」取出最後新增的 row 的 id。測試結果不同 mysql client 各自記各自的 last insert id, 不用擔心 race condition。我原本誤以為這個 function 會取出全域的結果, 結果是我多慮了。

這篇提到 Python 可用 cursor.lastrowid 取出, 不過我試了沒效。所以就自己手工多下「SELECT LAST_INSERT_ID();」取資料了。

用 sort 依兩欄數字排序

我是用 Ubuntu 8.04, 附上測的結果

$ cat in 77 1 7 11 7 3 8 2 7 1 $ sort in 7 1 7 11 7 3 77 1 8 2 $ sort -n in 7 1 7 11 7 3 8 2 77 1 $ sort -n -k1,2 in 7 1 7 11 7 3 8 2 77 1 $ sort -n -k 1,2 in 7 1 7 11 7 3 8 2 77 1 $ sort -n -k1 -k2 in 7 1 7 3 7 11 8 2 77 1
結論: 不要寫成 "sort -n -k1,2", 要寫成 "sort -n -k1 -k2", man page 明明是寫前者啊...., 我是亂試試出來的。

用 mysqladmin 觀察細部變化

大致上熟悉 explain、profiling、show processlist 等的用法, 但還不熟用 mysqladmin 觀察更細部的變化。網路上的文件提到用 mysqladmin extended -r -i10 觀察 10 秒內各細部資訊的相對變化, 但項目太多, 很難觀察。

後來想注意到可以用 grep 幫忙過濾。比方說只關心是否用到寫入硬碟的暫存表, 就加個 "grep Created_tmp_disk_tables":
mysqladmin -uUSER -pPASSWORD extended -r -i10 | grep Created_tmp_disk_tables若不知要觀察啥 (我目前的情況...), 至少可以先去掉沒變化的項目:
mysqladmin -uUSER -pPASSWORD extended -r -i10 | grep -v "| 0   "還在摸索怎麼用較適當。

登出其它台 Gmail / Google Account

不知道怎麼登出其它台的 Google Account, 不過登出 Gmail 有同等效果 進 Gmail中間下方有個「Last account activity: X hours ago on this computer. Details」, 點 Details點「Sign out all other sessions」

Scale out, HA, backup (for mysql)

前一篇經 DK 說明, 發覺我沒有細分需求和方法。混在一起寫變得很亂。
Scale out目的:
加機器就能應付成長的流量 (ref.)。作法:
Replication 可以應付大量讀取、少量寫入的情況。Sharding (partition) 可以應付大量寫入的情況 (各 server 應付不同段的資料)。High availability目的:
網站能持續提供服務。作法:
Master-Slave Replication: 寫入密集的情況下, Master 和 Slave 的資料有時間差,  掛掉 Master 後需要處理資料不同步的情況。MMM 或 DRBD 可以確保資料同步。MMM 不需暖機, 服務中斷時間最短。 DRBD 較易設定, 但需要暖機時間。Backup目的:  可以找回舊資料。 作法: Replication 可以做到「差不多即時」備份, 但若誤砍資料, slave 上的資料也「差不多即時」飛了。使用 replication 後, 可以在 slave 上固定間隔時間備份資料, 降低對網站效能的影響, 且不用停止服務。備份方式分為 logical 和 raw backup 兩種類型, 各有優缺點。搭配支援 snapshot 的 file system 用 raw backup, 時間和空間成本都很划算。DB 本身要搭配 crash-safe 的方案 (如 MySQL + InnoDB), 確保 server 掛掉時, 資料有保持一致。針對 InnoDB 做 logical backup 的話, 用 XtraBackup 較快。一定要測 restore, 我半信半疑的測了一下, 馬上發現少備份使用者帳號的 DB ......結論 Scale out、HA、backup 是三種不同的需求, 各自有不同的實踐手段。接下來會先: 做 logical backup: 易於實作, 資料量小時沒什麼缺點。做 replication: 協助 backup 和為 scale out 讀取做準備。將 DB 操作包在自己寫的 lib 裡: 之後需要 scale out 時比較好套。剩下的東西, 待之後有更深的需求再來細讀吧。 參考資料LiveJournal's Backend: A history of scaling, 五年前的文章, 了解作者機器架構的轉變仍很有收獲MySQL …

PNG 教戰守則

DK 那看到的, Does PNG work everywhere?

重點:
需要全彩但不用半透明的話, 用 JPEG用 PNG8, PNG24 太大了, GIF 也比 PNG8大用最佳化工具壓 PNG: find . -name '*.png' -print0 | xargs -P4 -0 -n1 pngout看不懂半透明部份的說明, 需要時再細看吧

Scale out, HA, backup

Scale out、high availability、backup 三者是不同的事, 之前對這幾個詞很陌生, 備忘一下它們的差別。
Replication 可以同時 scale out 讀取的操作和當作讀取的 HA, 但不是 backup。舉例來說, 不小心刪錯資料, slave 上的資料也一起飛了。所以 backup 要另外做。Replication 無法保證寫入的 HA, 因為 master 和 slave 之間會因延遲同步而少資料。針對寫入操作的 HA, DRBD 看起來是最穩且易於實作的 方案。但 DRBD 沒有附帶 scale out, 備份機就是待機狀態。MMM 可用作寫入的 HA, 備份機可充當讀取的 replication。Sharding (partition) 用來 scale out 寫入的操作, 沒包含 HA。結論是 backup 要分開規劃, scale out 和 HA 也要分開規劃。寫入量不大的話, 可以先用 replication 擋著。反之, 要用 sharding 來 scale out, 用 DRBD 或 MMM 做 HA。

順便記一下 backup 的心得:
Backup 分為 raw backup 和 logical backup, 各有利弊。若 file system 有支援 snapshot, 在 slave 上做 raw backup, 不管是執行時間還是占用的空間, 都挺划算的。切記要搭配 crash-safe 的方案, 如 MySQL + InnoDB。針對 InnoDB 做 logical backup 的話, 用 XtraBackup 較快。一定要測 restore, 我半信半疑的測了一下, 馬上發現少備份使用者帳號的 DB ......大概有個概念了, 接下來先做 replication 和 backup, 將 DB 操作包在自己寫的 lib 裡, 待需要 scale out 時比較好套。之後有更深的需求再來細讀吧。 參考資料LiveJournal's Backend: A history of scaling, 五年前的文章, 了解作者機器架構的轉變仍很有收獲MySQL 的 DRBD 與 MMM (1)MySQL HAMySQL (MyISAM) 的備份XtraBackup:線上備份 InnoDB 的…

varchar 與 text 的效能差異, 以及 order by 的運算方式

今天和同事 S 說明 MEMORY engine 512 bytes 限制時, 被問到用 varchar 超過 512 bytes 的話, 和 text 有何差別。想了一會兒才想到使用 text 會讓 mysql 用比較慢的方式排序, 在這裡小記一下。

在官網 7.3.1.11. ORDER BY Optimization 裡寫得很清楚, 使用 order by 或 group by 時, MySQL 可能會用到 filesort (可用 explain 看 extra 欄位確認)。filesort 有兩種版本:
original filesort: 取出符合條件的 rows, 只留 order by 裡的欄位和 row position。排好後再回頭一個個依 row position 取回需要的欄位。換句話說, 會從硬碟讀兩次同樣的資料。modified filesort: 取出符合條件的 rows, 將需要的欄位和 order by 裡的欄位一起排。只會從硬碟讀一次資料。舉例來說, 若是 select a from table order by b: original filesort: 先用 b 排序, 再依排好的結果取出各 row 的 a, 所以會有一堆 random access。modified filesort: 排序 (b, a ), 排好後就是最後結果。mysql 會盡量用 modified filesort, 以減少重覆讀硬碟 (更何況第二次還是 random access。) 但發生以下其中一個情況, mysql 會選用 original filesort: 用到 text 或 blob 時。單筆資料 (即上面範例的 (b, a)) 超過 max_length_for_sort_data, 單位為 bytes。除了避免使用第一種的 filesort, 說明文件最後有提供幾個最佳化的作法, 可以調一些參數。 2010-12-16 更新original filesort 不見得會比 modified filesort 慢, 今天就踏到雷, 調大 max_length_for_sort_data 讓 mysql 使用 modified filesort 反而慢兩倍多, 原因如文件上所言, 單一 row 變大, buffer 能放 row 的數量變少, 增加…

javascript non-blocking download 的原理

試了 head.js 的測試頁, 覺得很有意思, 為啥用 head.js 可以快這麼多, 就去看了 head.js 的原始碼, 看起來是 scriptTag 這個函式負責載入檔案。但是我不明白為何這樣可以加快載入速度, 畢竟瀏覽器的連線數量沒變, 為何用同樣的瀏覽器 (我用 Chrome 試的), 速度卻差這麼多。

在網路上查了一陣子, 看到 jnlin 寫的《Head JS : The only script in your HEAD》, 得知關鍵在於瀏覽器如何處理 tag <script>, 接著就查到這篇: 《What is a non-blocking script?》, 才明白前因後果。

摘要如下:
只有一個 thread 在執行 javascript。這意味著 UI rendering 和其它 js 操作都是同一個 thread 負責的。 載入 script tag 時, 瀏覽器怕 script 裡有 js code 會改變 UI, 所以會等 script 載入完並執行完才繼續 rendering。相對來說, 下載 javascript 檔比執行 javascript 慢得多。若寫程式的人知道這段 script 不會影響之後的 rendering, 有機會將下載的時間切開, 省去下載時間。做法有兩種: 第一種是動態地增加 script tag, 於是在讀入網頁時, rendering 不會被 script tag 卡住。第二種是用 HTML5 的語法 async, 目前只有 Firefox 3.6 有支援。結論是 javascript 是單 thread, 無法平行化執行, 但可以平行下載檔案 (視最大連線數)。並且有機制讓下載檔案不會擋住網頁 rendering, 但程式設計師得自己處理 javascript 相依性 (執行順序)。head.js 提供了不錯的介面解決這問題, 不過也有些小細節要程式設計師自行處理。head.js 的相關說明見附錄, 我有興趣的是為啥它可以加快網頁載入速度 (實際上只有 rendering 完主頁的速度, 不是整體時間)。 head.js 的說明: Head JS : The only script in your HEADheadjs - The only script in your <HEAD>

刪除大量檔案的方法

當一個目錄內有上百萬、千萬個檔案時, rm -rf 會慢到很糟糕的地步, 而且砍檔案時會吃掉一堆 IO, 做其它事只要一開檔、關檔, 就會頓一下。

上網查了各種說法, 要驗證何者的說法最合理, 需要讀 rm 的原始碼和 benchmark, 所以就不提了。有人提到一次 rm 多個檔案會比每個檔案都執行一次 rm 快, 減少 fork 和 execute 的時間, 這到是很合理。不確定在要刪除目錄的情況下, rm -rf DIR 和批次砍掉 DIR 下的檔案, 何者較快。

後來發現以前寫的 《/bin/rm: argument list too long 的解法和原因》 滿有用的, 我可以用 find + xargs 只砍部份檔案, 分多次砍完。這個作法的另一好處是, 可以在沒人用的時候放著讓它砍部份檔案 (像是先砍 a*, 再砍 b*, ...), 方便分階段處理, 只要每次砍檔案的時間不長, 不會和人使用的時間重疊到, 就沒問題了。

自動防護惡意 ssh / apache2 連線的方法

備忘一下, 有空再來試。

通用軟體
ban2failapache2
mod_evasivemod_security (Debian / Ubuntu 似乎因 license 的原因, 沒有內建 package)ssh
sshguarddenyhostBtw, 一但找到一個相關的軟體名稱後, 用它到 stackoverflow.com 裡搜, 就會找到不少相關軟體。利用比較性質的文章來找相關套件, 似乎是還不錯的作法。

atime, noatime 和 relatime

摘要讀這篇 Difference between noatime and relatime mount options 的心得:

atime: 會記錄最後讀取時間, 換句話說, 即使只讀資料, 也會寫入硬碟。noatime: 別記錄讀取時間, 會造成部份程式失常。比方說郵件閱讀軟體得比對最後讀取和更改時間, 若前者比後者小, 表示還有信件未讀。relatime: 只有當最後讀取時間比最後更改時間早的時候, 才記錄最後讀取時間。這解決郵件軟體的需求。結論: 用 relatime 安全又可提昇效能。不過我看 Ubuntu 8.04 灌好的時候就已設 relatime 了, 所以沒啥要改的。

更改 mysql conf 的注意事項

mysql 的設定檔有太多讀入的位置, google 一下 "mysql default config path" 會看到一堆文章, 由此可見這問題多令人困擾。

《High Performance MySQL 2e》提供一個不錯的找法:
$ /usr/sbin/mysqld --verbose --help | grep -A 1 'Default options' 我在 Ubuntu 8.04 上的輸出結果如下:
Default options are read from the following files in the given order: /etc/mysql/my.cnf ~/.my.cnf /usr/etc/my.cnf 另外要注意 App Armor 是否允許 mysql 讀它的設定檔。我參考書上的建議, 另外開一個 repository, 將系統相關設定檔放到 repository 裡, 再用 soft link 指回去 (例如: ln -s my-repository/mysql /etc/mysql)。結果啟動 mysql 後, 設定檔毫無作用, 也沒看到任何錯誤訊息。

更改 /etc/apparmor.d/usr.sbin.mysqld 讓 mysql 可以讀設定檔的實體位置後, 就解決這問題了。