21 八月

自製 PostgreSQL 2-node HA

前言

現在的雲端很發達, 幾個按鍵就可以產出一台機器, 然後也可以配合廠商的服務來做 HA。不過問題來了, 如果今天就是一個小的自行架設的機房, 兩台電腦要自己搞 HA 那要怎麼辦呢? 自行上網研究了一陣子, 得到的答案就是用 heartbeat 之類的東西。在研究的過程中, 有想過 HAProxy 或是 DNS round robin 之類的 solution, 但這都無法解決一台當掉另外一台備援要起來變成 primary 的問題。於是在看了 How to setup HAProxy with failover? 這篇文章講到 Stack Overflow 就是用 heartbeat 來做 failover 的機制。

不過這整件事情都很困難, 原因是如果搜尋 heartbeat 找到的資料似乎都已經很舊了, Linux-HA 更指出我們應該要去 Cluster Labs 才對。然後在上面出現了更多工具與名詞, 實在是容易混淆。所以這篇文章就是一個簡單的 Tutorial 一步一步在 Ubuntu Server 14.04 LTS 上架設一個 2-node failover 的 PostgreSQL HA。

如果你想要有更多資源的話, Ubuntu 的環境底下, Cluster Labs 的文件要看 Pacemaker 1.1 for CMAN or Corosync 1.x: Clusters from Scratch。然而官方的文件有非常多的錯誤, 因此還是會踩到一大堆雷。Ubuntu 自己也有一些筆記, ClusterStack/Natty, 而 PostgreSQL 的則是 High Availability with PostgreSQL and Pacemaker 這篇文章。基本上就是使用到 corosync 與 pacemaker 這兩個套件做 HA / Failover, 用 DRBD 來做網路磁碟同步, 因此上網搜尋也可以找這幾個關鍵字。

下面的 Tutorial 其實就是根據 High Availability with PostgreSQL and Pacemaker 這篇為基礎的。

Read More

15 十月

Django 1.7 Migration

雖然之前就有用 South, 基本上是一樣的東西, 但是當 Django 1.7 把他納入成為 Migration 之後, 就有種扶正的感覺, 當然也開心的使用、試用。

其中一個我很喜歡的功能是 RunPython。他可以讓你執行 arbitrary code, 也許是新增一些你想要的 fixtures, 或是針對你想要的狀況作一些資料的轉換、增減等等。

不過如果你按照官方文件上面的作法, 可能有些 behavior 會跟預期的不同。例如以下的 code

def forwards_func(apps, schema_editor):
    # We get the model from the versioned app registry;
    # if we directly import it, it'll be the wrong version
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).bulk_create([
        Country(name="USA", code="us"),
        Country(name="France", code="fr"),
    ])

你可能會預期 Country 就是跟你現在 models.py 裡頭的 Country 一樣。但事實上, 他是依照整個 migrations 裡頭依序建構出來的 models。譬如說, 假設 0005_blablabla.py 定義了 Country, 但是上述的 procedure 是在 0004_blablabla.py, 那麼當 django 在 migrate 的時候, get_model 就會噴錯告訴你找不到 Country。

因此, dependency 要做好… (雖然 documentation 有提到, 但有時候看到 sample code 太興奮就直接用了 XD 最後 trace code 才發現是怎麼回事 orz)

另外還有一件事情就是他的 schema_editor 有提供一些 functions, 例如 alter_db_table(…) 可以把一個 table 改名字, 或是 create_model、delete_model 等, 但由於這些東西你可能會寫在 RunPython 呼叫的 function 裡頭, 導致 django migration 在 traverse 整個 migration tree 的時候無法追蹤, 因此就會造成不一致的狀況。其實目前也不知道這是該怎麼解… 所以… 就盡量避免使用吧。

21 八月

雲端平台使用心得分享

52621490_90f5660b77_z

johnmueller CC BY NC ND 2.0

好爛的標題喔 XD 小弟有幸在這兩三年使用了幾家不同的雲端平台, 想說在此分享給一些還沒有用過的朋友, 也許這些資訊有些幫助。

現在跟以前比起來, 真是一個幸福的時代。記得以前都要想辦法找一台電腦, 架個 Slackware、Redhat, 一直到大學的時候, 同學們都要把汰舊的電腦組起來裝個 FreeBSD 或什麼的來做自己的實驗機 (二號機), 裝 BBS, 裝 news server, 裝 Web server, 裝 MovableType… 裝 FTP server (orz…)

出社會後, 也曾經在自己家的客廳把舊機器組起來, 想說 VDSL 的上傳頻寬也多了, 但噪音以及熱度的問題很快的就讓我把他丟在一旁。這種東西怎麼可能會比得過按幾個紐, 就可以產生出一整台給你用的 Linux 呢? 現在的 VPS 或 IaaS 可以給的實在是多太多, 也方便太多了!

Amazon Web Service

首先, 第一個要介紹的就是最有名的 AWS。由於工作上的需要, 我們當時的整個 infrastructure 都是建立在 AWS 上的。Infrastructure 聽起來真威猛, 其實就是我們所有建構的服務 (對內、對外) 都是建立在 AWS 上的。

AWS 的 EC2 功能真的非常多, 也是當時我認為最完整的 (目前我想應該也是, 但感覺 Google Cloud Platform 緊追在後)。他們提供了一個 Free-tier 可以讓你用一整年免錢。當時 EC2 Free-tier 的機種是 t1.micro。聽起來很吸引人對吧? 但到底 t1.micro 用起來會是怎樣的呢? 簡單來說, 就是卡卡的 XD

apt-get update、apt-get upgrade 或作什麼 compiling 的動作會卡住, 在 terminal 打東西打一打也會卡住… 其實這樣的效能實用性真的不怎麼高, 所以還是使用了最便宜的付費方案 m1.small。

機房, 想當然爾的要選擇靠近台灣一點的機房, 而我們當時選擇的是東京機房。雖然很近也挺快的, 但是可以很明顯的感受到, 速度不及機器放在台灣學術網路的快。

使用 AWS 有另外一個好處, 就是可以使用 VPC (Amazon Virtual Private Cloud)。VPC 可以讓你自己設定你的虛擬網路, 不會跟別人的干擾, 可以自己設定 routing 等。如果你沒有使用 VPC 的話, 使用 EC2 的內網的網域是跟其他人共用的。但使用了 VPC, 你可以自己切 10.x.x.x 開頭的虛擬網路。如此一來, 其中一台可以架個 openvpn, 你就可以讓內部才能使用的 service 只聽 10.x.x.x 的 address 不對外公開, 也是個隔離的好辦法。

最後, EC2 有個叫做 Reserved Instance 的東西。在此要特別講一下。如果你會長時間開啟 EC2, 可以考慮購買所謂的 Reserved Instance。RI 的玩法如下: 你一開始要先繳一筆大筆的費用, 叫做 Upfront price。然後接下來的一年或三年的時間裡, 每一個月機器的價格最低可以低到原本的 25 折 (以目前 m3.small, 3-year heavy utilization RI 來算)。跟原本的方案比起來, upfront 費用加上最低 25 折的小時費率基本上還是便宜非常多。

聽起來很誘人是吧? 當你一確認使用此方案後, 你會先被扣該方案的 Upfront price, 接著, 他就會直接把當月的小時數 x 費率直接扣款, 接著使用該機型的機器使用的費用都會算成 0 元。但是有些地方要注意。首先, 你要確保你的機器真的是 24/7 都開著, 而且會開一年 (或三年)。如果公司的營運計畫有轉變, 甚至是 Amazon Web Service 推出了新的機種, 你可能就會有點後悔你跟他買了一個一年 (或三年) 約的機器了。

不過他基本上是可以賣掉的, 而且他提供了一整套的機器讓你可以線上賣出你不想要的約。然而目前只接受你有在美國的銀行開戶的戶頭才能進行這種交易。

Linode

嚴格上, 他無法跟 AWS 做比較, 因為這個純粹是 VPS, 而 AWS 應該算 IaaS。但其實他們一樣都可以拿來當作架站的底層, 因次還是合在一起講好了。選用 Linode 的好處就是他很簡單, 計價方式也很簡單, 功能也不複雜。開了一台, 選定 Linux distribution 跟一些基本設定如 swap、硬碟大小、root password, 然後開機! 你馬上就有一台全新的 Linux。

Linode 一樣最近的機房在東京。而他目前最低的費率是 USD 10/mo, 今年的新機種全面改用 SSD 硬碟。有這些改變應該是被競爭對手 DigitalOcean 追著打。不過在此沒有特別介紹 DigitalOcean 是因為之前在網路上有過一些這家供應商的爭議, 因此我就沒有使用過他們的服務了。

其實 Linode 能講的不多, 大概就是這樣子了。但使用上, 大概一兩年前使用的時候, 常常到了晚上會有點 lag, 這大概是使用上最大的麻煩吧。

對了, 另外, 在 Linode 的機器上你也可以配置 Private IP。如果你有想台 Linode 想要互連的話, 可以兩台都配置 Private IP, 就可以連線了。但這一樣要小心一點, 因為大家的帳號都是共用這個內網的。

Untitled-1

最後, Linode 的客戶服務回信的速度之快, 是非常嚇人的! 通常你發一個 ticket, 大概五到十分鐘之內就會有人回信, 我個人覺得非常威猛!

Heroku

這個就更不能跟其他比了, 但之所以會拿他出來講, 一樣, 之前曾經考慮過的省錢的 solution。他算是 PaaS, 你沒有 OS 的控制權, 而佈署網站的方式是透過 git push。

Heroku 很棒的地方在於他最低的方案是免錢的, 而大部分小網站的流量光是這個方案就可以應付了。不過他最致命的缺陷就是你可以控制得太少, 尤其是 filesystem 這一塊。我碰到的大問題就是在於, 我客戶的網站有 Content Management, 如果使用者要上傳 media, 一定要搭配 S3 之類的東西。可惜 Django 目前的 solution (django-tinymce, django-storages, django-filebrowser 三個搭配起來) 對於 S3 這種狀況會出很大的問題。每個小的 system call 都要透過 HTTP call S3 API, 很容易 timeout, 即使不 timeout 也要等非常久。

因為這樣的事情後來就放棄這個選項了。(所以這個平台我沒有真的讓 production 在上面跑過)

Google Cloud Platform

這算是目前我的新歡吧… 他最大的優勢就是在於他的機房在台灣! 在台灣!! 網路速度從台灣連非常非常的快。他的功能組比較像是 AWS, 他有內網可以建制, 然後開的機器有很多不同規格可以作選擇與調整。

然而, 目前 Google Compute Engine 提供的 OS 有限, 目前免費提供的有 Debian 7, CentOS 6, openSUSE, coreOS。我個人用 Ubuntu 用慣了, 因此在這上面選用 Debian。其實 Debian 也不錯, image 跑起來沒有亂裝一堆有的沒的, 但其實我覺得還是沒有到 Ubuntu 那麼人性化, 感覺比較 hardcore 一點。

Untitled-2

Google Compute Engine 的定價很酷, 因為當初他主打的就是他把 AWS Reserved Instance 的概念直接 Bundle 在他們所有的計價方式。因此 Google Compute Engine 你只要開機時間越久費率就越低, 不用在額外選擇年約的方案。而目前試用下來, Google Compute Engine 最低的方案 f1.micro (shared core, 0.6G RAM) 其實也不會像當初我使用 AWS t1.micro 一樣卡卡的。而這個方案組起來也是非常便宜。但最華麗的還是在於他的速度, 隨便開一台 ping 一下值可以在 8-15ms 左右, 相當驚人。

Google Compute Engine 如果你最簡單的方案開個一天兩天其實也不到一塊錢美金, 想要試用的人不妨試試看。

結語

試用了這麼多, 最後仍然要回歸到你的需求是什麼? 當初選用 AWS 是因為預計會大擴張, 因此當然是選擇這方面的領導品牌。然而, 領導品牌的價格也高出很多。如果需求轉向了, 公司所打造的服務轉向了, 那是否還需要用到這麼豪華的東西呢? 其實可能主要以成本考量, 是否在成本上也符合需求。

有些事情其實當初也真的沒有想過, 例如說使用了 RI 優惠的價格但綁約之後, 公司轉型而使用別家廠商, 又或者是雲端供應商提供了更新更好的機種… 而在選用這些服務時, 搬遷的工程, 使用 PaaS 帶來的限制導致需要更多開發或研究的工作項目, 這些增加的成本與帶來的效益是否值得呢? 但不可諱言的, 這些東西真的比以前自己動手組實體機器還要來得好多了!

17 四月

AngularJS + SVG 地雷

情況是這樣的。由於 html5mode 很漂亮, 不再有醜陋的 hash 在網址裡頭, 所以理所當然的就想辦法用這個模式。然而, IE9 並不支援 History API, 因此依照 AngularJS 的文件, 要弄個

<base href="/blabla" />

在 head 裡頭。

這都還不打緊, 但是當我想要使用 SVG 的 textPath 的時候, 他就爆炸了。textPath 是一個可以讓文字彎彎曲曲, 依照先前定義的 path 來編排的一個功能, 他的語法大致如下

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <path id="myTextPath" d="M75,20 a1,1 0 0,0 100,0" />
    </defs>

    <text x="10" y="100" style="stroke: #000000;">
      <textPath xlink:href="#myTextPath" >
            Text along a curved path...
      </textPath>
    </text>
</svg>

Reference: The SVG textpath element

在 <defs> 裡頭定義了一個 path, 然後接著在 textPath 裡頭會藉由 xlink:href 來指向這個 path。

此時麻煩來了, 由於 angularjs 定義了 base, 因此 xlink:href 就會自動解讀為 /blabla#myTextPath, 而造成失效 (原因是這段 svg 是動態產生的), 他確實是會想要透過網路抓取外部資料, 而我沒有任何方式可以把 base 的設定取消。

於是, 就造成了一種死結無解的狀態… orz

最後我用了爛招, 就是我把 SVG 搬移到外面, 並且給他參數讓他自動產生我想要的向量… 真的滿弱的… 只是, 這種組合又有多少人會碰到呢?

21 二月

政府實價登錄資料購買心得

是的, 前陣子因為在做 houz (好室聯搜網)行情系統, 所以開始接政府的實價登錄資料。當然一開始曉得有 Open Data 可以下載, 而最原始我寫的 import 程式也是接 Open Data 的 XML, 但是後來間接得知同一批資料可以用購買的, 而購買的資料多了每一個物件的座標。這可是精準多了, 我原本用的爛招很不可靠, Open Data 上的地址都是模糊過的區間, 例如指南路一段 1~50 號這種, 所以得到他座標的方式是使用 geocode 查詢指南路一段 25 號這種很不可靠的方式去做查詢, 非但座標一定不怎麼準確, 連帶著 geocode 也很有可能因為使用量太頻繁或量大而爆掉。想當然的, 我們就多了一些成本但提供較準確的資料。

但我覺得這一切, 內政部的系統都沒有做得很好。

系統安全

首先, 先來看一下登入畫面:

lvr-login

眼尖的人應該看得出問題在哪兒, 我第一次碰到這個畫面時我人在咖啡廳只用 public wireless, 所以我當下心裡只想著, 看來我非得要到我比較信任的網路才能使用這個網站的登入。要不然, 就是我只能用一個我很不常用的密碼, 否則到底誰聽了我的資料也都不曉得。雖然我不是非常專業的安全專家, 不過這個真的是, 連最基本的防護都沒有做。

收費方式

註冊成功後準備要申請資料時, 就會看到下列畫面, 包含著收費方式。

內政部及所屬機關提供政府資訊收費標準 條款內容
一、本標準依政府資訊公開法(以下簡稱本法)第二十二條第二項規定訂定之。
二、閱覽、抄錄或攝影政府資訊,每二小時收取費用新臺幣二十元;不足二小時,以二小時計算。
三、重製或複製政府資訊,依政府資訊重製或複製收費標準表(如附表),收取費用。前項收費標準表未明定之項目,按重製或複製工本費,收取費用。重製或複製政府資訊,如另需提供郵寄服務者,其郵遞費用以實支數額計算。
四、申請政府資訊供學術研究或公益用途者,於申請時應檢附相關證明文件,經核准後,除郵遞費用仍以實支數額計算外,其餘費用,減半徵收。
五、申請之政府資訊屬規費法第十二條第一款所定辦理業務或教育宣導資料者,得免費提供。
六、本標準所定之費用,其收取應依預算程序辦理。
七、內政部所屬機關業務性質特殊者,得另定提供政府資訊收費標準。
八、收費標準:資料量小於等於100mb 收費新台幣2000元整,資料量大於100mb 不足500mb 收費新台幣4000元整,資料量500mb以上 收費新台幣6000元整。

其實依照這種情況, 大概就可以猜得出來, 一開始他們預設的使用者應該是屬於做學術研究之類的人, 一次查詢某個區間的資料並且以量計價。但我們剛好不是, 我們是想要在最新一批資料揭露的時候, 就趕緊批次購買。很顯然上述這種收費方式對我們非常的不利。

資料期間 (申報期間) vs. 交易期間

但其實對我們造成最大困擾的, 是網站上的用字不清導致的。

lvr-apply

在申請 (購買) 時出現的畫面如上圖, 第一個出現的欄位為「資料期間」, 第一個印象想到的就是交易日期的區間, 也就是我們要 query 的交易日期區間。

然而, 當我們把資料購買下來後, 發現了交易日期很明顯有超出這個期間的範圍, 於是在聯絡了相關單位後, 才了解此「資料期間」是業者申報的時間, 而非交易的日期。業者很有可能在交易後一兩個月才申報, 因此會有時間差。

儘管相關單位如此解釋, 但網站上的頁面很清楚的是不一致的, 因為當我在查詢我所申請過的資料時, 該頁面卻長這樣:

lvr-query

字面上的混淆讓人完全摸不著頭緒。不論如何, 這個頁面上的「交易期間」一定是字面上的錯誤了。

資料重複與比對

購買下來的資料, 基本上是沒有 unique id 的, 因此當我們過了一個月, 準備要銜接下一個月的資料時, 發現有大量的資料重複 (肉眼觀察)。最基本的問題就是在於上一個段落寫的申報期間與交易期間的落差, 因為字面上的誤解導致我們第二次購買的資料有大批的重複現象。然而當我想要手動濾掉重複的資料時, 卻無法在我們這一端做, 原因有二:

  1. 資料並沒有附上任何的 unique id。而兩筆所有欄位都一模一樣的資料, 有可能是重複的, 也有可能是不重複的, 因此完全無法辨識。
  2. 申請購買資料時, 所用的是申報期間, 但購買下來的資料卻沒有這個欄位, 因此根本無法比對。

無法與 Open Data 同步

就因為上述資料重複無法比對, 且 Open Data 每兩週釋出的基準為「交易日期」兩個月前的資料, 但購買時使用的基準是「申報日期」, 因此根本無從與 Open Data 同步取得最新的資料。

另一個問題是購買時所使用的最小單位是月, 例如 102 年 12 月, 但是實價登錄每兩週就揭露一批新的資料, 因此根本無法每次只購買新揭露的資料。

若是有個方式可以比對重複的資料, 則每次範圍有些重疊, 再濾掉匯入過的資料即可得到正確的資料。但無論如何, 問題的最根源還是在於政府實價登錄釋出的基準與購買資料時完全沒有搭配在一起。

資料格式

Open Data 提供的格式為 TXT, CSV 與 XML。然而購買的資料卻是 XLS orz。這是一件很令人吐血的事情。不負責任的猜測, 原始資料是放在 Microsoft SQL Server, 而這些資料從 SQL server 選擇好欄位後直接 dump 到 Excel 檔。雖然吐血但還是勉強可以接受, 有總比沒有好。不知道哪一天可以像資訊大廠一樣, 直接提供 API 然後吐 json 出來, 這樣大家應該都很 happy。

其實我認為當前所有的 Open Data 應該都要提供這種串接方式。

結論

簡扼的做一個小結, 這個網站在使用時請不要在免費的無線網路使用, 以免帳號密碼或身份證字號被駭客取走。申請資料時的資料區間為申報日期的時間, 並非交易日期。而由於購買來的資料沒有 unique id 以及所謂的資料區間, 若是購買到重複的資料將無法辨別哪些是重複的。網站上的許多標示不清, 資料也無法預覽是目前這個系統很大的問題。無法預覽我根本無法知道最新的資料是到哪一個區間, 而購買就是 2000 起跳。

以上就是為什麼 houz 的行情拖了快要二十天才把 12 月份的資料發佈。這個農曆年一過完後, 我立即購買了去年 12 月的資料 (兩個月前, 也就是最新的一批), 就發現上述一大堆問題。最後為了避免重複, 等到 2/16 才把 12 月份整個月份的資料弄出來。

我其實大概可以理解為什麼購買的流程會無法搭配在一起, 我猜測是因為原本在規劃時, 就沒有想到會有這種 user scenario。然而我們之所以會用這種方式, 純粹就是為了要有座標。但無論如何, 都還是需要改進。比方說, 假設 Open Data 資料的欄位就已經可以滿足我們, 但還是需要使用到購買的這個流程, 原因是 Open Data 開放下載的只有當前的兩週, 如果我需要歷史資料外加之後所有的 update, 我勢必還是需要購買之前的資料, 然後他需要與 Open Data 的資料切齊, 才能避免重複。當然除非 Open Data 之後開放所有歷史資料就沒有這種問題了。

其實在這一次事情發生之前, 我就已經跟相關單位接洽過, 而取得最新資料的方式也還在跟洽詢, 希望政府的相關單位在這一塊可以做得越來越好。經過幾次聯繫得到的回應比我原本預期想像的好很多, 就繼續看後續的發展吧!

17 二月

postgresql 上的 COLLATION

由於一些大大們在我的 Facebook 留言討論了 postgresql 的 COLLATION 這件事情, 於是乎我比較仔細地做了實驗。postgresql 的 COLLATION 並無法解決筆畫排序這件事情 orz。

但其實, 我發現要在一個剛裝好的 postgresql 使用 zh_TW.UTF8 這個 COLLATION 還真是有點麻煩!

首先, 如果是一台 EC2 (Ubuntu 12.04 LTS), 裝好之後會發現根本無法使用 zh_TW.UTF8 這個 COLLATION, 原因是系統沒有裝, postgresql 也沒有 create。

因此要先經過以下步驟

1. 在 Ubuntu 把 locale 給 gen 出來

$ sudo locale-gen zh_TW.utf8
Generating locales...
zh_TW.UTF-8... done
Generation complete.

2. 要 Restart postgresql

$ sudo service postgresql restart

3. 要在 postgresql generate collation

$ psql maindb
maindb=> CREATE COLLATION "taiwan" (LOCALE="zh_TW.UTF8");
CREATE COLLATION

當然 taiwan 是我自己取的名字, 比較通用的話可以用 zh_TW.utf8 之類的。

其實這時候可以查看 postgresql 裡頭有的 collation

maindb=> select * from pg_collation;
  collname  | collnamespace | collowner | collencoding | collcollate | collctype  
------------+---------------+-----------+--------------+-------------+------------
 default    |            11 |        10 |           -1 |             | 
 C          |            11 |        10 |           -1 | C           | C
 POSIX      |            11 |        10 |           -1 | POSIX       | POSIX
 C.UTF-8    |            11 |        10 |            6 | C.UTF-8     | C.UTF-8
 en_US      |            11 |        10 |            6 | en_US.utf8  | en_US.utf8
 en_US.utf8 |            11 |        10 |            6 | en_US.utf8  | en_US.utf8
 ucs_basic  |            11 |        10 |            6 | C           | C
 taiwan     |          2200 |     16384 |            6 | zh_TW.utf8  | zh_TW.utf8
(8 rows)

4. 這時候才能使用 COLLATION 這個東西 orz

maindb=> CREATE TABLE test1 (a varchar(64) COLLATE "taiwan");
CREATE TABLE

接著就發現… 他的 sorting 還是無法使用筆畫排序

maindb=> INSERT INTO test1 (a) VALUES ('丁甲乙');
INSERT 0 1
maindb=> INSERT INTO test1 (a) VALUES ('乙丙丁');
INSERT 0 1
maindb=> SELECT * FROM test1 ORDER BY a;
a
--------
丁甲乙
乙丙丁
(2 rows)

maindb=> SELECT * FROM test1 ORDER BY convert_to(a, 'big5');
a
--------
乙丙丁
丁甲乙
(2 rows)

不死心直接在 ORDER BY 下 COLLATE 看看

maindb=> SELECT * FROM test1 ORDER BY a COLLATE "taiwan";
a
--------
丁甲乙
乙丙丁
(2 rows)

一樣的結果…

alas…

為了確認, 我又開了一個 database LC_COLLATION, LC_CTYPE 都換成 zh_TW.utf8, 然後再做一次一樣的實驗, 一樣無果。
所以基本上就是確認了, 即使設定了 COLLATE, 在 postgresql 一樣無法正確的使用排序。

(如果有人可以提供更好的解法, 那就太棒啦 :D)

13 二月

UTF-8 正體中文 (繁體) 的筆畫排序

好像很久以前, 就已經發現到, 中文的排序是有一些問題的, 但我就放著。直到昨天在處理某個頁面時, 我發現, glossary 之類的東西, 按照筆畫排序是很重要的, 所以就 Google 了一下。

一開始, 我以為是 postgresql locale 的問題, 找到了幾個 link, 但是都沒有下文, 而且重點是我一開始在 Ubuntu 裝 postgresql 的時候, 我沒有更改設定, 所以 zh_TW 並沒有進去。開了台新機器隨便測一測, 仍然沒有達到我想要的結果。

直到我赫然發現我整個方向都錯了。後來才發現, 字元編碼本身, UTF-8 並不是按照筆畫順序排的, 但 Big5 是, 所以可以把該欄位轉成 Big5 之後再用這個欄位排序。

以下可以找到很多 MySQL 的版本:

以下是 postgresql 的 sample code。一般來說, 原本的搜尋就是像下面這樣:


db=# select id, title from kb_article order by title;
id | title
-----+------------------------------------------
158 | 丁種建築用地
80 | 不動產奢侈稅可用公告現值並扣成本嗎
78 | 不動產奢侈稅的課徵時點為何
79 | 不動產的奢侈稅稅率為何
157 | 丙種建築用地
179 | 主建物
161 | 乙種工業用地
156 | 乙種建築用地
214 | 事故屋
33 | 交屋注意事項
12 | 交易安全機制
116 | 什麼是區段化、去識別化方式提供查詢

從上面可以看到, 乙只有一劃, 但是他並沒有排在最前面。


db=# select id, title from kb_article order by convert_to(title, 'BIG5');
id | title
-----+------------------------------------------
42 | 「銷售契約」是指公契還是私契
161 | 乙種工業用地
156 | 乙種建築用地
158 | 丁種建築用地
34 | 入住前準備事宜
175 | 土地改良物
153 | 土地使用分區
152 | 土地持份
94 | 土地相關
44 | 土地重劃後之「持有期間」如何計算
191 | 大公
220 | 女兒牆
192 | 小公
79 | 不動產的奢侈稅稅率為何
80 | 不動產奢侈稅可用公告現值並扣成本嗎
78 | 不動產奢侈稅的課徵時點為何
116 | 什麼是區段化、去識別化方式提供查詢

沒想到… 這年頭… BIG5 還有這個作用 orz, 不過在 django 裡頭就只能下 raw 不能直接用他的 ORM 啦!

10 一月

座標轉換

我一直以為, 只要是用到 GPS 座標, 都是使用類似這種「23.973875,120.982025」latitude,longitude 的表示方式, 頂多是時分秒轉換成十進位而已。天曉得, 我錯了! 今天向內政部不動產成交案件實際資訊資料供應系統買了資料之後, 才發現內附的座標是一個我完全沒有看過的格式, 至少它的橫坐標跟縱坐標不在一般台灣的經緯度的值上, 而是長得像這樣「248170.787,2652129.936」。

經過了一翻查證, 終於看懂這種格式叫做 TWD97, 而一般在 Google Map 輸入的那種則是叫座 WGS84。TWD97 可說是 UTM 的一種, 原本的 UTM 分為很多不同 zones, 但是台灣應該是因應需求, 而有些參數有些調整

查了 Wikipedia, 發現 UTM 的頁面有提到了簡易版的轉換公式, 雖然要把它看懂真的是對於數學能力已經退化很多的我來說非常困難 (論文1) (論文2), 但如果只是照著他的公式寫程式, 照理說應該是寫得出來的。

Google 了一下 TWD97 以及 UTM, 有一些詳細的介紹, 也有不少人寫了一些程式碼 (python UTM 套件) (Vexed’s TWD97 in PHP) (ola 的 python/java version), 但是似乎都是根據另外一篇文章 (Converting UTM to Latitude and Longitude (Or Vice Versa)) 所述的公式而成的。

於是乎, 我就在想是否能夠 implement Wikipedia 的那個版本 (in python)。所以, 東西就放上 github 啦! 兩個小 function, 也可以直接執行, 這個轉換似乎都是有點誤差的。

18 十月

ElasticSearch 的小雷

又一段時間沒 post 文章了。今天採到一個小雷, 但他也不算雷啦, 只能說我沒有看清楚說明文件。

ElasticSearch 可以使用 Lucene 的 Query Parser Syntax, 基本上就是所謂的 Query String Query。若是使用 pyelasticsearchelasticutils, 都有相對應的 function 可以直接下 Query String, 其實還滿方便的。因為我做了一個小後台, 讓我自己下 Query, 撈我想要看的 ElasticSearch 裡面的項目。

原本用得好好的, 但是我發現我想要查詢某個 Y 開頭, 或 S 開頭的東西, 不論我下 column_name:Y* 或者是改用 regular expression 下 column_name:/Y[0-9]*/ 或者是 column_name:/[a-zA-Z0-9]*/ 都沒有任何作用。但詭異的是, 如果只查詢數字, 就 work, 例如 column_name:/[0-9]*/ 這樣是 ok 的。然後又偶然的發現, 另外一個欄位的查詢也是 work 的, 例如 column_name2:x* 這樣也搜得到。這樣我不禁懷疑, 難道是欄位有問題嗎?

但詭異的事情是, 當我使用 wildcard  query 的時候, 下 Y* 又是 ok 的。因此, 我開始傾向於想, 該不會是 ElasticSearch 在 query string query 的 implementation 有 bug 吧 orz

正當我真的有點無言的時候, 重新把 documentation K 了一遍 (雖然她現在的 documentation 已經好很多, 但我覺得還是一樣沒有非常清楚), 赫然發現一件事情…

有個東西叫做 lowercase_expanded_terms, default 是 true。

Whether terms of wildcard, prefix, fuzzy, and range queries are to be automatically lower-cased or not (since they are not analyzed). Default it true.

所以我所有打大寫的東西, 都被改成小寫了…!!!

而在 elasticutils 的 query(field__query_string=’QUERY’) 無法設定, 只好使用 query_raw 了。

以上的踩地雷, 花了我大約兩個小時 orz

13 六月

Django, celery, gevent 實作 long polling

我們的一個新的網站平台, 有讓使用者上傳圖片的功能。當照片上傳過後, 我們會縮放圖片至適當的比例, 並且貼上一些浮水印之類的東西。如果我是貼上一個單眼拍下來的相片, 使用 PIL 縮圖是挺耗費時間的, 因此為了有更好的 User Experience, 我覺得這些耗費時間變成 offline 來做。然而, 我希望 server 在做完之後, browser 能夠被通知到並且將縮圖抓回來。

一開始, 我打定的主義是使用 node.js 的 socketio, 可惜我完全沒碰過 node.js, 雖然 socketio 在前後台之間的結合很完美, 但有下列問題要先解決:

  1. Ubuntu 12.04 LTS 上的 nginx 版本還沒有支援 websocket。當然也是可以更新版本。
  2. nginx 的後端有兩種 solution, django + node.js。
  3. 我要想辦法讓 django 跟 node.js 可以一起溝通, 因為網站整個都是 django, user upload 完照片後, 整個都是在 django/python 裡頭, task 的資訊要在兩邊共享

基於上述會耗費太多開發時間, 因此我多 survey 了一下, 發現 long polling 基本上就是把 request 卡住, 等到一有結果就直接回傳。用最基本的東西就可以實作。

Celery

Celery 之前就大概用過, 這一次想說直接用 SQS 作為 Message Queue, 文件上有很基本的說明, 但是有一行講得非常模糊

If you specify AWS credentials in the broker URL, then please keep in mind that the secret access key may contain unsafe characters that needs to be URL encoded.

看完沒有 sample 真的很痛苦, 因為 python urllib 的 API 文件好像找不到合用的, 一開始看大概會選擇 urllib.urlencode(query[, doseq]), 但其實這是用在 GET parameter, 會有 key 跟 value, 而 urllib.quote(string[, safe]) 用起來卻不會將 / (slash) 做 encode。搞了半天, 原來要把 safe 變成空的才 ok:

BROKER_URL = 'sqs://%s:%s@' % (AWS_ACCESS_KEY_ID, urllib.quote(AWS_SECRET_ACCESS_KEY, ''))

然而, 實作之後, 發現有不穩定的狀況, 才發現在 Brokers 的頁面上有寫著, SQS 的 status 是 experimental, 因此就火速換掉使用之前用的 rabbitMQ。(不過後來發現不穩定的狀況應該是我自己耍腦殘, 請看下面段落敘述)

gevent & long polling

查了很多 long polling, python 還有 django 的資料, 後來發現可以使用 Django+gevent 來實作。如果不使用 gevent, Django 是不太適合直接拿來做這件事情, 因為如果有多個 request 都在等待, 很快就會把 worker 全部都佔滿 (如果有錯請糾正 >”<), 而使用 gevent, 則可以使用底層的 libevent 來做多工的切換, Gevent Tutorial 的這張圖有很清楚的表達他的運作方式。因此, 如果有多個 user 在做 long polling, 也不致於會卡住。

結合在一起

接在一起的流程很簡單, 步驟如下:

  1. User 上傳檔案到後端的某隻 view。
  2. 這個 view 製作一個 celery task (apply_async), 然後將 task_id 回傳給 browser
  3. Browser 定期帶著這個 task_id 去 check 另一個叫做 poll 的 view
  4. poll 的 view 只做一件事情就是 get celery task result, 並且設定一個 timeout。所以, 只要一 timeout, browser 就會再發一次 request 卡在 poll 裡頭。

當然, 如果一次上傳多個圖片, 就可以使用 gevent 的 group, 一次開多個 greenlet 去等待, 哪一個先回傳就把整個 group 幹掉回給 browser。我寫了一個很簡單的 django-longpolling-example 放在 github, 就只是簡單的示範這個概念。

Debug 很久之自己耍笨

前天下午在睡午覺的時候, 收到 E-mail 說有使用者講無法上傳圖片, 我就跳起來 debug, 用了非長久, 發現會有漏 task 的事情發生。於是乎我 trace code trace 到了 celery、kombu、amqp 等 python library, 才發現 message 是真的都有丟出去, 但是為什麼我的 celery worker 就是會掉 task 呢? 我今天才突然驚覺, 原來我 dev、stage、production 用到了同一個 queue, 所以我在測試機器上傳的圖片, 都委派給其他 server 去做了, 而這邊 code 沒有 sync, 或是檔案也不在那台 instance 上, 所以就無法完成 task。

這告訴了我們, follow tutorial 還滿重要的。在 Using RabbitMQ 的 Setting up RabbitMQ 提到, 要設定 virtual host, 才會把 queue 給隔開。

Deployment

在 django 使用 gevent, 若是使用 gunicorn, 可以直接將 worker 換成 gevent


gunicorn app.wsgi:application --worker-class gevent

如果直接執行的話, 可以參考 django-longpolling-examplerungevent.py