Java & Spring Startup Time

Java start-up time มี overhead หลายจุด จากความพยายามที่ผมพยายาม optimize ให้ระบบของบริษัท start ได้เร็ว เพื่อทำให้ productivity ของ engineer ดีขึ้น (ปัจจุบัน start up time ~30-40sec) แต่ก็ยังไม่สำเร็จ แต่ก็มีหลายเรื่องได้เรียนรู้ระหว่างที่ทำ

จากที่ลองทำ Profiler ออกมา เจอว่าตอนที่ java app ใช้ CPU ไปกับสิ่งเหล่านี้ในตอน start (เรียงจากมากไปน้อย)

  1. load class file
  2. generate bytecode
  3. parse XML

spring-startup-fg.png

Load class file

เมื่อ start spring boot application ผ่าน IntelliJ จะไม่เหมือนกับ start ผ่าน jar ตรงๆ มีความต่างกันประมาณนี้

# IntelliJ Jar file
1 scan class จาก target/classes และ file jar unzip file jar และ scan จาก path /BOOT-INF/
2 ใช้ class path จาก module setting ของ IDE อ่าน class path จากใน jar เพียงอย่างเดียว
3 class path มีจำนวนมาก ใช้ disk lookup หลายครั้ง มีแค่ file jar แต่เสียเวลา unzip + lookup ใน zip file

ปกติ JVM จะมี class loader เอาไว้ load code ของ class ต่างๆ เข้า memory ซึ่งจะทำงานเป็น hierarchy อ่านต่อ ตัว JVM จะ load class ก็ต่อเมื่อมี code เรียกใช้งาน class นั้นๆ โดยจะไป scan หาใน class path ที่ set เอาไว้เมื่อตอน start java app

ทุก class จะ load ขึ้นมาใหม่เสมออย่างน้อย 1 ครั้งเมื่อ start java app ซึ่งทำตอน runtime ไม่ใช่ compile time ทำให้มี overhead ในการ start (ตามตาราง)

code ของบริษัทมี dependency เยอะมากๆ และ code ของบริษัทเองก็เยอะมากๆ

เมื่อจำนวน class เยอะ JVM ก็จะ start ช้า และตอนนี้ยังหาวิธี optimize ไม่ได้ แต่ solution ที่เป็นไปได้มี 2 ทาง

  1. เขียน class loader เองที่ load class files ให้ครบตั้งแต่ start โดยไม่ต้องเสียเวลา lookup หลายๆ ครั้ง
  2. ใช้ GraalVM ซึ่งทำ Ahead-of-time compiler และสร้างเป็น native image เหมือนเวลา build C หรือ Go program ทำให้ไม่ต้องพึ่ง class loader ในตอน start

วิธีที่ 1 น่าจะช่วยได้น้อย และใช้ได้กับ IntelliJ เท่านั้น ถ้าจะใช้กับ Jar file ก็ต้องทำเพิ่ม เช่น repack jar ใหม่ให้ load class ได้เร็วขึ้น

วิธีที่ 2 ลองแล้วแต่ยังใช้งานกับ Java 15 และ Spring boot ใหม่ๆ ไม่ได้ และมี feature ไม่ support เยอะ

Generate bytecode

Spring + Hibernate ทำ code gen หลายจุด

ณ ตอน start spring และ hibernate จะต้อง generate class เพื่อ wrap class เหล่านี้ ทำให้เกิด overhead ในตอน start ซึ่งขึ้นอยู่กับจำนวน class ที่มี จากที่นับดูใน project มีประมาณ 500 class

การ generate bytecode ณ runtime อาจสร้างปัญหากับ GraalVM อีกด้วย

Parse XML

ที่บริษัท config spring โดยใช้ XML และ annotation ร่วมกัน ส่วน hibernate ใช้ XML ล้วน

ตัว Java XML parser ดูเหมือนจะมีปัญหา performance อยู่พอสมควร เนื่องจากต้อง validate schema ให้ถูกต้อง และอาจจะต้อง lookup schema จาก remote endpoint มา

จุดนี้ยังไม่มั่นใจว่าหากเลิกใช้ XML แล้วใช้ Annotation อย่างเดียวจะลด overhead ได้มากขนาดไหน

Random things

ก่อนหน้าที่จะดู 3 เรื่องด้านบน ก็ได้ optimize compile speed ไปแล้ว

ระบบมี machine learning model อยู่ด้วย ซึ่ง load เมื่อตอน start ถ้าทำ lazy load หรือย้ายออกจะลดเวลาได้แค่ ~1-2sec

initialize connection pool ใช้เวลา ~1sec

ลบ model hibernate ออกหมด เร็วขึ้น ~1-2sec

เป้าที่อยากจะไปให้ถึงคือ น้อยกว่า 10secs ดูแล้วน่าจะเป็นไปไม่ได้ นอกเสียจากทำให้ระบบมัน super slim

Tools ที่ใช้