Wednesday, December 28, 2005

ทดสอบแนวคิดการจัดการ large object ด้วยการเก็บชั่วคราวบน database

วันนี้มีโจทย์เรื่อง outofmemory
กล่าวคือ มีโปรแกรมที่จำเป็นต้อง access ข้อมูลจาก text file ขนาดใหญ่
โดยทำการ parse ข้อมูลเข้ามาเป็น object model ใน memory ก่อน
จากนั้นจึงจะ parse, aggregate เป็น ข้อมูลสรุปต่อไป
ซึ่งเมื่อข้อมูลมีปริมาณสูงถึงจุดหนึ่ง ก็จะเกิด outofmemory exception

ประเด็นของการทดลองก็คือ ต้องการใช้วิธีการ parse object แบบเดิม ​
แต่ต้องการให้ memory footprint น้อยที่สุด
(ไม่สามารถเปลี่ยนให้เป็นลักษณะการ parse แบบ sequential ครั้งเดียวได้
เพราะ business logic มันบังคับอยู่)

ทางเลือกที่ทดลอง ก็คือ ถ้าเราสร้าง Wrapper ขึ้นมาห่อ object ไว้
โดย object จริงๆจะมีการ save ลง database
และ Link เข้ากับ proxy ด้วย SoftReference

public class ItemWrapper {
private SoftReference ref;
private int key;

protected ItemWrapper(int key, Item item) {
ref = new SoftReference(item);
}

// getter here..
public XX getYY() {
return getItem().getYY();
}
...

private Item getItem() {
Item tmp = (Item) ref.get();
if (tmp == null) {
tmp = reload();
}
return tmp;
}

private Item reload() {
tmp = PersistentManager.getInstance().load(key);
ref = new SoftReference(tmp);
return tmp;
}


}

ถ้ามีการ access เมื่อไร ก็ check ว่า SoftReference ถูก
garbage ไปหรือยัง ถ้าถูก garbage ไปแล้ว ก็ให้ fetch ข้อมูลจาก
database ขึ้นมาใหม่

ข้อมูลที่ทดสอบคือ data ขนาด 100000 รายการ
ทดลอง implement ด้วย database 3 ตัวคือ JDBM, BerkeleyDB, MySQL

ขั้นที่ 1 ทดลองแบบ object ทั้งหมดเก็บใน memory ก่อน
ทดลองสร้างรายการ 100000 รายการ แล้วปรับค่า -Xmx จนได้ค่าเล็กสุด
ที่ไม่เกิด outofmemory
ได้ค่า footprint อยู่ที่ประมาณ 5 MB
เวลาที่ใช้ในการสร้าง items => 200 ms.


ขั้นที่ 2 ทดสอบ JDBM
การเก็บลง database ใช้วิธีนี้

RecordManager _manager = RecordManagerFactory.createRecordManager("/tmp/pok.db");
HTree hashtable = HTree.createInstance(_manager);

...

public void save(InternalItem item) {
hashtable.put(new Integer(item.getKey()), item);
count++;
if (count > 100) {
_manager.commit();
count = 0;
}
}

ผลการทดลองพบว่าในส่วนของ JDBM มี memory footprint เพิ่มขึ้นมา
2 MB นั่นคือคราวนี้ต้องใช้ heap memory เพิ่มขึ้นเป็น 7 MB จึงจะ run ได้
เวลาที่ใช้ในการสร้าง items => 20 วินาที
(เป็นเวลาที่ tune เรื่อง group commit แล้ว)
database file => 42 MB

ขั้นที่ 3 ทดสอบ BerkleyDB
การเก็บลง DB ใช้วิธีนี้


EnvironmentConfig ecfg = new EnvironmentConfig();
ecfg.setAllowCreate(true);

DatabaseConfig cfg = new DatabaseConfig();
cfg.setAllowCreate(true);

Environment env = new Environment(new File("/tmp/pokdb"), ecfg);

Database db = env.openDatabase(null, "test.db", cfg);
Database classDb = env.openDatabase(null, "class.db", cfg);
StoredClassCatalog cat = new StoredClassCatalog(classDb);
EntryBinding binding = new SerialBinding(cat, InternalItem.class);

...

public void save(InternalItem item) {
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry entry = new DatabaseEntry();

IntegerBinding.intToEntry(item.getKey(), key);
binding.objectToEntry(item, entry);
db.put(null, key, entry);
}

BerkleyDB ใช้ memory เยอะสุด
ต้อง config heap ถึง 32 MB จึงจะ run ได้
เวลาที่ใช้ในการสร้าง items => 12 วินาที
database file => 22 MB

ขั้นที่ 3 ทดลองกับ MySQL
การ save ใช้ sql ผ่าน jdbc และทำเป็น preparedStatment
โดยใช้ table type เป็น MyISAM

public void save(InternalItem item) {
saveStmt.setInt(1, item.getKey());
saveStmt.setString(2, item.getDesc());
saveStmt.setDouble(3, item.getAmount());
saveStmt.executeUpdate();
}

ผลการทดสอบ ต้อง config heap 7M
เวลาที่ใช้ในการสร้าง items => 27 วินาที
database file => 17 MB

สรุปผลเบื้องต้น

DBM-Xmxcreate timefile size
- (mem. only)5M200 ms.-
JDBM7M20 sec.42 MB
BerkleyDB32M12 sec.22 MB
MySQL7M27 sec.17 MB


ปัญหา
BerkleyDB ใช้ memory สูงมาก
เลยลองจับ profile ดู
พบว่ามี byte[] ค้างอยู่สูงมากๆ (91% ของทั้งหมด)
ไม่แน่ใจว่าเกิดจากการใช้ที่ไม่ถูกต้องหรือเปล่า

ส่วน JDBM นั้น ลองจับ profile แล้ว
พบว่า memory ของ SoftReference Object นั้น ใหญ่โตไม่เบาทีเดียว
คิดเป็นสัดส่วน memory ดังนี้
  • SoftReference 47 %
  • ItemWrapper 23 %
  • byte[] 12 % (เกิดจาก HTree)
  • Item 4.5 % (ตัวข้อมูลจริงๆ)

แต่ byte[] ใน JDBM ไม่ leak พอจบโปรแกรม ก็คืนหมด

ดูแล้ว SoftReference มันใหญ่เหลือเกิน
อย่างนี้มันจะช่วยประหยัด memory ได้ยังไงฟะ
หรือเราต้อง implement ReferenceQueue เพื่อ clear
SoftReference ??

Related link from Roti

1 comment:

Sand said...

OFBiz ใช้ JDBM ครับ ตอนนี้เห็นคุยๆกับว่าจะไปใช้ EHCache