Saturday, April 15, 2006

[การเมือง] มรดกของทักษิน

ไมเคิล ไรท เขียน "ทำอย่างไรในยุคหลังทักษิณ?" ใน มติชน
แต่สามารถอ่านได้จาก มหาวิทยาลัยเที่ยงคืน ด้วย

รัฐบาลไทยรักไทยให้ความสำคัญ เข้าถึง และปลุกระดมชนชั้นราษฎรชาวชนบทอย่างไม่เคยมีมาก่อน นโยบายนี้จะมีผลดี-ผลร้ายต่อไปเรายังมองไม่เห็น แต่เราต้องยกย่องว่าเป็นกุศโลบายที่ยกฐานะการเมืองไทยจาก "การเล่นละครของชนกลุ่มน้อย" มาอยู่ในระดับ "ชีวิตจริงของคนส่วนใหญ่" ซึ่ง ดีไม่ดี หมายถึงการเติบโตทางการปกครองบ้านเมือง (Political Maturity)

นโยบายเข้าถึงและปลุกระดมราษฎรของพรรคไทยรักไทยจะดีหรือชั่ว เราตัดสินไม่ได้เพราะเหตุการณ์ยังอยู่ประชิดตัวมากไป อย่างไรก็ตาม เราแน่ใจได้ว่าเข็มนาฬิกาไม่เดินทวนกลับหรอก ราษฎรผู้ยากจนในชนบทจะไม่หลับใหลกลับเข้าสู่ยุคนิทราอีกต่อไป ต่อไปนี้พรรคการเมืองใดๆ ที่อยากบริหารบ้านเมืองก็จำเป็นต้องเข้าถึงราษฎรและแก้ปัญหาของราษฎรอย่างจริงจังไม่ใช่หลอกกันเล่น

Related link from Roti

โปรแกรมประมวลผลสอบ

การตรวจผล admission ปีนี้เละเป็นโจ็กไปเลย
ในแง่ของคนทำโปรแกรมอย่างพวกเรา ก็น่าจะมีการเผยแพร่
บทสรุปและทบทวนข้อผิดผลาดกันบ้างนะ
เป้าหมายไม่ใช่เพื่อพิพากษาคนทำโปรแกรมคนใดคนหนึ่ง
แต่เพื่อที่จะได้เรียนรู้ข้อผิดผลาดที่เกิดขึ้น และหลีกเลี่ยงไม่ให้มันเกิดซ้ำอีก

ในแง่คนนอกที่ไม่เกี่ยวข้องแบบเรา ก็ได้แต่เดาว่า เกิดอะไรขึ้นบ้าง
ที่ผมเดาๆไว้ ก็น่าจะมี 2 ประเด็นนะ
ประเด็นแรก คือเรื่องความฉุกละหุก
สังเกตงานราชการ มักจะมีปัญหานี้ทุกครั้ง
ประมูลช้า(ทำให้เหลือเวลาน้อย) หรือไม่ก็
คนเขียนขาดประสบการณ์ โปรแกรมก็เลยก้าวหน้าไปอย่างช้าๆ
ซึ่งทั้งคู่จะไปตายทีเดียวกัน ก็คือ ไม่มีเวลา test
ซึ่งเรารู้กันอยู่แล้วว่าโปรแกรมทุกโปรแกรม
มันมี bug แหง๋ๆ

อันนี้สองนี่ไม่แน่ใจว่าจริงไหม
ใครรู้จริงช่วยแย้งด้วยแล้วกัน
ไม่รู้ยังจำสมัยก่อนที่มีการนำ computer
เข้ามาควบคุมระบบไฟจราจร ในกรุงเทพฯได้หรือเปล่า
ข้อผิดผลาดครั้งนั้น ก็คือ
การ over estimate สิ่งที่โปรแกรมสามารถทำได้
โดนเชื่อกันมากไปว่า computer(software) มันต้องทำได้
ผลสุดท้าย ก็เละเป็นโจ๊กเหมือนกัน
สำหรับ admission คราวนี้ ผมเดาว่าคงมีกลิ่นนี้โชยๆอยู่เหมือนกัน
(ในแง่ของ OCR)

สำหรับผม บทเรียนที่สำคัญในเรื่องนี้ น่าจะเป็นประเด็นเรื่อง Fault tolerance
ที่ควรจะต้อง design และ implement ไว้ทั้งในระดับ work procedure
(เช่น การ scan ต้อง scan 2 ครั้ง แต่ละครั้งทำโดยทีมงานคนละชุด)
และในระดับ software fault tolerance

Related link from Roti

Friday, April 14, 2006

[java] Jetty Continuations

ใน Web container ปัจจุบัน
model ของ thread จะเป็นแบบ Thread per Connection
นั่นก็คือ 1 connection ที่วิ่งเข้ามา จะมี 1 thread ที่ถูก assign ให้รับผิดชอบ
(ผลพวงมาจาก socket api, ที่มีการ assign connection ให้กับ thread)

แนวคิดใหม่ๆ ก็คือการใช้ NIO เข้ามาช่วย
อย่างใน Jetty6 จะมี model ที่เรียกว่า Thread per Request
ความหมายก็คือ connection ที่เปิดทิ้งไว้, แต่ไม่มี request วิ่งเข้ามา
จะไม่มี thread รองรับมัน (thread จะถูกส่งกลับเข้า pool)
เมื่อไรก็ตามที่มี request เข้ามา จึงจะมีการ assign thread ให้

บางคนอาจจะสงสัยว่า 1 connection มีได้หลาย request ด้วยหรือ
Connection ใน HTTP1.1 จะมี default เป็น keep alive
และเมื่อบวกกับ App สมัยใหม่ที่เป็น ajax style
มีข้อมูลวิ่งไปมาถี่ๆ, connection ที่ browser เปิดไว้ก็แทบจะไม่ disconnect เลย
บางคนถึงกับล้อว่า นี่เราจะเปลี่ยน browser ให้กลายเป็น client-server แบบเดิมๆหรืออย่างไร
(client-server แบบที่เปิด connection ค้างไว้)

แล้ว Jetty Continuations คืออะไร
Continuations ใน Jetty ไม่ใช่ continuation แบบใน scheme, ruby (พวก callcc)
ความหมายจริงๆของมันก็คือ ในระหว่างการทำงานของ Servlet,
servlet สามารถที่จะสั่ง suspend thread ได้
โดยถ้ามีการสั่ง suspend เมื่อไร, jetty จะทำการ detach thread ออกจาก request,
และคืน thread กลับเข้าสู่ thread pool

แล้ว App ประเภทไหนที่เหมาะกับ Continations?
ที่ชัดเจนที่สุด ก็น่าจะเป็นพวกที่ต้องมีการ polling เป็นระยะๆ เช่น พวก chat
หรือพวกที่มี nature การทำงานเป็น asynchronous
เช่น app ที่มีการเรียกใช้ service ผ่านทาง JMS แล้วต้องรอ reply message

ลองมาทดสอบ NIO กับ continuation ดู
เริ่มด้วย servlet แบบง่ายๆ
protected void processRequest(HttpServletRequest request, 
HttpServletResponse response)
throws ServletException, IOException {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet HelloServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");

out.close();
}

ใน servlet ให้มี sleep 100 ms,
ส่วน version ที่เป็น continuation ก็แค่เปลี่ยนจาก sleep เป็น
    Continuation ctn = ContinuationSupport.getContinuation(request,this);
ctn.suspend(100);


ผมใช้ jmeter ทดสอบ โดยเรียก 120 request ใน 1 วินาที
เป็นเวลา 20 วินาที (รวมทั้งหมด 2400 request)
ตั้ง duration assert ไว้ที่ 200 ms

Jetty 6 NIO
50 % 109 105 106
90 % 147 125 125
> 200 ms 69 91 82

Jetty 6 BIO
50 % 103 103 104
90 % 111 111 114
> 200 ms 0 2 83

Jetty 6 NIO + continuations
50 % 108 106 106
90 % 120 125 122
> 200 ms 0 78 89

Tomcat 5.0.30
50 % 104 103 103
90 % 111 114 113
> 200 ms 0 0 0

Note: 50, 90 % คือ distribution ของ response time
เช่น 50% ใช้เวลาน้อยกว่าหรือเท่ากับค่าที่ระบุ


ดูแล้ว NIO จะช้ากว่า BIO (block IO) อยู่นะ
ส่วน continuation ก็ไม่ต่างกับ NIO
และ jetty ใน version นี้เริ่มสู้กับ tomcat 5.0.x ได้แล้ว

ที่นี้ลองเปลี่ยนช่วงเวลาที่ sleep กับ suspend เป็น 1000 ms บ้าง
Thread.sleep(100);
==========================
ctn.suspend(1000);


ใช้ JMeter เรียกที่ 120 request/ sec, 20 วินาที เหมือนเดิม
แต่เปลี่ยน assert เป็น 1100 ms
ผลที่ได้

Jetty 6 NIO
50 % 1021 1008 1009
90 % 1397 1438 1419
> 1100 ms 956 919 883

Jetty 6 BIO
50 % 1002 1003 1002
90 % 1439 1549 1487
> 1100 ms 788 786 707

Jetty 6 NIO + Continuation
50 % 1002 1002 1002
90 % 1015 1011 1013
> 1100 ms 126 93 93

Tomcat 5.0.30
50 % 1005 1002 1002
90 % 1349 1035 1257
> 1100 ms 562 398 400

จะเห็นว่ากรณีที่ thread เริ่มสะสมกันมากๆ
(เนื่องจากเราตั้งค่า sleep ไว้ที่ 1 sec. ทำให้ request ชุดที่ 2 เริ่มเข้ามากวน request ชุดที่ 1)
jetty continuations ให้ผลลัพท์ที่ดีขึ้นอย่างเห็นได้ชัด

ข้อที่ต้องระวังในการใช้ jetty continuation ก็คือ
หลังจากที่เราสั่ง suspend ไปแล้ว, เมื่อ thread resume กลับมาอีกครั้ง
HttpServletRequest ที่ได้ในคราวนี้ จะไม่ใช่ตัวเดิมแล้ว
ค่า parameter ต่างๆ จะหายหมด
ดังนั้นเราต้อง save ค่านี้เก็บไว้ใน session หรือใน database ก่อน
นอกจากนี้ ยัง suspend ยังห้ามใช้หลังจากมีการเขียน response ไปแล้ว

ประเด็นที่ผมยังไม่รู้คำตอบ ก็คือ
ในการใช้งานจริง เรามักจะวาง web container ไว้หลัง web server
คำถาม ก็คือพวก persistent connection ที่เกิดขึ้น มันจะเกิดกับ web server
แล้ว jetty continuation ยังมีประโยชน์อยู่อีกหรือเปล่า
case หนึ่งที่เห็นว่า น่าจะมีประโยชน์อยู่บ้าง ก็คือพวก asynchronouse nature
(พวกที่ใช้ jms ต่อไปยัง service)
เพราะถ้าใช้ jetty contination เข้ามาช่วย ก็น่าจะทำให้เรา
เพิ่มจำนวน connection ระหว่าง web server กับ web container ได้
ซึ่งมีผล ทำให้ through put เพิ่มขึ้น

Related link from Roti

Wednesday, April 12, 2006

[art] links 12-04-06


Simone Zahradka
design เขาน่าสนใจดี, อย่าลืมลอง click ดูลูกเล่นเขา ตอนที่เลือก รูปใน portfolio


bertmonroy
digital photo-realist artist
Damen คือชิ้นงาน ที่ใครเห็นก็ต้องทึ่ง
  • The image size is 40 inches by 120 inches.
  • The flattened file weighs in at 1.7 Gigabytes.
  • It took eleven months (close to 2,000 hours) to create.
  • Taking a cumulative total of all the files, the overall image contains over 15,000 layers.

ปล. แฟนผมเห็นภาพนี้แล้วพูดออกมาว่า "น่ากลัว"
(ขยันจนน่าตกใจ)

Related link from Roti

[java, ajax] polling in DWR 2.0

Feature ที่น่าสนใจใน DWR 2.0 ก็คือ Reverse Ajax
ที่ทำให้ฝั่ง java server สามารถ call ไปยัง javascript ที่อยู่ใน browser ได้
(จริงๆแล้วมันคือ polling นั่นแหล่ะ แต่ api มันออกแบบมาให้ดูเหมือน
เป็นการ call function โดยตรง)

ลองแกะ demo ของเขาดูกัน
เริ่มด้วย browser ที่ต้องการใช้ feature นี้ต้องสั่งดังนี้
DWREngine.setPolling(true);

มาดู input form ในฝั่ง browser ก่อน
<p>
Your Message:
<input id="text" onkeypress="DWRUtil.onReturn(event, sendMessage)"/>
<input type="button" value="Send" onclick="sendMessage()"/>
</p>

<p>Messages:</p>
<div id="chatlog"></div>

จะเห็นว่าถ้ามีการกด enter หรือ click button เมื่อไร
ก็จะมีการเรียกใช้ function ที่ชื่อ sendMessage

สิ่งที่ sendMessage ทำ ก็คือ เรียกใช้ method addMessage
(สำหรับคนที่ไม่คุ้นกับ DWR, Chat object เป็น object ที่อยู่บน server,
DWR ช่วยทำให้มันเหมือนกับอยู่บน space เดียวกัน)
function sendMessage() {
var text = DWRUtil.getValue("text");
DWRUtil.setValue("text", "");
Chat.addMessage(text);
}


ตามไปดู method addMessage ที่อยู่บนฝั่ง server บ้าง
เริ่มด้วยการ validate message ก่อน
จากนั้นก็ add เข้า LinkList ที่ทำหน้าที่ maintain message history
public class Chat
{

public void addMessage(String text)
{

if (text != null && text.trim().length() > 0)
{
messages.addFirst(new Message(text));
while (messages.size() > 10)
{
messages.removeLast();
}
}


ขั้นถัดไปก็คือการ send message ไป update ที่ browser
หัวใจหลักของขั้นนี้ ก็คือ Class WebContext
Object WebContext จะคอย maintain ว่ามี browser ไหน กำลัง online อยู่บ้าง,
และกำลังใช้ page ไหนอยู่
(ความหมายของ online ก็คือ browser ยังมีการ polling มาที่ server เป็นระยะๆ)
        WebContext wctx = WebContextFactory.get();
String currentPage = wctx.getCurrentPage();

การ call กลับไปยังฝั่ง browser จะใช้ class OutboundVariable มาช่วยทำ serialize
        try
{
OutboundVariable ov = wctx.toJavascript(messages);
String eval = ov.getInitCode() + "receiveMessages(" + ov.getAssignCode() + ");"; //

สำหรับผู้ที่สงสัยว่า getInitCode ใน code ข้างบน มันทำอะไร
สิ่งที่ method getInitCode ของ OutboundVariable generate ออกมา
มีหน้าตาดังนี้
var s0=[];var s1={id:1144813303647,text:"xxxx"};
var s2={id:1144813193425,text:"hello"};
var s3={id:1144813140215,text:"I know"};
var s4={id:1144813133617,text:"yes"};
var s5={id:1144813128413,text:"from camino"};
var s6={id:1144813122412,text:"hello"};
var s7={id:1144813118411,text:"yyy"};
var s8={id:1144813116211,text:"xxx"};
var s9={id:1144813095408,text:"zzzz"};
var s10={id:1144813092406,text:"yyyy"};
s0=s0.concat([s1,s2,s3,s4,s5,s6,s7,s8,s9,s10]);

ส่วน script ที่ ov.getAssigneCode gen ออกมา ก็มีหน้าตาดังนี้
receiveMessages(s0);


จากนั้นก็ loop เพื่อ call ไปยัง page ทุก page ที่ online อยู่
พระเอกในตอนนี้ก็คือ ScriptSession

// Loop over all the users on the current page
Collection pages = wctx.getScriptSessionsByPage(currentPage);
for (Iterator it = pages.iterator(); it.hasNext();)
{
ScriptSession otherSession = (ScriptSession) it.next();
otherSession.addScript(eval);
}
}

message ก็จะวิ่งต่อกลับมายัง javascript
ให้สังเกตว่า code ข้างบน ทำการ call javascript function ที่ชื่อ receiveMessages
โดยมี parameter คือ javascript code ที่ generate จาก method ข้างบน
จากนั้นที่ฝั่ง javascript ก็จะทำการ update หน้าจอ
function receiveMessages(messages) {
var chatlog = "";
for (var data in messages) {
chatlog = "<div>" + messages[data].text + "</div>" + chatlog;
}
DWRUtil.setValue("chatlog", chatlog);
}


ถ้าเราลองไปดู http protocol ที่วิ่งระหว่าง browser กับ server
จะเห็นว่ามีการส่ง polling ออกมาเป็นระยะ
หน้าตาของ polling packet มีหน้าตาดังนี้
POST /dwr/dwr/plainjs/DWRSystem.poll.dwr HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
....

callCount=1
c0-scriptName=DWRSystem
c0-methodName=poll
c0-id=2235_1144813311449
httpSessionId=3h2qek9hq3utl
scriptSessionId=0EADE3F10D560BBF374C1EDB48193BDA


การใช้ polling จะทำให้เกิดประเด็นตามมา ก็คือเรื่อง load ที่มากขึ้น (long lived HTTP Request)
มี solution หนึ่งที่ถูกเสนอขึ้นมา ก็คือ
Jetty 6 continuations
ซึ่งตอนนี้อยู่ในขั้น beta แล้ว

Related link from Roti

Tuesday, April 11, 2006

[Rails] Functional Test & cookie setting

วันนี้นั่งเขียน Functional test case ใน rails
เจอปัญหาว่าไม่สามารถ set cookie ได้
หลังจากพึ่ง google แล้วก็พบว่า
การ set cookie ใน test case ต้องเขียนเต็มๆอย่างนี้
def test_sort_by
@request.cookies['sort_by'] = CGI::Cookie.new(
'name' => 'sort_by',
'value' => 'location')
@request.cookies['lang'] = CGI::Cookie.new(
'name' => 'lang',
'value' => 'th')
get :index
assert_response :success
assert_equal 'location_th', @controller.check_sort_by
end


ห้ามเขียนสั้นๆแบบนี้
@request.cookies['lang'] = 'th'

Related link from Roti

M or F

seapegasus ตั้งข้อสังเกตว่า IDE มีธรรมชาติเป็นผู้หญิง
ลองไปดูเหตุผลที่นี่
NetBeans Evandalists #10

Related link from Roti

Monday, April 10, 2006

Gavin King เขียนถึงเรื่อง Red Hat to acquire JBoss
เล่าถึงสมัยก่อนเริ่มทำ hibernate
Three years ago I was dragging myself out of bed each morning to go to my depressing job building boring web applications for organizations run by risk-averse middle managers and clueless "architects".

That was a world where an idea - however unoriginal - was valued on the basis of how many grey hairs were in evidence on the head of the person expressing the idea.

บ้านเราเป็นแบบนี้หรือเปล่า ?

Related link from Roti

Sunday, April 09, 2006

[C#, LINQ] C# 3.0 กับ Haskell

Erik Meijer เขียน paper นี้ขึ้นมา
Confessions Of A Used Programming Language Salesman - Getting The Masses Hooked On Haskell
(แค่ชื่อ paper ก็เจ็บแล้ว)

ในช่วงแรกๆของ paper เขาพูดถึงความพยายามของเหล่า Haskell programmer
ที่พยายามจะ implement interface ที่ทำให้ Hashkell สามารถติดต่อกับ
โลกภายนอกได้ (imperative world)

ในท้ายบท(ที่ว่าด้วย background ของ Haskell),
เขาได้ปล่อยอารมณ์ขันออกมา
I often joke that the world’s population of Haskell program-
mers fits is a 747, and when that crashed nobody will notice.
Howerver, the world’s population of Mondrian programmers fits
in a Cessna and when that would crash nobody would really notice.

Note: Mondrian -> Internet Scripting Language

ที่ชอบมาก ก็คือประโยคนี้
Apparently, I lack the talents to entice the functional pro-
gramming community, so I decided to sell my soul to the most
popular programming paradigm, objects, and to the company
that has the biggest market share, Microsoft to save the com-
mon programmer.


บทหลังๆ จะพูดถึง Haskell เข้าไปมีอิทธิพลใน C# กับ ​LINQ อย่างไรบ้าง

หลังจาก scan paper นี้แล้ว ก็ได้ข้อสรุปอย่างหนึ่ง
C# 3.0 ที่มี Monads + Closures + Meta Programming + LINQ Framework
Java หมดทางสู้เลย
แฟน Java แบบผมก็ได้แต่ทำใจ
แล้วหันไปหากิ๊ก Ruby

Related link from Roti