2010年4月29日 星期四

mysql 搬資料小技巧

若要將舊有 table 內的資料搬到新 table 內, 注意兩個小事可以加快操作速度:
  • INSERT ... SELECT, 省下 mysql client 取出資料的時間。
  • 先 INSERT 再建 INDEX。
關於第二點,在有數百萬筆資料的情況下,試的結果如下:
  • 先建 INDEX 再 INSERT:跑了十多分塞不到五十萬筆。
  • 沒建 INDEX 的情況下一分多鐘 INSERT 完畢,再建 INDEX 花了三分鐘左右。

2010年4月28日 星期三

hg serve allow push

官網 Publishing Mercurial Repositories 有寫, man hgrc 也有寫。不過我眼睛太大了, 之前都沒看到, 自己來備忘一下。

若想要立即有個可以和別人 push / pull 的 mercurial server 又不在意權限設定, 就在 mercurial repository config (`hg root`/.hg/hgrc) 裡這麼設:
[web]
allow_push = *
push_ssl = false
接著打 hg server -p SOME_PORT, 別人就能用 http://YOUR_HOST:SOME_PORT/ 和你同步資料了。雙方用 Windows 也能通哦!!

mysql 的 varchar 與 text

今天撞到一堆錯誤, 簡記一下。
  • text 不能下 index。
  • varchar 在 5.03 前最多占 256 個 bytes, 之後可到 65536 bytes。Ubuntu 8.04 上對應的版本是 5.0.51a。
  • varchar 只能 index 前 1000 bytes, 語法是
    ALTER TABLE table_name ADD INDEX index_name ( column_name (key_length))
  • 注意是最大數量的單位是 bytes, 但設 varchar 大小和 key length 的單位都是用 characters。若 charset 用 utf8_general_ci, 則 key_length 只能設到 300 多。不然會出現錯誤訊息: "Specified key was too long; max key length is1000 bytes"。
透過天瓏下單買的 High Performance MySQL 2/e 來了 (還有附書套啊!!), 之後來好好讀讀, 別再自己亂查亂試, 太浪費時間了。

從 mysql 中隨機取出千筆資料 (別用 order by rand())

前陣子看到這篇提到別在 mysql 裡用 order by rand()。若有 1m 筆資料, order by rand() 相當於呼叫 1m 次 rand() 再做排序。可以想見速度不會快。若想取出隨機的資料, 視情況有不同的取代方案。注意 django 裡的 order_by('?') 轉換後的 sql 就是 order by rand(), 別用它。

若 table 有個流水號 id, 且沒有刪過資料, 可以先產生 1 ~ max(id) 之間的數字, 再用 id in (...) 取出隨機的資料。用 Django 的 ORM 來表示大概長這樣:
from django.db.models import Max
import random

mid = MyTable.objects.aggregate(Max('id'))['id__max']
ids = random.sample(xrange(1, mid + 1), 1000)
MyTable.objects.filter(id__in=ids)  # 隨機取出 1000 筆資料

若有刪過資料的話, 可能就多取幾筆最後再取前 1000 筆, 或是先取出所有 id 存下來再 sample 吧。

我自己測的結果, 百萬筆的資料用 order_by('?') 要數秒, 但改用 id__in 的寫法則是瞬殺。

2010年4月26日 星期一

弄出 JavaScript 中 print 的替代品

2010-04-27 更新

經 ericskScott 提醒, 我才發覺有 console.log() 這樣的好東西。Firebug 和 Chrome JavaScript Console  都支援。console.log(someObject) 後將該物件變成清單, 可以點選展開細項。原文就當初玩 prototype 的記念物吧。

原文

初用 JavaScript 時, 覺得這語言很難搞, 沒有 print 我要怎麼除錯? 轉念想想, 沒有 terminal 在瀏覽器裡是要 print 給誰看。對不起 JavaScript, 我錯怪你了。

學一陣子 JavaScript 後, 發覺大家似乎用 alert() 或寫到網頁裡某個區塊來做出 print 的效果 (用 jQuery 的話, 就是 $('#myPrintArea').html(...) )。但被 ipython 養壞胃口的我, 希望能在 interactive console 下有類似 print 的功能。這樣方便一邊試一邊看結果。

直到看到 JSON.stringify, 才找到頗理想的解法。JSON.stringify(someObject) 會將 someObject 轉成字串, 這樣就能看到 someObject 的內容啦。最近常在 Chrome JavaScript Console 裡這麼用 , 效果頗不錯的。不知大家都用什麼方法做到 print 的效果呢?

附帶一提, 若懷念 Python 的 repr(), 可以利用 prototype 填入:
Object.prototype.repr = function() {
    return JSON.stringify(this);
};

於是就能用 someObject.repr() 達到類似 Python 中 repr(someObject) 的效果了。目前不熟 prototype, 不知這樣搞有啥副作用。 JavaScript 還滿有趣的, 今年學 Common Lisp 沒成, 改成來練 JavaScript 吧。明年再來碰 Scala, 而一直沒學好的 Haskell 和 Common Lisp, 之後有緣再說吧。

2010年4月23日 星期五

coding style: spaces vs. tab

最近有不少體悟, 很多事沒接觸到那個環境, 看再多資料、想再久也不會明白。但有時候有一點相關經驗後, 反而沒多久就明白了。像大家常說的「換個位置換個腦袋」, 我覺得也是類似的情境。

話說兩年前和 York 聊到這個問題, 當時 York 說他發覺強者都支持用空白縮排, 但看了很多文章, 仍不明白用空白到底好在那。我們小聊了一陣子, 仍沒有定論。

結果出社會工作沒多久, 我忽然明白用空白縮排的好處了 --- 它能確保程式碼在任何地方長得一個樣。

有些人可能會說在自己的 vim / emacs 裡設好 tab 的寬度, 也能做到大家的畫面一致, 又保有日後修改寬度的彈性。然而, 觀看程式碼的介面太多了, 可能的場合遠比我原本想到的還多。像是 vim、emacs、Eclipse (或任何 IDE)、email (還得看用那家 mail client)、VCS (GUI 和 console 版都有)、Issue tracking system、各家 browser, 很難全部都提供彈性的設法, 即使有, 一一設定也太累了。還是爽快點直接用空白縮排比較省事啦。

以我自己為例, 平時用 vim coding, commit 前會用 hg 或 hgtk 看 diff。偶而會用 Redmine 看 changeset。上網找程式讀碼時會用 browser 直接看部份程式, 像看 JavaScript / CSS 是免不了的。附帶一提, 學生時代時, 我只用 vim 和 Eclipse, 自然會天真地認為大家設好 vim 和 Eclipse 就好啦。

2010年4月22日 星期四

幫 python -m 加上 auto completion

完整程式在這裡, 設好 py_dist_path 和 py_dist_name, source 這個 bash script 就OK 了。比方說你自己寫的 package root 是 mypkg, 放在 /home/fcamel/mypkg:
py_dist_path=/home/fcamel/mypkg
py_dist_name=mypkg
source ~/.bash_completions/python_module

之後打 python -m mypkg 後就可以按 tab 補完。

注意, 我直接覆蓋原本 python 註冊的 complete function, 需要的話到 /etc/bash_completion 裡撿出 _python() 再把它們拼在一起吧。

An introduction to bash completion 將運作原理介紹得相當清楚, 看完就會做了。關鍵在於了解 bash 和 complete 的溝通方式。man bash 裡也有相關訊息, 不過直接啃還是太硬了。

django 的 group by

Django 用 aggregate 和 annotate 來表示 SQL 中 group by 的效果,讀完 Aggregation 的說明應該就會知道怎麼用。

SELECT MAX(a), MIN(b) FROM some_table 還滿容易的。像 Book.objects.aggregate(Max('price')) 相當於 SELECT MAX(price) FROM book。annotate 則是對 QuerySet 裡每筆資料做 aggregation, 比方 Book 和 Author 是 many-to-many relation, 可以用 Book.objects.annotate(Count('authors')) 算出每本書各有幾位作者。

注意若 group by 後還想繼續用 filter 之類的方法操作 aggregation 產生的 field 的話, 要改變 aggregate / annotate 產生的欄位名稱, 預設會用 field__operator 的名稱 (如 authors__count)。但名稱中有兩個底線時, 就沒法再塞到 filter 的 key 裡, 不知這算不算 bug 啊。所以若要選出有兩位作者以上的書時, 得寫成 Book.objects.annotate(nauthor=Count('authors')).filter(nauthor__gte=2)。

SELECT F, MAX(A) FROM ... GROUP BY F 比較難做, 要透過 values。先用 values 轉成 ValueQuerySet, 再用 aggregate / annotate。我寫這篇也只是要記 values 的連結而已, 因為 google "django group by" 看一會兒沒找到它......。最近碰 Django 的東西, 總覺得還是乖乖把文件掃完一遍比較快, 用關鍵字太難找了。

2010年4月17日 星期六

讀 coding style guide 了解語言細節

最近和朋友解釋語法問題時, 發覺 coding style guide 不錯用, 通常會說明為何要這樣寫而不那樣寫 (如放括號、用 const 等), 有耐性的人寫程式一段時間後, 偶而讀些這種手冊挺不錯的。比方說:
個人覺得 JavaScript code convention 堪稱一絕, 少數語言有理論根據說明 '{' 要放右邊, 別換行放左邊 (理由見 JavaScript: The Good Parts。)

不過這種手冊都挺硬的, 一天看完全部效果有限, 有疑問時查一下, 或是偶而讀一小段可能會比較有效。

database 和 raid 5

聽到 jnlin 說跑 MySQL 不要用 raid 5 後, 查了一些資料, 的確有不少人說這是 DBA (SA?) 的常識。最後詢問 skylight 一些 raid、硬碟的知識後, 總算弄明白原因。在這簡記一下。我相當不熟這些用詞, 以下極可能用錯名詞, 看時請小心, 有錯還請指正。

raid 5 在寫入資料時得先讀出原本的資料, 再重算 parity, 算 parity 本身不慢, 但卻因此多花一個讀取的動作 (7200 rpm 的 hd, 轉一圈要 8.3ms)。而 raid 1 不需要多花這個讀取, 所以寫入時比 raid 5 快。當然, 有不少方式降低 raid 5 寫入的負擔, 像用 cache 之後再補寫之類的, 不清楚這些解法的代價 (如硬體成本較高)。

而在讀取時, 若大多為 random access, 那 raid 5 或 raid 0 將資料分散到各顆硬碟也沒有幫助。甚至可能為了統一所有硬碟的讀取動作而比不做 raid 還慢 (不清楚會慢多少, 也許不嚴重)。

先看個會變快的例子。若下個 sql 從 table student 裡取出所有學生資料, 由於 raid 5 或 raid 0 已將資料分散到各硬碟裡, 可以同步讀回全部硬碟資料。理論上若讀入的檔案有 1G 大, 用 raid 5 分散到五個硬碟裡, 就能快上四倍 (用 raid 0 就是五倍)。

然而, 常見的讀資料情境卻是這樣。下個 sql 從 table course_student 裡取出所有修「音樂」的 student_id, 再用 student_id 取出 table student 裡的資料。可以想見, 單筆 student data row 的資料量很小, 但散佈在 table student 各處 (也就散落在硬碟各處)。於是 raid 5 或 raid 0 無用武之地, 得帶著全部硬碟一起 seek & rotate 到第一個 student data row 讀完 block 內容, 再跳到下一個 student data row, ..., 沒有省到時間。再加上 write penalty, 結果就是在 raid 5 上跑 database 反而變慢了。

若用 raid 1 的話, 寫入時仍是一般速度, 挺多為了同步兩顆硬碟而慢一點點。若 raid controller 寫得好, 讀取時甚至能分散到兩顆同樣的硬碟裡讀。至於 raid 10, 應該也會因大量的 random access 無法善用 raid 0 來加快讀取。

btw, 還有一些零碎的知識得弄清楚, 我的大概理解如下:
  • 硬碟有各自的 controller, 硬碟到 RAM 的頻寬遠大於全部硬碟的輸出率。所以若能用 sequential read 讀大量資料, raid 5 或 raid 0 可以快上數倍 (看用幾個硬碟)。
  • 硬碟讀和寫的速度差不多。
  • 硬碟慢在 seek (移動讀取頭到目標 track) 和 rotation (轉動讀取頭到目標 sector), 兩者時間差不多。
  • ZFS 自己搞 file system 和 raid control, 於是有機會做得比分頭搞好。
參考資料

    2010年4月14日 星期三

    virtualenv 運作原理以及Python 載入 sys.path 的過程

    今天試了 virtualenv 覺得滿有趣的, 就研究了一下它怎麼換掉 site-packages, 搞出獨立的 site-packages。關鍵在於 python 載入 sys.path 的過程 ( python 會依 sys.path 內的路徑找 package / module )。

    site 所言, python 預設會載入 site.py 來載入 sys.path 的主要內容 (但可用 python -S 取消這點)。這就是 virtualenv 切入的點, 它利用 python 找到 site.py 的規則, 讓 python 載入 virtualenv 修改過的 site.py, 於是就能掌控之後的載入內容 (如不要載入 global site-packages)。

    詳細的的規則參照 site, 這裡以 Ubuntu + python2.5 為例。執行 /X/bin/python 後, 若存在目錄 /X/lib/python2.5/ 的話, sys.prefix 會存成 /X (我試的結果是, 若找不到 /X/lib/python2.5/, 會存成 /usr), sys.path 的內容依序如下:
    1. 被執行的 python script 的位置。比方執行 python /home/fcamel/main.py, 就是 /home/fcamel
    2. sys.prefix + "/lib/python2.5/site.py"
    3. site.py 載入的東東, 懶得看到底做了那些事
    4. ...
    5. shell 內設的環境變數 PYTHONPATH
    6. ...
    剩下一些細節之後有機會再來弄清楚。從 site.py 裡沒看到 PYTHONPATH, 而 python 原始碼裡有類似的東西 ( Modules/getpath.c )。

    回歸正題, 建立 virtualenv 時, virtualenv 會產生一個目錄, 其中包含一個 shell script 。執行 script 後會換掉 PATH, 讓使用者執行 python 時會先執行到 virtualenv 放的 python。這個 python 和系統裝的 python 一模一樣, 目的只是要換掉 sys.prefix, 藉此控制 site.py 載入的路徑。比方說執行到 /home/fcamel/my_env/bin/python, 若它發現能找到 /home/fcamel/my_env/lib/python2.5/, 就會把 sys.prefix 設成 /home/fcamel/my_env, 於是就能載入自己放的 site.py 了 (即 /home/fcamel/my_env/lib/python2.5/site.py )。

    2010年4月8日 星期四

    Unicode, UTF-8, UTF-16, UCS-2 和一些程式語言的支援方式

    讀 JavaScript 書時, 忽然想到 Unicode 支援問題, 就複習了一下相關知識。有錯還請指正。

    什麼是 Unicode? 什麼是 UTF-X?

    • Unicode: 將全部語言用的字集統整成一張表。定義每個文字對應的數字, 像是 'a' 對應到 97,  '我' 對應到 25105。整張表含蓋的字集範圍介於 0 ~ 1114111 (0x10FFFF) 之間。
    • UTF-X: X bits Unicode Transformation Format。有了 Unicode 這張表, 再來就是定義怎麼儲存這張表。

    UTF-8、UTF-16 和 UCS-2

    • UTF-32: 原封不動地把 Unicode 表裡的每個數字用 4 bytes 表示。相當浪費空間。
    • UTF-8: 可以想成某種資料壓縮方式。使得常用的 ASCII 字元仍用 1 byte 表示, 中文、日文、韓文等象型文字用 3 bytes 表示, 比原本各國自己用的表示方法多 1 byte (如日文的 Shift-JIS、簡體中文 GB 和繁體中文 Big5)。文字處理軟體 (如記事本、瀏覽器) 得「解壓縮」這種文字編碼方式, 還原回 Unicode 裡定義的數字, 才知道它是那個字。
    • UTF-16: 0 ~ 65535 的字用 2 bytes 表示, 之後的用 4 bytes 表示。
    • UCS-2: UTF-16 的子集, 只有 2 bytes 的字元組。
    Wikipedia 的介紹很清楚地說明 UTF-8、UTF-16 如何動態地用 2 ~ 4 bytes 表示不同範圍的數字。設計的很巧妙, 值得一看。對程式設計師來說, 最棒的是沒有任何字元是其它字元的子字元, 於是字串比對演算法可以直接使用。像 Big5 設計不良, 有名的許功蓋問題, 實在是夢魘啊。

    程式如何明白檔案使用的編碼方式?

    有了這些編碼方式, 程式得先明白讀到的檔案用什麼編碼才能正常處理。像 HTML 的開頭:
    <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">”
    和 Python 程式的開頭:
    #-*- coding: utf-8 -*-
    都是用來說明編碼方式。

    程式語言支援 Unicode 的方式

    不同的程式語言, 支援 Unicode 的程度不同, 方式也有差異。所謂「支援」的意思, 就是程式能正確地算出字串長度、索引到正確的字等。

    Java 在 J2SE 5 前用 UCS-2, 從J2SE 開始用 UTF-16。JavaScript 似乎用 UCS-2 的樣子。而 Python 另外用了物件 unicode 來表示 Unicode (也是用 UTF-16)。在 Python 裡, 從 UTF-8 格式的檔案內讀入文字後, 文字的型別是 str, 若讀入的文字是中文的話, 它相當於 byte stream, 無法正確地操作字串, 得先轉為 unicode 才行。詳細的說明請見《All About Python and Unicode》, 講得超級清楚, 我之前就是讀這份弄懂的。

    2010年4月3日 星期六

    在 Ubuntu 8.04 上裝 Redmine 0.9.x

    關鍵在於 Redmine 0.9.x 需要 rails 2.3.5, 而 rails 2.3.5 需要 rubygems v1.3.1。但是 Ubuntu 8.04 上的 rubygems 不夠新, 也無法透過 gem 自己昇級 (gem update --system), Ubuntu 會跑出禁止 gem 自己昇級的訊息。之前才讀到《RubyGem is from Mars, AptGet is from Venus》, 想說系統管理和開發者戰得真激烈, 結果馬上就被這場戰爭掃到。

    評估了網路上各種解法, 最後決定直接裝 rubygems tarball, 用 gem 裝 rails 2.3.5, 希望之後爛也只會爛 ruby package (我不確定這樣做到底會如何)。

    總結安裝流程如下:
    1. 參照這篇裝 Ubuntu 上必要的 package:
      sudo aptitude  install build-essential
      sudo aptitude  install rails rubygems mongrel libmagick9-dev ruby1.8-dev
    2. 直接從原始碼裝 rubygems v1.3.1
    3. 參照 Redmine install 的流程
    使用 MySQL 的注意事項:
    • MySQL 資料庫設定檔裡除帳號等資訊外要加一欄 socket:
      production:
        adapter: mysql
        database: redmine
        host: localhost
        username: redmine
        password: my_password
        encoding: utf8
        socket: /var/run/mysqld/mysqld.sock   # Ubuntu's path
      
    • rake migrate 會出現錯誤訊息: "ERROR: Failed to build gem native extension."。參考這篇的解法:
      sudo aptitude install libmysqlclient15-dev
      sudo gem install mysql
    和 Mercurial 整合部份參考官方文件和作法如下:
    1. 在 Redmine 的 Repository 設定裡填入 Mercurial repository 的檔案路徑。
    2. 在執行 mongrel 的使用者的 $HOME/~.hgrc 裡加入信任 Mercurial repository 目錄的擁有者, 比方說我用 fcamel 跑 Redmine, 但 repository 的擁有者是 hg, 就要在 ~fcamel/.hgrc 裡加入:
      [trusted]
      users = hg
      
      不然會跑出 "Not trusting file hgrc from untrusted user" 的錯誤訊息。
    我懶得設 apache, 就直接跑 mongrel daemon, 用起來還頗順的:
    mongrel_rails start -e production -p 3000 -d
    

    Python 取出 call stack 的方法

    雖然明白 interpreter 有記錄每個模組內每個物件所在的行數, 也會在執行期間記住每行程式的執行順序, 卻不清楚要怎麼自己取出這些資訊?

    剛才順著 django-debug-toolbar 的程式一路往下追, 看到它用 traceback.extract_stack() 取出 call stack, 於是就看了一下這函式的內容:
    def extract_stack(f=None, limit = None):
        # ... 中略 ...
        if f is None:
            try:
                raise ZeroDivisionError
            except ZeroDivisionError:
                f = sys.exc_info()[2].tb_frame.f_back
        # ... 中略 ...
        while f is not None and (limit is None or n < limit):
            lineno = f.f_lineno
            # ... 中略 ...
            list.append((filename, lineno, name, line))
            f = f.f_back
            n = n+1
        list.reverse()
        return list
    
    自己丟 Exception 再抓回取出 call stack, 這作法真是太妙了, 相當地簡單易懂。

    在 Fedora 下裝 id-utils

    Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...