2014年8月28日 星期四

[SQL] Common Table Expression (CTE)

CTE 簡單來說就是依據一定的查詢條件建立一個暫時的 table ,以便重複性的 Subquery 查詢。

舉例來說,我需要從 pc, laptop, printer 三個 Table 取最貴的產品出來,想像的寫法可能是如此:

SELECT model FROM (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)
WHERE price=(
    SELECT MAX(price) FROM (
        SELECT * FROM pc
        UNION
        SELECT * FROM laptop
        UNION
        SELECT * FROM printer
    )
)

我們會發現三個 table 的 UNION 被重複使用,非常麻煩。
這時我們可以將這三個 table 的 UNION 暫存成 CTE,藉由以下的方式。

WITH product AS (
    SELECT * FROM pc
    UNION
    SELECT * FROM laptop
    UNION
    SELECT * FROM printer
)

SELECT model FROM product
WHERE price=(
    SELECT MAX(price) FROM product
)

TADA~~~是不是簡單明瞭又方便勒?

2014年8月9日 星期六

ES6 新玩意兒 - Generator

ㄜ....其實不太能算是新玩意了,畢竟小弟也好一陣子沒有繼續追JS的近況(廢)。
實在是因為前陣子臉書上太多好友在提Kao和CO,當初用的express好像過時了一樣....

「不行!我還沒老啊!」

身為一個技術潮人,臨時抱佛腳是必要的,就從ES6的新規範 - Generator(迭代器) 開始吧。


宣告方式

Generator 的宣告方式比較特別,有點像是 function ,基本上是長這樣:

function * gen(){
    console.log("start!");
    yield "hello";
    yield "I'm Kevin";
    console.log("end!");
}

這個 gen 是一個ㄜ.....函式?總之,使用他後會返回一個 generator,像這樣:

var g = gen(); // g 是一個 Generator


這時,就可以拿它來做這樣的事:

r = g.next(); // "start!"
console.log(r); // { value: "hello", done: false }
r = g.next();
console.log(r); // { value: "I'm Kevin", done: false }
r = g.next(); // "end!"
console.log(r); // { value: undefined, done: true }


Generator.next() 回傳什麼?Value & Done!

value的形式不拘,端看generator的yield後接著什麼,會被放到value中。
至於 done 則是個單純的 boolean,告訴你這個 generator 玩完了沒。

看到這邊,聰明的你應該可以得到一個結論,generator中的yield有點像是中斷點,當 next() 被執行時,generator內的程式碼開始被執行,直到下一個 yield 的那一行。直到 generator的最後一行,回傳 { done: true }。


Generator 也能接收資料

在迭代的過程中,我們也是可以將變數傳入next中的:

function * gen(){
    var got = yield "hello";
    yield got;
}

var g = gen();

g.next("Good morning!"); // "Hello"
g.next(); // "Good morning!"

TADA~~~很好玩吧?


Generator 的特性及用途

因為迭代器中斷函數時,是不會卡死其他程式碼的,這樣的特性非常適合拿來做同步處理。

舉例來說....

舉例來說....

舉例來說....

舉不出來,看看大大們怎麼說好了:


基本上主要是用作「非同步流程控管」及「解決回調地獄(callback hell)」用途使用。


2014年7月29日 星期二

[JS] nextTick() 介紹與應用


Node.js是單線程的,除了系統IO之外,在它的事件輪詢過程中,同一時間只會處理一個事件。你可以把事件輪詢想象成一個大的隊列,在每個時間點上,系統只會處理一個事件。即使你的電腦有多個CPU核心,你也無法同時並行的處理多個事件。但也就是這種特性使得node.js適合處理I/O型的應用,不適合那種CPU運算型的應用。在每個I/O型的應用中,你只需要給每一個輸入輸出定義一個回調函數即可,他們會自動加入到事件輪詢的處理隊列里。當I/O操作完成後,這個回調函數會被觸發。然後系統會繼續處理其他的請求。

在這種處理模式下,process.nextTick()的意思就是定義出一個動作,並且讓這個動作在下一個事件輪詢的時間點上執行。我們來看一個例子。例子中有一個foo(),你想在下一個時間點上調用他,可以這麽做:
function foo() {
    console.error('foo');
}
 
process.nextTick(foo);
console.error('bar');

執行結果將會是:
bar
foo

我們也可以使用setTimeout()函數來達到貌似同樣的執行效果:
setTimeout(foo, 0);
console.log('bar');

但在內部的處理機制上,process.nextTick()和setTimeout(fn, 0)是不同的,process.nextTick()不是一個單純的延時,他有更多的特性,這裡有兩者的效能比較。
更精確的說,process.nextTick()定義的調用會創建一個新的子堆棧。在當前的棧里,你可以執行任意多的操作。但一旦調用netxTick,函數就必須返回到父堆棧。然後事件輪詢機制又重新等待處理新的事件,如果發現nextTick的調用,就會創建一個新的棧。


什麼時候會使用到 nextTick ?

因為JavaScript是單線程的Runtime,如果有需要長時間的運算,往往會卡死其他的程式,例如事件觸發的 function。
更多應用情境可以參考原出處。

也可以參考Fred的這篇:探討 NODE.JS 的非同步機制

2014年2月19日 星期三

在development模式下,assets 與 precompiled assets 衝突的解決方法

因為自己的開發環境效能往往比production server 還好,有時候我們為了求快,會直接在local 將 assets precompile 過,上傳到 production。
但繼續在 development 模式下開發時,就有個很機車的情況出現了,在 public/assets 中已經 compile 過的 assets 也被 require 進來,造成某些 lib 被 require 兩次而造成不正常的情況,當有新的變更時也會被舊的蓋過去。

查了一下發現可以針對 development 模式對 asset 路徑加上prefix:
  config.assets.prefix = "/dev/assets"

這樣子 request 的路徑就會變成 /dev/assets/application.js ,避免被precompile的assets搞亂。



2014年2月12日 星期三

修正 PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey' 問題

PG::UniqueViolation: ERROR: Duplicate Key Value Violates Unique Constraint 'Your_table_name_pkey'

今天在新產品上遇到了這個 error,查了一下發現是因為 postgres 因為不明原因而把 seq 搞亂了,以致於新增資料時使用到了既有資料的 id 。

了解問題所在後,循以下作法順利解決了問題:
  1.  rails db production
  2.  SELECT setval('your_table_id_seq', (SELECT MAX(id) FROM your_table));
打完收工~


2014年1月8日 星期三

Rails 的 i18n fallback 問題

當在 config/application.rb 中指定了 config.i18n.fallbacks 語系順序後,別忘了要 comment 掉 produciton.rb 中的 config.i18n.fallbacks = true 。
production 下的語系順序才會乖乖實現。

2014年1月3日 星期五

Unicorn + Nginx 配置及設定

最近部屬公司的新產品 - flyTutor 的時候,嘗試了不同的架構,從以往的 passenger-plugin on Nginx 換成了Unicorn 搭配 Nginx 的「反向代理」架構

What's "Reverse Proxy Model" ?

在提起 Unicorn 及 Nginx 的配置之前,我們得先了解一下「反向代理機制」,下面這張圖應該可以幫助你了解:

(轉自: ROR實戰聖經)


在用戶端向 server 作請求的時候,不是直接與 app server 連結 (中間那三個),而是先連到 web server ,通常是 Apache 或 Nginx,再由 web server 指派 request 給 app server、或直接 serve static assets。
所以在這樣的架構下你可以想像你的系統需要常駐著以下的服務:
  1. web server
  2. 數個 app server
  3. DB server

這樣的架構是與常見的 Apache/Nginx + Passenger 架構有所不同的,需要作一點觀念上的調適。下面我們就來介紹一下如何做相關的配置。


Nginx 配置

以下是預設的 Nginx 設定檔 - nginx.conf:
user deployer; # 定義操作 nginx 的使用者
worker_processes 1; # 定義 worker 的數量

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {

  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  access_log    /var/log/nginx/access.log;

  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout  65;

  gzip  on;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_proxied any;
  gzip_vary off;
  gzip_types text/plain text/css application/x-javascript text/xml application/xml application/rss+xml application/atom+xml text/javascript application/javascript application/json text/mathml;
  gzip_min_length  1000;
  gzip_disable     "MSIE [1-6]\.";

  server_names_hash_bucket_size 64;
  types_hash_max_size 2048;
  types_hash_bucket_size 64;

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;

}

上面這段預設的設定檔有兩個地方需要注意:
第一行的使用者身分必須為負責 deploy 的用戶,否則常常會出現 403 權限問題,因為未經設定的話 nginx 會沒有權限存取 deployer 的檔案,如 js, css。
第二行的 worker 數量建議別設定超過 cpu 數目,多了也沒好處。

再來就是重頭戲了,我們要開始加入 app server 的設定,這段設定需要加在 http 的括號內:
  upstream unicorn {
    server 127.0.0.1:8080 fail_timeout=0;
  }

  server {
    listen 80 default deferred;
    server_name flytutor.com;
    root /var/www/flytutor/public;

    location ^~ /assets/ {
      gzip_static on;
      expires max;
      add_header Cache-Control public;
    }

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      if (!-f $request_filename) {
        proxy_pass http://unicorn;
      }

    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
  }


upstream:

首先我們先來講解一下第一部分的 upstream。首先,upstream 後的「unicorn」並非一定的,端看你希望怎麼命名,但是要注意的是 proxy_pass 也需作對應的設定。再來,upstream 內的 server 可以不只一個,如果你有數個 app server 在不同的 host 的話,你也可以做如下的設定:
upstream flytutor.com {
  server 192.168.0.111:8080 weight=3;
  server 192.168.0.222:8080 weight=2;
  server 192.168.0.333:8080 weight=3;
}
其中 weight 代表的是「被配發 request 的權重」,當 weight 越高的時候被分配到的機率越大,可以做 loading balance。

而除了走 TCP/IP 外,如果是在 web server 和 app server 都在同一個主機的情況下,你也可以使用 socket 來做設定,基本上效能會比 TCP/IP 好一點點(因為少了 header):
  upstream unicorn {
    server unix:/tmp/unicorn.sock fail_timeout=0;
  }

server:

接下來我們要介紹一下 server 的設定,基本上這邊指的 server 就是指 app server,一台 nginx server 可以依 server 設定派發 request ,所以可以做 vhost 的應用,例如判斷 request 的不同網域 (設定檔中的 "server_name")、或是同網域但是不同 port (設定檔中的 "listen"),來派發到不同的服務。
proxy_pass 很重要,許多網友都死在這個地方,切記要將他設置成 upstream 的命名,例如 "http://<upstream_name>"。
你可以想像當一個 request 經過 location(path), server_name(domain), listen(port) 重重篩選後,你卻不指派 proxy_pass 的目的地給他,讓這個 request 就此隱沒在數據流中,成了「digital phantom」,你於心何忍?


Unicorn 配置

unicorn 的配置通常是放在 rails project 中的 config 資料夾,長得像這樣:
app_root = "/var/www"
app_name = "flytutor"
listen "127.0.0.1:10101" #, :backlog => 2048 #這邊要跟nginx虛擬主機檔中upstream內定義的務必一樣
worker_processes 2 #看情況開
preload_app false
timeout 30
 
module Rails
  class <<self
    def root
      File.expand_path(__FILE__).split('/')[0..-3].join('/')
    end
  end
end

_working_directory = File.join(app_root, app_name)
working_directory _working_directory
logs_path = "#{_working_directory}/log"
pid "#{_working_directory}/tmp/pids/unicorn.pid"
stderr_path "#{logs_path}/unicorn.stderr.log"
stdout_path "#{logs_path}/unicorn.stdout.log"
 
GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true
 
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  
  old_pid = "#{Rails.root}/tmp/pids/unicorn.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      puts "Send 'QUIT' signal to unicorn error!"
    end
  end
end
 
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

這邊要注意的地方有兩個:

  1. listen 的對象必須與 nginx.conf 中 upstream 設定的一樣,unicorn 與 nginx 就是靠這行設定連起來的。還有,如果是走 TCP/IP 的設定,盡量不要使用 1024 以下的 port 以免 deployer 沒有權限啟動 unicorn 導致失敗(你總不能老用 root 權限作 deploy 吧?)。
  2. unicorn 是採用 master-slave 機制,啟動 unicorn 後叫出  process 列表你就會了解的。而這裡的 worker_processes 的數目顧名思義就是指定 worker 的數目。開多少個才好?基本上沒有一定的標準答案,還是要依你的使用情況及環境做測試後才知道,如果懶得想的話就直接設定為「CPU 數目 + 1」吧。


操作 unicorn

許多網友在網路上搜尋到的啟動法是以 unicorn_rails 來啟動:/usr/bin/unicorn_rails -c config/unicorn.rb -E $RAILS_ENV -D。但現在 unicorn 官方已經不建議使用這種作法而是直接改用 unicorn 來啟動/重啟 unicorn。

Hot restart with zero down time

許多人之所以選擇 unicorn 或許就是因為他的 hot restart 特性。如果對這有興趣的人可以參考這篇。