Wednesday, December 07, 2005

Switch to Postgresql

อ่านเจอเรื่อง FeedLounge Now running on PostgreSQL
เขาพูดถึงประเด็นว่าทำไมเขาถึง switch จาก MySQL -> PostgreSQL
แรกเริ่มเดิมทีเขาใช้ MySQL 's MyISAM table แต่ต่อมาก็เปลี่ยนไปใช้
mysql 's InnoDB table แทน โดยเขาพบปัญหาจาก
table locking behavior in MyISAM
เนื่องจากลักษณะงานของเขามี write ครึ่งหนึ่งของ read
(read 4 ล้าน quries/day, write 2 ล้าน queries/day)
ผลก็คือทำให้ scalable ของเขาต่ำมาก (< 10 users)

ผลลัพท์จากการใช้ InnoDB เขาบอกว่า
พบปัญหา slow performance
จากการ load Data (ไม่ใช่เรื่อง query)
ก็เลยตัดสินใจเปลี่ยนอีกที
คร่าวนี้ย้ายไป PostgresSQL แทน

เหตุผลที่เป็นแรงจูงใจในการเปลี่ยนคราวนี้ ก็มี
  • Database Size
    เมื่อตอนเปลี่ยนจาก MyISAM -> InnoDB database
    ที่เคยมีขนาด 1 GB ก็กลายเป็น 10+GB
    ปัจจุบัน ณ ขณะที่เปลี่ยน ขนาดได้กลายเป็น 34 GB
    หลังจากย้ายลง Postgres แล้ว ขนาดเหลือเพียง 9 GB
  • Load time
    การ load data ลง database
    เดิม MySQL ใช้เวลา 1 วัน ในการ load
    เมื่อเปลี่ยนมาใช้ PostgreSQL ก็เหลือแค่ 4 ชั่วโมง


นอกจากนี้ยังพบว่า Postgres ใช้ memory
แค่ 1/3 ของ mysql

ที่น่าสนใจก็คือ comment ที่มีคนมา post
เช่น
  • InnoDB ใน MySQL 5 มี feature "compact" row format
    ที่ช่วยลดขนาด table ได้
  • Postgres ไม่สามารถ set ให้ใช้ share buffer
    ได้เต็มที่เหมือน MySQL แต่ Postgers สามารถใช้ประโยชน์จาก
    OS cache ได้, และมีค่า effective_cache_size ที่ใช้ set
    เพื่อให้ optimizer รับรู้
  • Feedlounge ใช้ GUID เป็น primarykey
    คนของ MySQL ก็เลยบอก
    GUIDs tend not to do well for index storage efficiency, since InnoDB stores the uncompressed primary key in all secondary index records.

    ผลก็คือเขาต้องใช้ IO เยอะขึ้นไปอีก (จากการ load index)
  • UUID is really poor choice for primary key for Innodb.
    The data is going to be clustered by it… and it is random which means you will insert in radom spots in giant BTREE (as it holds rows). Furthermore inserts in the middle will frequently result in page spits which causes IO and fragmentation.

Related link from Roti

Monday, December 05, 2005

Samorost



samorost เป็นชื่อ game ที่เขียนโดย
Jakub Dvorský
art มากๆ

Related link from Roti

Constraint Programming

เขียนเรื่อง ruby, rails เยอะแล้ว กลับมาที่ java บ้าง

วันก่อนผมอ่านเจอเรื่อง Solving Sudokus in Java
คนที่ยังไม่รู้จัก sudokus ให้ลองดูคำอธิบายได้ที่ wikipedia on sudokus
(อธิบายละเอียดยิบเลยครับ)
ที่สนใจก็คือ ผมยังไม่เคยได้ยินคำว่า Constraint Programming มาก่อน
ใน tutorial ข้างบนเขาใช้ library ที่ชื่อ Koalog Constraint Solver
ซึ่งเป็น commercial product
ผมก็เลยลอง search หา Library ที่เป็น opensource license ดู
เจอเจ้า Cream: Class Library for Constraint Programming in Java
ผลผลิตจาก Japan (ดีที่ web site เขาเป็นภาษาอังกฤษ)

ลองดูวิธีการใช้ Cream solve Sudokus กัน
(เอกสารของ cream มีไม่เยอะ แต่โชคดีที่เขามีตัวอย่างให้ดู
รวมทั้งมีตัวอย่าง sudokus ด้วย ก็เลยแกะมาเล่าให้ฟังได้)
โดยเราจะใช้โจทย์เดียวกับ tutorial ที่อ้างถึงข้างบน
เริ่มด้วยการ define problem array ก่อน
static int prob[][] = {
{6,0,0,0,5,8,4,9,0},
{0,2,4,0,0,0,0,0,0},
{0,0,0,0,2,0,0,3,6},
{0,0,0,0,0,0,9,0,7},
{7,0,0,3,0,0,8,0,0},
{1,5,0,0,8,9,0,0,0},
{0,3,1,0,0,0,0,0,0},
{0,0,0,0,3,0,5,0,0},
{0,0,8,0,9,5,0,0,0}
};


ก่อนที่จะ solve ก็ต้องมีการสร้างตัวแปรให้ cream รับรู้ก่อน
ในกรณีของเรา ก็คือ เราจะมีตัวแปรทั้งหมด 9 * 9 = 81 ตัว
โดยตัวแปรทั้งหมดจะต้องถูกสร้างภายใต้ context network
Network net = new Network();

IntVariable v[][] = new IntVariable[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (prob[i][j] == 0) {
v[i][j] = new IntVariable(net, 1, 9);
} else {
v[i][j] = new IntVariable(net, prob[i][j]);
}
}
}

จะเห็นว่าถ้าค่าใน prob array เป็น 0 เราจะ new variable
ด้วย constructor new IntVariable(net, 1, 9)
ความหมายก็คือ ค่าใน variable นี้ เป็นไปได้ตั้งแต่ 1 ถึง 9
ส่วนกรณีที่มีค่าอยู่แล้ว ก็จะใช้ constructor new IntVariable(net, x)
โดย x ก็คือค่าของ variable นั้นๆเลย

ขั้นถัดไปก็คือการ declare constraint ของปัญหา
เริ่มด้วย
  • แต่ละ row ห้ามมีเลขซ้ำกัน
    // แต่ละ row ห้ามมีเลขซ้ำกัน
    IntVariable tmps[] = new IntVariable[9];
    for (int i = 0; i < 9; i++) {
    for (int j = 0; j < 9; j++) {
    tmps[j] = v[i][j];
    }
    new NotEquals(net, tmps);
    }


  • แต่ละแถวห้ามมีเลขซ้ำกัน
    // แต่ละ column ห้ามซ้ำกัน
    for (int j = 0; j < 9; j++) {
    for (int i = 0; i < 9; i++) {
    tmps[i] = v[i][j];
    }
    new NotEquals(net, tmps);
    }


  • ในแต่ละ box 3 * 3 ห้ามมีตัวเลขซ้ำกัน
    // ในแต่ละ box 3x3 ห้ามมีตัวเลขซ้ำกัน
    for (int bi = 0; bi < 3; bi++) {
    for (int bj =0; bj < 3; bj++) {
    int cnt = 0;
    for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
    tmps[cnt++] = v[i+(bi*3)][j+(bj*3)];
    }
    }
    new NotEquals(net, tmps);
    }
    }


จะเห็นว่า constraint ข้างบนใช้ constructor NotEquals(net, array_of_variable)
สร้างขึ้นมา
ความหมายก็คือ ในตัวแปรที่ผ่านเข้าไปทั้งหมดนั้น ห้ามมีตัวใดตัวหนึ่งซ้ำกันเลย

พอเสร็จจากขั้นการ declare constraint ก็เป็นขั้นการ solve แล้ว
Solver solver = new DefaultSolver(net);
Solution sol = solver.findFirst();

for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
System.out.print(sol.getIntValue(v[i][j]));
if (j < 8) {
System.out.print(",");
}
}
System.out.println();
}

โดยในการ call เราสามารถเลือก call โดยกำหนด timeout ได้ด้วย
จากการจับเวลาเครื่องผม
กรณีใช้ findFirst จะใช้เวลาประมาณ 76-80 ms
ส่วนกรณี findBest จะใช้เวลาประมาณ 90-100 ms

คนที่สนใจ Constraint Programming ลองเข้าไปดูที่นี่ครับ

Related link from Roti

Rails Migrations

เคยเขียนเรื่อง Migration ของ RoR ไปทีหนึ่ง
ในเรื่อง ทดลอง acts_as_tree feature ของ ActiveRecord (Ruby on Rails)
วันนี้เห็น link tutorial ที่เข้าใจง่ายดี
ก็เลยเอามาลงให้ดูกัน
The Joy of Migrations

ประโยคคำสั่งที่เราสามารถใช้ใน Migration class ก็มี
  • create_table
    syntax ของ create_table ก็คือ
    create_table :test do |t|
    t.column :col0, :string
    t.column :col1, :text
    t.column :col2, :integer
    t.column :col3, :float
    t.column :col4, :datetime
    t.column :col5, :timestamp
    t.column :col6, :time
    t.column :col7, :date
    t.column :col8, :binary
    t.column :col9, :boolean
    t.column :col10, :int2
    end

    ตั้งแต่ col0 -> col9 เป็น datatype ที่ ActiveRecord define ไว้แล้ว
    ซึ่งมันจะทำการแปลงเป็น datatype จริงๆให้อีกที
    โดยขึ้นอยู่กับ Database ที่เราเลือกใช้
    ส่วนกรณี col10 เป็น datatype ที่ ActiveRecord ไม่ได้ define ไว้
    เวลา generate sql ActiveRecord ก็จะ generate ออกไปตามที่เราพิมพ์เลย

    sql ที่ได้จากข้างบน จะหน้าตาดังนี้
     CREATE TABLE test ("id" serial primary key,
    "col0" character varying(255),
    "col1" text,
    "col2" integer,
    "col3" float,
    "col4" timestamp,
    "col5" timestamp,
    "col6" time,
    "col7" date,
    "col8" bytea,
    "col9" boolean,
    "col10" bigint)


    กรณีที่เป็น varchar เราสามารถกำหนด length ได้แบบนี้
      t.column :name, :string, :limit=>25


    กรณีที่ต้องการกำหนด null, default ก็เขียนดังนี้
      t.column :col11, :string, :limit=>25, :default=>"blank", :null=>false

    ซึ่งจะได้ออกมาดังนี้
     "col11" character varying(25) DEFAULT 'blank' NOT NULL)


    รายละเอียดปลีกย่อยของ create_table ยังมีอีกจำนวนหนึ่ง
    ให้ลองดูใน RDoc ของ activeRecord ดู
  • drop_table
    อันนี้ชัดเจน ใช้ drop table
  • add_column
    format ของ add_column เหมือนตอนที่เราเขียน t.column
  • remove_column
    อันนี้ชัดเจน ไม่ต้องอธิบาย
  • rename_table
  • rename_column
  • change_column
    เปลี่ยน definition ของ column
  • change_column_default
  • add_index
    ตัวอย่าง
    add_index(:accounts, [:branch_id, :party_id], :unique => true)

  • remove_index

Related link from Roti

Sunday, December 04, 2005

MySQL Compatiblility Functions

อ่านเจอจาก Robby on Rails
ใน pgFoundry มี project MySQL Compatiblility
A reimplemenation of as many MySQL functions as possible in PostgreSQL, as an aid to porting.

ใน release ล่าสุดมี function แบ่งออกเป็น 8 กลุ่มคือ
  • aggregate
  • bit
  • controlflow
  • datetime
  • information
  • mathematical
  • operators
  • string


สำหรับคนที่ไม่ต้องยุ่งกับการ port mysql -> postgres
ก็สามารถ load มาศึกษาว่า
user define function บน postgres เขียนอย่างไร?

Related link from Roti