5 วิธีที่ AirSHIFT ได้ปรับปรุงประสิทธิภาพรันไทม์ของแอป React

กรณีศึกษาในชีวิตจริงเกี่ยวกับการเพิ่มประสิทธิภาพ React SPA

Kento Tsuji
Kento Tsuji
Satoshi Arai
Satoshi Arai
Yosuke Furukawa
Yosuke Furukawa

ประสิทธิภาพของเว็บไซต์ไม่ได้วัดแค่เวลาในการโหลด การให้ประสบการณ์การใช้งานที่รวดเร็วและตอบสนองต่อผู้ใช้เป็นสิ่งสำคัญอย่างยิ่ง โดยเฉพาะสำหรับแอปเดสก์ท็อปที่เน้นประสิทธิภาพการทำงานซึ่งผู้ใช้ใช้ทุกวัน ทีมวิศวกรของ Recruit Technologies ได้ทำโปรเจ็กต์การจัดโครงสร้างใหม่เพื่อปรับปรุงเว็บแอป AirSHIFT หนึ่งในแอปของตนให้มีประสิทธิภาพในการป้อนข้อมูลของผู้ใช้ที่ดียิ่งขึ้น มาดูว่าพวกเขาทําได้อย่างไร

ตอบสนองช้า ประสิทธิภาพการทำงานลดลง

AirSHIFT เป็นเว็บแอปพลิเคชันบนเดสก์ท็อปที่ช่วยให้เจ้าของร้านค้า เช่น ร้านอาหารและคาเฟ่ จัดการงานกะของพนักงานได้ แอปพลิเคชันหน้าเว็บเดียวที่สร้างด้วย React มีฟีเจอร์สำหรับไคลเอ็นต์ที่หลากหลาย รวมถึงตารางตารางกริดต่างๆ ของตารางกะที่จัดเรียงตามวัน สัปดาห์ เดือน และอื่นๆ

ภาพหน้าจอของเว็บแอป AirSHIFT

เมื่อทีมวิศวกรของ Recruit Technologies เพิ่มฟีเจอร์ใหม่ๆ ลงในแอป AirSHIFT ก็เริ่มได้รับความคิดเห็นเกี่ยวกับประสิทธิภาพที่ช้ามากขึ้น Yosuke Furukawa ผู้จัดการฝ่ายวิศวกรของ AirSHIFT กล่าวว่า

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

หลังจากทําการวิจัยแล้ว ทีมวิศวกรพบว่าผู้ใช้จํานวนมากพยายามโหลดตารางกะขนาดใหญ่ในคอมพิวเตอร์สเปคต่ำ เช่น แล็ปท็อป Celeron M 1 GHz จากเมื่อ 10 ปีก่อน

สปินเนอร์ทำงานไม่หยุดในอุปกรณ์ระดับล่าง

แอป AirSHIFT บล็อกเธรดหลักด้วยสคริปต์ที่มีราคาแพง แต่ทีมวิศวกรไม่ทราบว่าสคริปต์มีราคาแพงเพียงใดเนื่องจากกำลังพัฒนาและทดสอบบนคอมพิวเตอร์ที่มีสเปคสูงและมีการเชื่อมต่อ Wi-Fi ความเร็วสูง

แผนภูมิที่แสดงกิจกรรมรันไทม์ของแอป
เมื่อโหลดตารางกะ เวลาที่ใช้ในการโหลดประมาณ 80% จะใช้ไปกับสคริปต์ที่ทำงานอยู่

หลังจากสร้างโปรไฟล์ประสิทธิภาพในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome โดยเปิดใช้การจำกัด CPU และเครือข่ายแล้ว เห็นได้ชัดว่าจำเป็นต้องเพิ่มประสิทธิภาพ AirSHIFT ได้จัดตั้งกลุ่มทำงานเพื่อจัดการปัญหานี้ ต่อไปนี้คือ 5 สิ่งที่ทีมมุ่งเน้นเพื่อให้แอปตอบสนองต่ออินพุตของผู้ใช้ได้ดียิ่งขึ้น

1. จำลองตารางขนาดใหญ่

การแสดงตารางกะงานต้องใช้ขั้นตอนหลายขั้นตอนที่สิ้นเปลืองทรัพยากร เช่น การสร้าง DOM เสมือนและแสดงผลบนหน้าจอตามสัดส่วนจำนวนเจ้าหน้าที่และช่วงเวลา ตัวอย่างเช่น หากร้านอาหารมีพนักงานที่ทำงาน 50 คนและต้องการตรวจสอบตารางกะงานรายเดือน ตารางดังกล่าวจะเป็นตาราง 50 (สมาชิก) คูณด้วย 30 (วัน) ซึ่งจะทำให้ต้องแสดงผลคอมโพเนนต์เซลล์ 1,500 รายการ ซึ่งการดำเนินการนี้ค่อนข้างมีค่าใช้จ่ายสูง โดยเฉพาะสำหรับอุปกรณ์ที่มีสเปคต่ำ แต่สถานการณ์จริงกลับแย่กว่านั้น จากการศึกษา พบว่ามีร้านค้าที่จัดการพนักงาน 200 คน ซึ่งต้องใช้คอมโพเนนต์เซลล์ประมาณ 6,000 รายการในตารางรายเดือนตารางเดียว

AirSHIFT ได้ทำให้ตารางการเปลี่ยนเป็นเวอร์ชวลเพื่อลดต้นทุนของการดำเนินการนี้ ตอนนี้แอปจะเมานต์เฉพาะคอมโพเนนต์ภายในวิวพอร์ตและยกเลิกเมานต์คอมโพเนนต์นอกหน้าจอเท่านั้น

ภาพหน้าจอที่มีคำอธิบายประกอบซึ่งแสดงให้เห็นว่า AirSHIFT เคยใช้แสดงผลเนื้อหานอกวิวพอร์ต
ก่อน: การแสดงผลเซลล์ตารางการเปลี่ยนทั้งหมด
ภาพหน้าจอที่มีคำอธิบายประกอบซึ่งแสดงให้เห็นว่าตอนนี้ AirSHIFT จะแสดงผลเฉพาะเนื้อหาที่มองเห็นในวิวพอร์ต
หลัง: แสดงผลเฉพาะเซลล์ในวิวพอร์ต

ในกรณีนี้ AirSHIFT ใช้ react-virtualized เนื่องจากมีข้อกำหนดเกี่ยวกับการเปิดใช้ตารางตารางกริด 2 มิติที่ซับซ้อน นอกจากนี้ ยังกําลังหาวิธีแปลงการติดตั้งใช้งานเพื่อใช้ react-window เวอร์ชันน้ำหนักเบาในอนาคตด้วย

ผลลัพธ์

การทำให้เป็นเสมือนของตารางเพียงอย่างเดียวทำให้เวลาในการเขียนสคริปต์ลดลง 6 วินาที (ในสภาพแวดล้อม Macbook Pro ที่ชะลอความเร็ว CPU ลง 4 เท่าและ 3G ที่เร็ว) นี่เป็นการเพิ่มประสิทธิภาพที่ส่งผลมากที่สุดในโปรเจ็กต์การจัดระเบียบใหม่

ภาพหน้าจอที่มีคำอธิบายประกอบของการบันทึกแผงประสิทธิภาพของ Chrome DevTools
ก่อน: สคริปต์ทำงานประมาณ 10 วินาทีหลังจากผู้ใช้ป้อนข้อมูล
ภาพหน้าจอที่มีการกำกับเนื้อหาอีกภาพหนึ่งของการบันทึกแผงประสิทธิภาพของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
หลัง: สคริปต์ทำงาน 4 วินาทีหลังจากผู้ใช้ป้อนข้อมูล

2. ตรวจสอบด้วย User Timing API

ถัดไป ทีม AirSHIFT ได้ปรับโครงสร้างสคริปต์ที่ทำงานกับอินพุตของผู้ใช้ แผนภูมิเปลวไฟของ เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ช่วยให้คุณวิเคราะห์สิ่งที่เกิดขึ้นจริงในเธรดหลักได้ แต่ทีม AirSHIFT พบว่าการวิเคราะห์กิจกรรมของแอปพลิเคชันตามวงจรชีวิตของ React นั้นง่ายกว่า

React 16 แสดงการติดตามประสิทธิภาพผ่าน User Timing API ซึ่งคุณดูภาพได้จากส่วนการวัดเวลาของเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome AirSHIFT ใช้ส่วน "ช่วงเวลา" เพื่อค้นหาตรรกะที่ไม่จําเป็นซึ่งทํางานในเหตุการณ์วงจรชีวิตของ React

ส่วนการกําหนดเวลาของแผงประสิทธิภาพในเครื่องมือสําหรับนักพัฒนาเว็บใน Chrome
เหตุการณ์เกี่ยวกับเวลาของผู้ใช้ React

ผลลัพธ์

ทีม AirSHIFT พบว่ามีการปรับยอด React Tree ที่ไม่จำเป็นเกิดขึ้นก่อนการไปยังส่วนต่างๆ ของเส้นทางทุกครั้ง ซึ่งหมายความว่า React อัปเดตตารางกะโดยไม่จำเป็นก่อนการไปยังส่วนต่างๆ การอัปเดตสถานะ Redux ที่ไม่จำเป็นทำให้เกิดปัญหานี้ การแก้ไขนี้ช่วยประหยัดเวลาในการเขียนสคริปต์ได้ประมาณ 750 มิลลิวินาที AirSHIFT ได้ทําการเพิ่มประสิทธิภาพเล็กๆ น้อยๆ อื่นๆ ด้วย ซึ่งสุดท้ายแล้วส่งผลให้เวลาในการเขียนสคริปต์ลดลงทั้งหมด 1 วินาที

3. โหลดคอมโพเนนต์แบบ Lazy Loading และย้ายตรรกะที่มีค่าใช้จ่ายสูงไปยัง Web Worker

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

ตอนนี้ AirSHIFT ใช้ React.lazy และ Suspense เพื่อแสดงตัวยึดตําแหน่งสำหรับเนื้อหาตารางขณะโหลดคอมโพเนนต์จริงแบบ Lazy เพื่อปรับปรุงประสบการณ์นี้

ทีม AirSHIFT ยังย้ายข้อมูลตรรกะทางธุรกิจที่มีราคาแพงบางส่วนภายในคอมโพเนนต์ที่โหลดแบบเลื่อนเวลาไปไว้ที่Web Worker ด้วย วิธีนี้ช่วยแก้ปัญหาการกระตุกของอินพุตของผู้ใช้ด้วยการทำให้เธรดหลักว่างเปล่าเพื่อให้มุ่งเน้นที่การตอบสนองต่ออินพุตของผู้ใช้

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

ใน App.js ให้ใช้ React.lazy และ Suspense เพื่อแสดงเนื้อหาสำรองขณะโหลด

/** App.js */
import React, { lazy, Suspense } from 'react'

// Lazily loading the Cost component with React.lazy
const Hello = lazy(() => import('./Cost'))

const Loading = () => (
 
<div>Some fallback content to show while loading</div>
)

/
/ Showing the fallback content while loading the Cost component by Suspense
export default function App({ userInfo }) {
   
return (
   
<div>
     
<Suspense fallback={<Loading />}>
       
<Cost />
     
</Suspense>
    </
div>
 
)
}

ในคอมโพเนนต์ต้นทุน ให้ใช้ comlink เพื่อเรียกใช้ตรรกะการคํานวณ

/** Cost.js */
import React from 'react';
import { proxy } from 'comlink';

// import the workerlized calc function with comlink
const WorkerlizedCostCalc = proxy(new Worker('./WorkerlizedCostCalc.js'));
export default async function Cost({ userInfo }) {
 
// execute the calculation in the worker
 
const instance = await new WorkerlizedCostCalc();
 
const cost = await instance.calc(userInfo);
 
return <p>{cost}</p>;
}

ใช้ตรรกะการคำนวณที่ทำงานในเวิร์กเกอร์และแสดงด้วย comlink

// WorkerlizedCostCalc.js
import { expose } from 'comlink'
import { someExpensiveCalculation } from './CostCalc.js'

// Expose the new workerlized calc function with comlink
expose
({
  calc
(userInfo) {
   
// run existing (expensive) function in the worker
   
return someExpensiveCalculation(userInfo);
 
}
}, self);

ผลลัพธ์

แม้ว่าจะมีตรรกะแบบจำกัดที่เปลี่ยนเป็นเวิร์กเกอร์เป็นการทดลอง แต่ AirSHIFT ก็ย้าย JavaScript ประมาณ 100 มิลลิวินาทีจากชุดข้อความหลักไปยังชุดข้อความของผู้ปฏิบัติงาน (จำลองด้วยการจำกัด CPU 4 เท่า)

ภาพหน้าจอของการบันทึกแผงประสิทธิภาพของเครื่องมือสําหรับนักพัฒนาเว็บใน Chrome ซึ่งแสดงให้เห็นว่าขณะนี้สคริปต์กำลังทำงานบนเว็บเวิร์กเกอร์แทนเธรดหลัก

ปัจจุบัน AirSHIFT กำลังสำรวจว่าสามารถโหลดคอมโพเนนต์อื่นๆ แบบ Lazy Load ได้หรือไม่ และสามารถส่งผ่านตรรกะเพิ่มเติมไปยัง Web Worker เพื่อลดการกระตุกได้หรือไม่

4. การตั้งงบประมาณด้านประสิทธิภาพ

เมื่อใช้การเพิ่มประสิทธิภาพทั้งหมดเหล่านี้แล้ว สิ่งสำคัญคือต้องตรวจสอบว่าแอปยังคงมีประสิทธิภาพอยู่เสมอ ตอนนี้ AirSHIFT ใช้ bundlesize เพื่อไม่ให้มีขนาดไฟล์ JavaScript และ CSS เกินขนาดปัจจุบัน นอกเหนือจากการตั้งงบประมาณพื้นฐานเหล่านี้แล้ว ทีมยังได้สร้างแดชบอร์ดเพื่อแสดงเปอร์เซ็นต์ต่างๆ ของเวลาในการโหลดตารางการเปลี่ยนเพื่อตรวจสอบว่าแอปพลิเคชันมีประสิทธิภาพหรือไม่แม้ในสภาพที่ไม่เหมาะสม

  • ตอนนี้ระบบจะวัดเวลาในการดำเนินการสคริปต์เสร็จสมบูรณ์สำหรับเหตุการณ์ Redux แต่ละรายการ
  • ระบบจะรวบรวมข้อมูลประสิทธิภาพใน Elasticsearch
  • ประสิทธิภาพของเหตุการณ์แต่ละรายการในเปอร์เซ็นต์ไทล์ที่ 10, 25, 50 และ 75 จะแสดงเป็นภาพด้วย Kibana

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

แผนภูมิที่แสดงว่าเปอร์เซ็นไทล์ที่ 75 เสร็จสมบูรณ์ในประมาณ 2,500 มิลลิวินาที เปอร์เซ็นไทล์ที่ 50 เสร็จสมบูรณ์ในประมาณ 1,250 มิลลิวินาที เปอร์เซ็นไทล์ที่ 25 เสร็จสมบูรณ์ในประมาณ 750 มิลลิวินาที และเปอร์เซ็นไทล์ที่ 10 เสร็จสมบูรณ์ในประมาณ 500 มิลลิวินาที
แดชบอร์ด Kibana ที่แสดงข้อมูลประสิทธิภาพรายวันตามเปอร์เซ็นต์ไทล์

ผลลัพธ์

จากกราฟด้านบน คุณจะเห็นว่าตอนนี้ AirSHIFT ส่วนใหญ่ใช้งบประมาณ 3 วินาทีสําหรับผู้ใช้ในเปอร์เซ็นไทล์ 75 และโหลดตารางการเปลี่ยนภายใน 1 วินาทีสําหรับผู้ใช้ในเปอร์เซ็นไทล์ 25 การเก็บรวบรวมข้อมูลประสิทธิภาพ RUM จากเงื่อนไขและอุปกรณ์ต่างๆ ช่วยให้ AirSHIFT ตรวจสอบได้ว่าการเปิดตัวฟีเจอร์ใหม่ส่งผลต่อประสิทธิภาพของแอปพลิเคชันจริงหรือไม่

5. แฮ็กกาธอนด้านประสิทธิภาพ

แม้ว่าความพยายามทั้งหมดในการเพิ่มประสิทธิภาพเหล่านี้จะสำคัญและส่งผลดี แต่การทำให้ทีมวิศวกรและทีมธุรกิจให้ความสำคัญกับการพัฒนาที่ไม่เกี่ยวข้องกับฟังก์ชันการทำงานก็ไม่ใช่เรื่องง่ายเสมอไป ปัญหาส่วนหนึ่งคือการเพิ่มประสิทธิภาพบางอย่างเหล่านี้ไม่สามารถวางแผนได้ นวัตกรรมเหล่านี้ต้องอาศัยการทดลองและแนวคิดแบบลองผิดลองถูก

ตอนนี้ AirSHIFT กำลังจัดแฮ็กแฮทเกี่ยวกับประสิทธิภาพ 1 วันภายในองค์กรเพื่อให้วิศวกรมุ่งเน้นที่งานที่เกี่ยวข้องกับประสิทธิภาพเท่านั้น ในแฮ็กกาธอนเหล่านี้ ทีมจะนําข้อจํากัดทั้งหมดออกและเคารพความคิดสร้างสรรค์ของวิศวกร ซึ่งหมายความว่าการใช้งานที่ส่งผลต่อความเร็วควรได้รับการพิจารณา AirSHIFT ได้แบ่งกลุ่มออกเป็นทีมเล็กๆ เพื่อให้การแฮ็กคอลตันสามารถดำเนินการได้เร็วขึ้น และแต่ละทีมจะแข่งขันกันเพื่อดูว่าใครจะปรับปรุงคะแนนประสิทธิภาพ Lighthouse ได้มากที่สุด แต่ละทีมแข่งขันกันอย่างดุเดือด 🔥

รูปภาพแฮ็กกาธอน

ผลลัพธ์

แนวทางแฮ็กกาธอนได้ผลดีกับพวกเขา

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

ผลพลอยได้ที่ดีคือทีมวิศวกรคนอื่นๆ จำนวนมากภายใน Recruit สนใจแนวทางปฏิบัติจริงนี้ และตอนนี้ทีม AirSHIFT กำลังจัดแฮ็กแฮทแบบเร่งด่วนหลายรายการภายในบริษัท

สรุป

เส้นทางนี้ไม่ใช่เส้นทางที่ง่ายที่สุดสำหรับ AirSHIFT ในการทําการเพิ่มประสิทธิภาพเหล่านี้ แต่คุ้มค่าแน่นอน ตอนนี้ AirSHIFT โหลดตารางการเปลี่ยนภายใน 1.5 วินาทีเป็นค่ามัธยฐาน ซึ่งปรับปรุงประสิทธิภาพได้ 6 เท่าจากก่อนเริ่มโปรเจ็กต์

หลังจากเปิดตัวการเพิ่มประสิทธิภาพแล้ว ผู้ใช้รายหนึ่งกล่าวว่า

ขอขอบคุณอย่างยิ่งที่ทำให้ตารางกะทำงานโหลดเร็ว ตอนนี้การจัดตารางงานเป็นกะมีประสิทธิภาพมากขึ้น