มาลองสร้าง Spring Boot App ด้วย GraalVM Native Image
ปัญหาหนึ่งของ Java Application อย่าง Spring Boot Web Service ที่ต้องทำงานบน JVM นั้นคือใช้ทรัพยากรของระบบอย่างมาก และเป็นจุดด้อยที่สำคัญมากสำหรับระบบที่ทำงานเป็นแบบ Microservice บน Cloud เพราะทำให้มีค่าใช้จ่ายมาก เมื่อเทียบกับภาษาใหม่อย่าง Golang ที่ compile เป็น machine code ที่ทำงานได้เลยโดยไม่ต้องใช้ Virtual Machine เพื่อให้ Application ทำงานได้ แต่ในโลกของ Java ก็มีเครื่องมือหนึ่งที่สร้างมาเพื่อแก้ปัญหานนี้ นั้นคือ GraalVM โดย Oracle ที่จะทำให้ Java Application ทำงานบน GraalVM และแปลงเป็น machine code และทำงานได้โดยไม่ต้องใช้ JVM ทำให้ใช้ทรัพยากรของระบบลดลงมาก (GraalVM เคลมว่าใช้ memory ลดลง 5 เท่า) ดังนั้น ในบทความนี้จะมาสร้าง Spring Boot Web Service ให้ทำงานบน GraalVM ซึ่งแปลง application ให้เป็น machine code โดยที่ Spring มี Spring Native สร้างมาเพื่อรองรับการทำงานบน GraalVM ด้วย
GraalVM
GraalVM เป็น high-performance runtime ที่มีการปรับปรุงความสามารถและประสิทธิภาพของ application อย่างมีนัยสำคัญ ซึ่งเป็นแนวคิดสำหรับ Microservice โดยที่ออกแบบมาสำหรับ application ที่เขียนด้วย Java, JavaScript และ ภาษาที่เป็น LLVM-based อย่าง C และ C++ รวมทั้ง dynamic language อื่นๆ อีกด้วย
ซึ่ง GraalVM ทะลายความแตกต่างของภาษาโปรแกรมและทำให้หลายๆ ภาษาสามารถทำงานได้บน GraalVM อย่างมีประสิทธิภาพ
ติดตั้ง GraalVM
สำหรับการใช้งานบนเครื่องเรานั้นจะต้องติดตั้ง GraalVM ก่อน ซึ่งก็เหมือนการติตั้ง JDK โดยที่ GraalVM จะมาพร้อมกับเครื่องมือที่ใช้สำหรับการ compile application เป็น native image
ซึ่ง GraalVM จะ distribute เป็น 2 edition คือ Community ที่เป็นตัวฟรี และ Enterprise ที่เป็นตัวเสียเงิน (แน่นอนในบทความนี้จะใช้ Community edition)
ตัว Community edition ก็สามารถ download ได้จาก Github ส่วน Enterprise edition จะ download จากเว็บของ Oracle เอง
หลังจากที่ download file มาแล้วก็แตกไฟล์ด้วยคำสั่ง
tar -xvf <graalvm-archive>.tar.gz
และย้าย folder ที่แตกไฟล์แล้วไปไว้ใน Java Virtual Machine ในเครื่องด้วยคำสั่ง
sudo mv <graalvm> /Library/Java/JavaVirtualMachines
สามารถตรวจสอบว่าย้ายไปเรียบร้อยไหม ด้วยคำสั่ง /usr/libexec/java_home -V
ถ้าในเครื่องเรามีหลาย JDK จะต้องตั้งค่า PATH
และ เปลี่ยน JAVA_HOME
ให้เป็นของ GraalVM เสียก่อน
ตรวจสอบ Java ของเครื่องว่าใช้ GraalVM หรือยัง ด้วยคำสั่ง java -version
และใช้ คำสั่ง gu list
เพื่อตรวจสอบ GraalVM ในเครื่อง
แต่ถ้าใช้ macOS Catalina ขึ้นไปจะต้องลบ quarantine attribute ด้วยคำสั่งนี้
sudo xattr -r -d com.apple.quarantine path/to/graalvm/folder/
สร้าง Spring Boot Web Service
ในบทความนี้จะสร้าง Spring Boot application ง่ายๆ โดยใช้ Spring WebFlux ด้วย Spring Initializr
และสร้าง HelloController
class ง่ายๆ (ในบทความนี้จะสร้างเป็น inner class เลย เพื่อให้ controller กระชับ อยู่ในไฟล์เดียว)
หลังจากนั้นลอง run Spring Boot Web Service ดูว่าทำงานได้หรือไม่ เป็นอันเสร็จสิ้น
ซึ่งในบทความนี้ใช้ maven เป็น build tool ดังนั้นเมื่อ run คำสั่ง mvn package
แล้วจะได้ jar ไฟล์ใน folder /target
โดยที่เราสามารถ run jar ไฟล์นี้ได้ ด้วยคำสั่ง
java -jar /target/demo-graalvm-0.0.1-SNAPSHOT.jar
เพื่อ run Spring Boot app ขึ้นมาแล้วลองใช้ Postman ยิงไปที่ /hello
endpoint ดูว่าได้ response เป็น Hello, World
กลับมาหรือไม่
เตรียม build GraalVM Native Image
หลังจากที่ได้สร้าง web service ง่ายๆ แล้วต่อไปก็มาเตรียม Spring Boot project ให้สามารถ build เป็น GraalVM native image ได้ ซึ่ง Spring framework ได้มี Spring Native project (ซึ่งตอนนี้อยู่ใน Spring experiment) มารองรับการสร้าง GraalVM Native Image ดังนั้นเราจะต้องเพิ่ม dependency spring-graalvm-native
ใน pom.xml แต่เนื่องจากว่า Spring Native ยังไม่ release เป็น Official และยังอยู่ใน Spring milestone ดังนั้น เราจะต้องเพิ่ม spring-milestones
ใน repository และ pluginRepository เพื่อให้ maven resolve dependency ได้จาก spring-milestone repository
ก่อนหน้า Spring Native เวอร์ชั่น 0.8.3
จำเป็นต้องเพิ่ม proxyBeanMethods = false
ใน @SpringBootApplication
และ @Configuration
เพื่อปิดการใช้งาน GCLIB proxies ที่ GraalVM ไม่รองรับ โดยใช้ JDK proxies แทน ดังนั้นใน Spring Boot web service ก็ไม่ต้องแก้ไขอะไร
เนื่องจาก Spring Boot ที่ทำงานบน GraalVM ไม่ได้ทำงานบน JDK โดยตรง ดังนั้นเราจะต้องเพิ่ม main.class
ในส่วนของ properties
เพื่อให้ Spring Native ใช้ตอน build GraalVM native image
ต่อมาก็ต้องมาเตรียม build script สำหรับ run maven build และ compile native image ซึ่งก็ใช้ compile.sh
จาก project ตัวอย่าง ของ Spring Native มาใช้งาน ซึ่งมีส่วนต่างๆ ดังนี้
ส่วนแรกจะเป็นส่วนของ run maven build โดยเริ่มจากลบ folder target
และสร้าง folder target/native-image
ขึ้นมาใหม่ และใช้คำสั่ง mvn package
เพื่อ compile java code และสร้างเป็นไฟล์ .jar
ของ Spring Boot app จากนั้นก็แตกไฟล์ .jar
ออกมาไว้ใน folder target/native-image
ส่วนต่อไปเป็นการ compile เป็น native image ด้วย GraalVM native image สร้างเป็น machine code
Build GraalVM Native Image
เมื่อเตรียมทุกอย่างเรียบร้อยก็มาลอง build GraalVM native image ด้วย build script compile.sh
ซึ่งในส่วนของ compile native image จะใช้เวลานานพอสมควร ขึ้นอยู่กับเครื่องที่ใช้ compile (เครื่องผมใช้เวลานานๆๆๆ มากๆๆๆ)
./compile.sh
หลังจาก compile เรียบร้อยแล้วจะได้ executable file ชื่อเดียวกับ project ใน folder target
และสามารถ run ไฟล์นี้ได้เลย ด้วยคำสั่ง ./target/demo-graalvm
จะเห็นได้ว่า Spring Boot app ใช้เวลา start time แค่ 0.12 วินาที เท่านั้น
เมื่อเทียบกับ Spring Boot app ที่ run บน JVM ใช้เวลา start time 3.4 วินาที
และในมุมของหน่วยความจำ บน GraalVM ก็ใช้ memory เพียง 24 MB เท่านั้น
เมื่อเทียบกับ บน JVM ที่ใช้ memory ไป 440M
สรุป
จกาที่ได้ลองใช้ GraalVM เบื้องต้นนี้จะเห็นได้ว่า tool นี้สามารถแก้ปัญหาของ Java Application ที่ run บน JVM แล้วใช้ทรัพยากรของเครื่องอย่างมาก ทำให้ Web Service ที่ใช้ Spring Boot มีขนาดเล็กลงอย่างมากและเมื่อทำเป็น Microservice ที่ทำงานบน Cloud ก็ใช้ทรัพยากรและที่แน่นอนใช้เงินน้อยลง ทำให้ application ที่พัฒนาด้วย Java เทียบเคียงได้กับ Golang แม้ว่าของ Golang จะเล็กกว่าแต่ก็ลดช่องว่างลงได้เยอะมาก แต่ GraalVM กับ Spring Boot ก็ยังต้องพัฒนาอีกมากตอนนี้ก็อยู่ใน experiment project อยู่ และคง official release ในอีกไม่นาน