3 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Linear เป็นเครื่องมือเพิ่มประสิทธิภาพการทำงานที่จัดการงานติดตาม issue ด้วยฐานข้อมูลในเบราว์เซอร์และการซิงก์แบบ local-first ทำให้การอัปเดต issue สะท้อนบน UI ได้ภายในไม่กี่มิลลิวินาที
  • ฐานข้อมูลจริงที่ UI อ่านอยู่ใน IndexedDB โดยการเปลี่ยนแปลงจะถูกนำไปใช้ในเครื่องก่อน แล้วค่อยส่งไปยังเซิร์ฟเวอร์แบบ asynchronous และกระจาย delta กลับผ่าน WebSocket
  • การโหลดครั้งแรกใช้กลยุทธ์ลดเวลารอเครือข่าย เช่น ส่ง JavaScript·CSS ให้น้อย, แบ่งโค้ดอย่างหนัก, ใช้ modulepreload, service worker precache และ inline app shell
  • ซิงก์เอนจินจะ hydrate ข้อมูลจาก IndexedDB เข้าไปยัง MobX object pool, เก็บการเปลี่ยนแปลงไว้ใน transaction queue และเรนเดอร์ใหม่เฉพาะเซลล์ที่จำเป็นด้วยสถานะแบบ observable ระดับฟิลด์
  • ความเร็วที่รับรู้ได้เกิดจากผลลัพธ์ของ การออกแบบระบบ ที่รวมทั้งการป้อนข้อมูลเน้นคีย์บอร์ด, global command palette, แอนิเมชันที่เป็นมิตรกับ GPU และเวลาเปลี่ยนผ่านที่สั้น

ฐานข้อมูลภายในเบราว์เซอร์

  • เว็บแอป CRUD แบบดั้งเดิมจะผ่านขั้นตอนคำขอ HTTP หลังผู้ใช้คลิก, การค้นหาฐานข้อมูลบนเซิร์ฟเวอร์, การรับคำตอบ และการรีเพนต์ของเบราว์เซอร์ ทำให้เกิดสปินเนอร์, skeleton และ UI ค้างนานหลายร้อยมิลลิวินาที
  • Linear วางฐานข้อมูลจริงที่ UI อ่านไว้ใน IndexedDB ของเบราว์เซอร์ นำการเปลี่ยนแปลงไปใช้ในเครื่องก่อน แล้วส่งไปยังเซิร์ฟเวอร์แบบ asynchronous จากนั้นเซิร์ฟเวอร์จะ broadcast delta ไปยังไคลเอนต์อื่นผ่าน WebSocket
  • คอขวดที่ใหญ่ที่สุดของเว็บแอปที่เร็วคือเครือข่าย และการส่งข้อมูลระหว่างไคลเอนต์กับเซิร์ฟเวอร์มีต้นทุนระดับหลายร้อยมิลลิวินาที
  • แกนหลักของแนวทาง Linear คือทำให้คำขอเครือข่ายมองไม่เห็นสำหรับผู้ใช้ และกำจัดสถานะการโหลดให้ได้มากที่สุด
// Linear
issue.title = "Faster app launch";
issue.save();
  • issue.title = "Faster app launch" คือการอัปเดต data store ในหน่วยความจำ และในกรณีของ Linear ใช้ MobX observable
  • issue.save(); คือการที่ซิงก์เอนจินจัดการแบบ batch แล้วนำธุรกรรมเข้าไปต่อคิวเพื่อ flush ไปยังเซิร์ฟเวอร์
  • UI จะเรนเดอร์ใหม่แบบ synchronous โดยอิงจากการเปลี่ยนแปลงในหน่วยความจำฝั่งเครื่อง และการซิงก์ข้อมูลเกิดขึ้นในเบื้องหลัง จึงไม่จำเป็นต้องมีสปินเนอร์
  • Tuomas กล่าวในงานคอนเฟอเรนซ์ปี 2024 ว่าโค้ดชุดแรกที่เขาเขียนใน Linear คือซิงก์เอนจิน และใช้ถ้อยคำว่านี่เป็นแนวทางที่ไม่ค่อยพบในสตาร์ตอัป
  • แอปส่วนใหญ่ไม่จำเป็นต้องสร้างซิงก์เอนจินเองแบบ Linear และเพียงใช้ optimistic update ของ TanStack Query กับ SWR ก็สามารถให้ความรู้สึกเร็วได้ใกล้เคียงมาก
  • optimistic request ให้ผลการปรับปรุงสูงผ่านการตัดสปินเนอร์ที่ไม่จำเป็น, อัปเดตสถานะทันที, ตรวจสอบในเบื้องหลัง และ rollback เมื่อจำเป็น
  • ความตอบสนองของ UI ไม่ควรขึ้นอยู่กับ network latency และความเร็วที่ผู้ใช้รับรู้นั้นถูกกำหนดโดยความเร็วในการตอบสนองของอินเทอร์เฟซมากกว่าความเร็วการตอบกลับของเซิร์ฟเวอร์
  • แอบดูสแต็กของ Linear

    • Linear สร้างขึ้นบนสแต็กที่เรียบง่ายอย่าง React, TypeScript, MobX, Postgres และ CDN
    • ฟรอนต์เอนด์ใช้ React และ react-dom, MobX, TypeScript, Rolldown-Vite กับ plugin-react-oxc, ProseMirror กับ y-prosemirror, Radix UI primitives, Emotion กับ StyleX, Comlink, idb, graphql-request, Sentry และ Inter Variable
    • แบ็กเอนด์ใช้ Node.js และ TypeScript, PostgreSQL บน Cloud SQL, Memorystore Redis, turbopuffer, Kubernetes บน GCP และ Cloudflare Workers
    • เดสก์ท็อปไคลเอนต์ใช้ Electron ส่วนมือถือมีการเขียนใหม่ทั้งหมดแยกต่างหากด้วย Swift สำหรับ iOS และ Kotlin
    • เว็บไซต์การตลาดใช้ Next.js, styled-components และ inline SVG sprite
    • Linear ยังคงใช้การเรนเดอร์ฝั่งไคลเอนต์ และเป็นตัวอย่างว่า CSR ก็ให้ความรู้สึกฉับไวได้ หากมีสถาปัตยกรรมและการออกแบบที่ถูกต้อง
    • การคงทั้งแอปไว้ฝั่งไคลเอนต์ช่วยให้มี mental model ที่เรียบง่ายขึ้น โดยลดความซับซ้อนอย่างการแยกเซิร์ฟเวอร์·ไคลเอนต์, การเข้าถึง window ได้หรือไม่ และการตั้งค่า cache header

ทำให้การโหลดครั้งแรกรู้สึกว่าเกิดขึ้นทันที

  • เวลาที่ใช้กว่าจะเริ่มทำงานจริงได้ในเครื่องมือเพื่อการเพิ่มประสิทธิภาพเป็นรายละเอียดสำคัญ
  • การโหลดเริ่มต้นของแอปฝั่งไคลเอนต์อาจช้าจากลำดับของการขอ index.html, การขอ JavaScript และ CSS, การประมวลผลการยืนยันตัวตน, และการขอ API เพื่อแสดงแอป
  • โฟลว์บันเดลเลอร์ของ Linear: Parcel, Rollup, Vite, Rolldown

    • ความเร็วที่รู้สึกได้ทันทีเริ่มต้นตั้งแต่ build time ก่อน runtime และสิ่งสำคัญสำหรับการโหลดที่เร็วคือการลดปริมาณ JavaScript และ CSS ที่ส่งออกไป
    • Linear เขียน build pipeline ใหม่ตามลำดับ Parcel → Rollup → Vite → Rolldown โดยแต่ละการย้ายมีเป้าหมายเพื่อลดปริมาณ JavaScript·CSS และปรับปรุงประสบการณ์ของนักพัฒนา
    • ตัวเลขการปรับปรุงตามบล็อกของ Linear
    • โค้ดที่ส่งลดลง 50%
    • ขนาดหลังบีบอัดลดลง 30%
    • การโหลดหน้าแบบ cold cache ดีขึ้น 10~30%
    • Time-to-first-paint ของมุมมอง active-issues บน Safari ลดลง 59%
    • การใช้หน่วยความจำลดลง 70~80%
    • ส่วนสำคัญของการปรับปรุงมาจากการผสมกันของการตัดสินใจรองรับเฉพาะเบราว์เซอร์สมัยใหม่, dead-code elimination ที่ดีกว่า, และการแยกโค้ดอย่างเข้มข้น
    • การยุติการรองรับระบบเก่าทำให้ได้ประโยชน์มากจากการลบ polyfill, ES5 transpile, และ nomodule fallback
    • แม้หลังการปรับแต่ง Linear ก็ยังส่ง JavaScript ที่ minified แล้วราว 21MB แต่ใช้วิธีแยกอย่างเข้มข้นเป็นชังก์ระดับ route หลายร้อยชิ้นแล้วดึงมาเมื่อจำเป็น
    • หัวใจสำคัญไม่ใช่การเลือกบันเดลเลอร์ตัวใดตัวหนึ่ง แต่คือการตัดเบราว์เซอร์รุ่นเก่าออก, เปลี่ยนไปใช้ native ESM, และทำ code splitting อย่างจริงจัง
    • เมื่อขั้นตอนเหล่านี้สะสมกัน JavaScript สำหรับการโหลดครั้งแรกของ Linear จะลดลงเหลือประมาณครึ่งหนึ่ง และเวลา build ลดลงไม่ใช่แค่ระดับหลักหน่วย แต่ลดลงถึงอีกหนึ่งลำดับขั้น
  • Preload หลังการโหลดเริ่มต้น

    • เมื่อแบ่ง JavaScript เป็นชังก์เล็ก ๆ จะเกิดปัญหาการโหลดแบบน้ำตกที่แต่ละชังก์ต้อง import ชังก์อื่นต่อกันไป
    • Linear จัดให้เบราว์เซอร์เห็นรายการทั้งหมดและเริ่ม request แบบขนานก่อนที่ JavaScript จะรัน เพื่อให้เมื่อ entry script ไปถึง import แรก ชังก์ที่เกี่ยวข้องอยู่ในแคชแล้ว
    • โดยทำให้ค่า modulepreload ใน <head> และค่า crossorigin ของ entry script ตรงกัน เพื่อให้เบราว์เซอร์ไม่มอง preload กับ import เป็นทรัพยากรคนละตัว และสามารถใช้ fetch ที่แคชไว้ซ้ำได้
    • ไทม์ไลน์การโหลดแบบ cold load จึงเปลี่ยนจากลำดับแบบน้ำตกเป็นการส่งชุดขนานเพียงครั้งเดียว โดยงานเครือข่ายยังมีอยู่แต่ทำพร้อมกันทั้งหมด
    • งานนี้จะถูกรันเบื้องหลังเมื่อผู้ใช้มาถึงหน้าเข้าสู่ระบบครั้งแรก และไม่กี่วินาทีต่อมาแอปทั้งหมดก็ถูกเก็บไว้ในแคชพร้อมเสิร์ฟทันที
    โฆษณา
  • Service worker เพื่อความเร็วที่สูงขึ้นและความสามารถออฟไลน์

    • ชังก์ระดับ route ของมุมมองที่ผู้ใช้ยังไม่เคยเปิดจะถูกแคชเบื้องหลังโดย service worker
    • Service worker มี precache manifest ที่ฝังอยู่ในซอร์ส ซึ่งครอบคลุม asset แบบ hash ราว 1,200 รายการ ทั้ง route chunk, ไอคอน และฟอนต์
    • โครงสร้างนี้ทำให้ภายในไม่กี่วินาทีหลังถึงหน้าล็อกอิน แอปทั้งหมดจะเข้าไปอยู่ในแคช
    • หลังจากนั้นการนำทางจะข้ามเครือข่ายไปทั้งหมด และ service worker จะตอบกลับจากแคชของตัวเองโดยตรงโดยไม่ผ่าน HTTP cache
    • เมื่อทำงานร่วมกับ sync engine แบบ local-first และข้อมูลผู้ใช้ที่เก็บอยู่ใน IndexedDB แล้ว Linear ก็สามารถใช้งานแบบออฟไลน์ได้
    • รองรับการอ่าน issue, สร้าง issue ใหม่, แก้ไขชื่อและคำอธิบาย, และเปลี่ยนสถานะ
    • ทุกการทำงานจะถูกเข้าคิวไว้ใน local transaction store และ flush เมื่อการเชื่อมต่อกลับมา
    • modulepreload คือกลไกที่ดึงสิ่งที่ต้องใช้ตอนนี้มาแบบขนานเพื่อไม่ให้เบราว์เซอร์ติดอยู่กับ serial import chain
    • Service worker คือกลไกที่เตรียมสิ่งที่จะต้องใช้ถัดไป
    • ขั้นตอนการโหลดเร็วของ Linear คือการตัดโค้ดออกให้ได้มากที่สุด, แบ่งเป็นชิ้นเล็ก ๆ, และทำ background precache โดยมีเป้าหมายเพื่อทำให้ network request เร็วขึ้นหรือกำจัดมันไปเลย
  • การจัดโครงสร้าง vendor bundle

    • แต่ละแพ็กเกจที่ Linear ใช้จะถูกแยกเป็นชังก์ของตัวเองและแคชอย่างอิสระ
    • vendor.js แบบดั้งเดิมจะทำให้แคชของ dependency graph ทั้งหมดใช้ไม่ได้ทันทีแม้อัปเดตเพียง dependency ตัวเดียว
    • การแยกชังก์ของ Linear สร้าง vendor caching แบบละเอียดแทนไฟล์ใหญ่ไฟล์เดียว ดังนั้นเมื่ออัปเดต dependency เฉพาะตัว จะมีเพียงชังก์นั้นที่ถูก invalidated ส่วนที่เหลือยังคงอยู่ในแคช
  • การโหลดไฟล์ฟอนต์ขนาดใหญ่

    • การโหลดฟอนต์ที่ผิดพลาดอาจทำให้ข้อความหายไปชั่วคราว, เกิด layout shift เมื่อสลับเป็นฟอนต์จริง, และเกิด fetch ซ้ำจาก preload ที่ไม่ตรงกัน
    • Linear preload ฟอนต์ Inter Variable ใน <head> และทำ preconnect ไปยัง static.linear.app
    <link rel="preload"
          href="https://static.linear.app/fonts/InterVariable.woff2?v=4.1";
          as="font" type="font/woff2" crossorigin="anonymous">
    <link rel="preconnect" href="https://static.linear.app"; crossorigin>
    
    • Variable font จัดการแกน weight ทั้งช่วง 100~900 ด้วย woff2 ไฟล์เดียว จึงไม่ต้อง request แยกตามแต่ละ weight
    • font-display: swap จะเรนเดอร์ fallback stack ทันที แล้วค่อยสลับเป็น Inter หลังโหลดเสร็จ
    • crossorigin="anonymous" ในแท็ก preload เป็นการตั้งค่าหลักที่ทำให้เมื่อ CSS อ้างถึงฟอนต์เดียวกันในภายหลัง เบราว์เซอร์สามารถใช้ทรัพยากรที่แคชไว้ซ้ำได้
    • หากไม่มี crossorigin โหมด CORS ของ preload กับการอ้างอิงใน CSS จะต่างกัน ทำให้เบราว์เซอร์ต้องดึงฟอนต์ซ้ำอีกครั้ง
  • Inline app shell

    • Linear ใส่ CSS แบบ inline ไว้ใน <head> มากพอสำหรับวาดสถานะกำลังโหลด เพื่อให้แสดง app shell ได้โดยไม่ต้องรอ request ของ stylesheet ภายนอก
    • JavaScript แบบ inline จะตัดสินใจเงื่อนไขที่จำเป็นต่อประสบการณ์เริ่มต้นได้ทันที
    • ตรวจจับ Electron และ Linear user agent แล้วเพิ่มคลาส electron
    • ถ้าไม่มี localStorage.ApplicationStore ก็เพิ่มคลาส logged-out
    • กู้คืน shell token เช่น พื้นหลัง sidebar, ความกว้าง sidebar, และ dark mode จาก localStorage.splashScreenConfig
    • หากผู้ใช้ตั้งค่าให้เปิดลิงก์ในแอปเดสก์ท็อป จะปรับความกว้าง sidebar เป็น 8px
    • ก่อนที่ JavaScript bundle แรกจะมาถึงผ่านเครือข่าย หน้าจอโหลดก็จะมีธีม ขนาด และตำแหน่งที่ตรงกับสถานะการล็อกอินอยู่แล้ว
    • วิธีที่เร็วที่สุดในการทำให้ผู้ใช้รู้สึกว่าแอปพร้อมทันทีที่กด Enter หลังพิมพ์ URL คือส่ง app shell ไปพร้อมกับการตอบกลับ index.html ตั้งแต่แรก
    โฆษณา
  • เรนเดอร์ก่อนแล้วค่อยยืนยันตัวตนทีหลัง

    • โฟลว์การยืนยันตัวตนทั่วไปจะเป็นลำดับ HTML fetch, โหลด bundle, ตรวจสอบ session, fetch ผู้ใช้, fetch workspace, แล้วค่อย render ซึ่งอาจใช้เวลา 1~3 วินาทีกว่าผู้ใช้จะเห็นอะไรบางอย่าง
    • Linear จัดการการยืนยันตัวตนแบบเดียวกับการประมวลผลการเปลี่ยนแปลง คือสมมติว่าเป็นเส้นทางปกติไว้ก่อนแล้วตรวจสอบในเบื้องหลัง
    • แอป CRUD ส่วนใหญ่จะเก็บ session จริงไว้ใน HttpOnly cookie และเพิ่ม cookie แยกที่ JavaScript อ่านได้หรือเพิ่ม request ไปที่ /me เพื่อให้ frontend รู้ระหว่างเริ่มต้นว่าผู้ใช้ล็อกอินอยู่หรือไม่
    • inline boot script ของ Linear ไม่ใช้สัญญาณยืนยันตัวตนแบบขนาน แต่ตรวจเพียงว่ามี localStorage.ApplicationStore อยู่หรือไม่
    if (localStorage.getItem("ApplicationStore") === null) {
      document.documentElement.classList.add("logged-out");
    }
    
    • ถ้ามี ApplicationStore แปลว่าผู้ใช้เคยใช้ Linear บนเบราว์เซอร์นี้ และมีข้อมูล workspace อยู่ใน IndexedDB แล้ว
    • ถ้าไม่มีค่า ก็ไม่มีข้อมูลให้เรนเดอร์ ดังนั้น shell จะสลับไปเป็นเลย์เอาต์แบบ logged-out แล้วเข้าสู่โฟลว์การล็อกอินต่อ
    • session token จริงอยู่ในคุกกี้ และ bundle จะไม่ตัดสินสถานะ session ล่วงหน้า
    • หาก WebSocket handshake, sync delta, หรือ HTTP call ใดก็ตามได้รับ 401 จาก session ที่หมดอายุ ไคลเอนต์จะ redirect ไปหน้าเข้าสู่ระบบ
    • แพตเทิร์นทั้งหมดคือเชื่อถือข้อมูลในเครื่องเพื่อเรนเดอร์ได้ทันที, ใช้เซิร์ฟเวอร์เป็นแหล่งความถูกต้อง, และปรับให้ทั้งสองฝั่งสอดคล้องกันแบบอะซิงโครนัส

เอนจินการซิงก์

  • ความเร็วของ Linear เริ่มต้นจากการตัดสินใจมองเซิร์ฟเวอร์เป็น sync target ไม่ใช่ source of truth ของ UI
  • ความเร็วไม่ใช่ผลจากองค์ประกอบเดียว แต่เป็นผลลัพธ์จากสามแกนที่ทำงานประสานกัน
  • 1. มีข้อมูลอยู่แล้ว

    • ตอนแอปบูต จะไม่ดึง workspace จากเซิร์ฟเวอร์ แต่ hydrate จาก IndexedDB ไปยัง object pool ของ MobX ในหน่วยความจำ
    • ทุก query ของ UI จะชี้ไปที่ object pool ก่อน และเพราะ issue อยู่บนอุปกรณ์ของผู้ใช้อยู่แล้ว จึงไม่มีสถานะ “loading issues”
    • ระหว่างการขยายระบบ Linear ได้แบ่งข้อมูลของเอนจินการซิงก์เป็นชังก์ตามหลักการคล้ายกับ JavaScript bundle
    • ตารางที่หนักที่สุดสองตารางคือ Issue และ Comment จะไม่ถูกดึงมาทีเดียว แต่จะ lazy-hydrate เมื่อจำเป็น
    • วิธีนี้คือ data-level code splitting และทำให้ต้นทุนตอนเริ่มต้นขึ้นกับโครงสร้างของ workspace ไม่ใช่ขนาดของ workspace
    • แม้เป็น workspace ที่มี issue 10,000 รายการ ก็ยังบูตได้เร็วแทบไม่ต่างจาก workspace ที่มี issue 100 รายการ
    • เมื่อเข้าไปในโปรเจกต์ issue ก็มีอยู่แล้ว และเมื่อกรองตาม assignee ดัชนีก็ถูกสร้างไว้แล้ว
  • 2. การเปลี่ยนแปลงไม่รอเครือข่าย

    • เมื่อเปลี่ยนสถานะของ issue จะมีสามสิ่งเกิดขึ้นแทบพร้อมกัน
    • อัปเดต MobX observable เพื่อสะท้อนการเปลี่ยนแปลงใน UI
    • บันทึกการเปลี่ยนแปลงลงใน durable transaction queue ของ IndexedDB
    • เพิ่มการเปลี่ยนแปลงเข้าไปในคิวส่งไปยังเซิร์ฟเวอร์
    • ณ จุดนี้ เครือข่ายยังไม่ได้ถูกใช้งาน
    • ผู้ใช้ไม่ต้องรอเพื่อเห็นการเปลี่ยนแปลงของตัวเอง ส่วนการ retry, rollback และ reload across durability จะถูกจัดการทั้งหมดในเบื้องหลัง
    • หากเซิร์ฟเวอร์ปฏิเสธ observable จะถูกย้อนกลับและเกิด flicker สั้น ๆ แต่การเปลี่ยนแปลงที่ไม่ถูกต้องส่วนใหญ่จะถูกจับได้ก่อนสร้าง transaction
    • โฟลว์ของ Linear เริ่มจากการเปลี่ยนแปลงในเครื่อง และมองเซิร์ฟเวอร์เป็นขั้นตอนยืนยัน ไม่ใช่ขั้นตอนอนุญาต
  • 3. หนึ่งเดลตา, หนึ่งเซลล์

    • เมื่อเซิร์ฟเวอร์ยืนยันการเปลี่ยนแปลงของผู้ใช้หรือของคนอื่น JSON envelope ขนาดเล็กที่บอกว่ามีอะไรเปลี่ยนจะถูกส่งกลับมาที่ไคลเอนต์
    • ไคลเอนต์จะนำการเปลี่ยนแปลงไปใช้ด้วยการเขียนค่าเข้า MobX observable ที่เกี่ยวข้อง
    • ทุกคุณสมบัติของโมเดลใน Linear เป็น observable แยกกัน และทุกคอมโพเนนต์ที่อ่านคุณสมบัตินั้นจะถูกครอบด้วย observer()
    • MobX จึงรู้ได้อย่างแม่นยำว่าคอมโพเนนต์ใดขึ้นต่อฟิลด์ใด
    • การเปลี่ยนแปลงหนึ่งฟิลด์ของ issue หนึ่งรายการจะ re-render เฉพาะคอมโพเนนต์ที่อ่านฟิลด์นั้น และจะไม่ re-render รายการแม่หรือ sidebar ทั้งหมด
    • การอัปเดต issue 50 รายการจึงไม่ใช่การ re-render ทั้งลิสต์ แต่เป็นการ re-render 50 เซลล์
    • แม้อยู่ใน workspace ที่วุ่นวายซึ่งมี 10 คนแก้ไขพร้อมกัน ต้นทุนของการรับอัปเดตก็จะเพิ่มตามจำนวนรายการที่เปลี่ยนจริง ไม่ใช่จำนวนรายการทั้งหมดบนหน้าจอ
    โฆษณา
  • เหตุผลที่ทั้งสามอย่างต้องทำงานร่วมกัน

    • ถ้ามีแค่ฐานข้อมูลในเครื่องแต่ไม่มี optimistic write ตอนบันทึกก็ยังต้องเจอสปินเนอร์
    • ถ้ามีแค่ optimistic write แต่ไม่มี observable ที่ละเอียดพอ ก็จะยังหน่วงทุกครั้งที่มีอัปเดต
    • ถ้ามีแค่ observable ที่ละเอียดพอ แต่ไม่มีฐานข้อมูลในเครื่อง ก็ยังต้องรอตอนโหลดครั้งแรก
    • ความเร็วของ Linear ไม่ใช่คุณสมบัติของเลเยอร์ใดเลเยอร์หนึ่ง แต่เป็นคุณสมบัติของทั้งระบบ
    • bundler และ loader shell ทำให้ first paint รู้สึกเร็ว ส่วนเอนจินการซิงก์ทำให้ยังรู้สึกเร็วต่อเนื่องหลังเริ่มใช้งาน

การออกแบบเพื่อความเร็ว

  • ความเร็วเป็นทั้งปัญหาด้านวิศวกรรมและปัญหาด้านการออกแบบ
  • ถ้าเส้นทางไปยังแอ็กชันที่เร็วที่สุดยังต้องใช้เมาส์ เมนูสามชั้น และการคลิก ผู้ใช้ก็ต้องจ่ายต้นทุนของขั้นตอนเหล่านั้นโดยไม่เกี่ยวกับความเร็วของเอนจินภายใน
  • อีกแกนหนึ่งของความเร็วใน Linear คือการผสานคีย์บอร์ดให้เป็นเครื่องมือหลักสำหรับการนำทางและการทำงานให้เสร็จ
  • งานทั่วไปทุกอย่างมี shortcut, command palette เปิดได้ด้วยการกดปุ่มครั้งเดียว และ right-click menu ก็สร้างขึ้นแบบคัสตอม
  • ทุกแอ็กชันมี shortcut

    • ตัวอักษรเดี่ยวใช้แก้ไข issue ที่โฟกัสอยู่ ชุดตัวอักษรสองตัวใช้สำหรับการนำทาง และ modifier ใช้สำหรับการทำงานแบบ global
    • ตั้งแต่ช่วงแรกของ Linear นั้น shortcut เป็นองค์ประกอบพื้นฐาน และเอนจินการซิงก์ก็ส่วนหนึ่งถูกออกแบบมาเพื่อให้ทุกแอ็กชันสามารถทำได้ทุกเมื่อ
    • shortcut ปรากฏอยู่ทั่วทั้ง UI และ shortcut ที่ใช้บ่อยที่สุดเป็นตัวอักษรเดี่ยว
    • เพื่อไม่กีดกันผู้ใช้มือใหม่ ทุกแอ็กชันจึงยังทำได้ด้วยเมาส์
  • Command palette อยู่ห่างออกไปแค่การกดปุ่มครั้งเดียวเสมอ

    • ⌘ k จะเปิด command palette ที่สามารถค้นหาแอ็กชันแทบทั้งหมดใน Linear ได้
    • สิ่งที่ค้นหาได้มีทั้ง issue, โปรเจกต์, label, การเปลี่ยนสถานะ, การนำทาง, การสร้าง issue, การตั้งค่า, การสลับธีม และอื่น ๆ
    • command palette ค้นหาจาก object pool ของ MobX ในเครื่อง ไม่ใช่จากเซิร์ฟเวอร์ จึงเร็วมาก
    • แอปทั้งแอปเข้าถึงได้จาก pane เดียว โดยทั้งการนำทาง การสร้าง issue และการเปลี่ยนสถานะทำได้ผ่านการค้นหา
    • command palette จะปรับตามบริบทการทำงานปัจจุบัน และทำหน้าที่สอนแอ็กชันหลักกับ shortcut ของแต่ละ view
    • แอปที่เร็วต้องมีทั้งวิศวกรรมที่ยอดเยี่ยมและการออกแบบที่ยอดเยี่ยม โดยความเร็วเชิงวิศวกรรมทำให้แต่ละปฏิสัมพันธ์เกิดขึ้นได้เร็ว ส่วนความเร็วเชิงการออกแบบทำให้เส้นทางไปถึงปฏิสัมพันธ์สั้นลง
    • ในเครื่องมือที่ใช้ทั้งวัน ความต่างระหว่าง shortcut กับเส้นทางใช้เมาส์ 2 วินาทีจะสะสมในทุกแอ็กชัน

แอนิเมชัน

  • แอนิเมชันที่แย่อาจทำให้มิลลิวินาทีที่อุตส่าห์ลดลงจากการปรับแต่ง initial load, การอัปเดต และการ query ฐานข้อมูล ถูกใช้ทิ้งไปอีกครั้งในขั้นตอนสุดท้าย
  • องค์ประกอบอย่าง height animation 500ms สามารถทำลายความพยายามที่จะไม่ให้ผู้ใช้ต้องรอได้
  • มีเพียงไม่กี่ property ที่ควรนำมาแอนิเมต

    • การเปลี่ยนแปลง property ในเบราว์เซอร์มีต้นทุน 3 ระดับ ขึ้นอยู่กับตำแหน่งใน rendering pipeline
    • composited property อย่าง transform และ opacity จะส่งงานไปให้ GPU และทำงานโดยแยกจาก main thread
    • paint-triggering property อย่าง color, background-color, border-color, fill จะข้าม layout แต่ทำให้เกิดการวาดพิกเซลใหม่
    • layout-triggering property อย่าง width, height, top, left, margin, padding จะทำให้ต้องคำนวณตำแหน่งของทุกองค์ประกอบถัดไปใหม่ และไม่ควรถูกใช้เป็นเป้าหมายของแอนิเมชัน
    โฆษณา
    /* วิธีของ Linear */
    .row:hover {
      background-color: var(--color-bg-hover);
      transition: background-color 0.12s;
    }
    .icon-arrow {
      transform: translateX(0);
      transition: transform 0.15s;
    }
    
    • ถ้าแอนิเมต margin-left layout ของทุก row ใต้ row ที่ hover จะถูกคำนวณใหม่ทุกเฟรมตลอดช่วง transition 200ms
    • ในรายการ issue ที่ยาว ความแตกต่างนี้คือสิ่งที่แบ่งระหว่างภาพที่ลื่นไหลกับอาการ jank
    • property สำหรับแอนิเมชันของ Linear ส่วนใหญ่เป็น composited property อย่าง transform และ opacity และบางครั้งใช้ background-color กับ border-color
  • ต้องรู้ว่าเมื่อไรควรยับยั้ง

    • ในเครื่องมือที่ใช้งานทุกวัน แอนิเมชันที่ดูดีบนเว็บไซต์การตลาดอาจรบกวนการทำงานได้
    • แม้แต่ hover delay เล็กน้อยที่วางผิดจุดก็อาจเป็นสิ่งที่ผู้ใช้สังเกตเห็นได้
    • แอนิเมชันจำนวนมากของ Linear ทำงานได้อย่างมีประสิทธิภาพเพราะอ้างอิง origin
    • status popover จะ scale out จาก status pill และ agent panel จะ slide in จาก toggle
    • motion แบบนี้ไม่ได้เป็นแค่ fade เพื่อความสวยงาม แต่ทำหน้าที่เชิงพื้นที่เพื่อบอกว่าองค์ประกอบใหม่มาจากไหน
  • รักษา duration ให้สั้นและตอบสนองทันที

    --speed-highlightFadeIn: 0s;
    --speed-highlightFadeOut: .15s;
    --speed-quickTransition: .1s;
    --speed-regularTransition: .25s;
    --speed-slowTransition: .35s;
    
    • design system จำนวนมากตั้งค่า duration เริ่มต้นไว้นานเกินความจำเป็น
    • standard duration ของ Material คือ 200ms และ iOS spring อยู่ใกล้ 350ms
    • ค่าเริ่มต้นของ Linear อยู่ฝั่งที่สั้นกว่าธรรมเนียมปฏิบัติของอุตสาหกรรม
    • Linear ใช้ timing แบบไม่สมมาตรระหว่าง enter และ exit
    • hover highlight, popover และ agent panel จะแสดงขึ้นทันทีเมื่อถูกเรียกใช้ และ fade out เป็นเวลา 150ms ตอนปิด
    • หน้าต่าง agent จะแสดงขึ้นทันทีและ fade out คล้ายกับ macOS

วิธีที่ Linear ทำให้เร็ว

  • ประสิทธิภาพของ Linear ไม่ได้มาจากเคล็ดลับเดียวหรือเทคโนโลยีอย่างใดอย่างหนึ่ง แต่เป็นผลสะสมของการตัดสินใจที่ถูกต้องนับร้อยครั้ง
  • แนวทางจำนวนมากนั้นเรียบง่าย และเป็นผลจากการกำหนดสถาปัตยกรรมที่เหมาะกับผู้ใช้ตั้งแต่ต้นแล้วรักษาไว้ โดยไม่ต้องพึ่ง Next, TanStack หรือ framework หรูหรา
  • เซิร์ฟเวอร์ไม่ได้ทำหน้าที่เป็น source of truth ของ UI แต่เป็นเป้าหมายสำหรับการ sync
  • ฐานข้อมูลอยู่ภายในเบราว์เซอร์ และการเปลี่ยนแปลงจะถูกนำไปใช้ในเครื่องก่อน แล้วค่อยปรับให้ตรงกันในเบื้องหลัง
  • การโหลดครั้งแรกจะส่งโค้ดให้น้อยลงแต่แบ่งเป็นชิ้นมากขึ้น และ service worker จะ precache ส่วนที่เหลือขณะที่ผู้ใช้อยู่ในหน้าเข้าสู่ระบบ
  • การยืนยันตัวตนตั้งต้นจาก local state โดยสมมติว่าเส้นทางปกติใช้งานได้ แล้วค่อยตรวจสอบภายหลัง
  • sync engine จะ hydrate จาก IndexedDB ไปเป็น MobX observable ระดับ per-property ดังนั้นการอัปเดต issue 50 รายการจึงกลายเป็นการ re-render 50 เซลล์ แทนที่จะ re-render ทั้งรายการ
  • โมเดลการป้อนข้อมูลเน้น keyboard-first และงานทั่วไปทั้งหมดมี shortcut กับ global command palette
  • แอนิเมชันยึดอยู่กับ property ที่เป็นมิตรกับ GPU และไม่แอนิเมต property ที่ trigger layout
  • ส่วนที่ยากไม่ใช่แค่ตัวการ implement แต่คือการรักษาความใส่ใจในรายละเอียดด้านคุณภาพตลอดหลายปี ขณะที่ codebase เติบโต ขยายตัว และเจอกับข้อจำกัดใหม่ๆ

1 ความคิดเห็น

 
GN⁺ 4 시간 전
ความคิดเห็นจาก Hacker News
  • ถ้าอยากใส่ประสบการณ์แบบนี้ลงในแอปพลิเคชัน ลองดู Zero(https://zero.rocicorp.dev/) ได้
    เดโมสด: https://gigabugs.rocicorp.dev/
    มีรายการทางเลือกอยู่ที่นี่ด้วย: https://zero.rocicorp.dev/docs/when-to-use#alternatives
    ถ้าสงสัยว่าภายในทำงานอย่างไร เอกสารออกแบบของ Replicache ก็น่าอ่าน: https://doc.replicache.dev/concepts/how-it-works
    Replicache คือรุ่นก่อนหน้าของ Zero และโปรโตคอลแกนหลักก็ยังทำงานในลักษณะเดียวกัน

    • Zero นี่แนะนำได้จริง ๆ abstraction ยอดเยี่ยมและเป็นซอฟต์แวร์ที่ทำมาอย่างพิถีพิถัน
      นอกจากข้อได้เปรียบด้านประสิทธิภาพที่ชัดเจนจากการซิงก์ข้อมูลมาไว้ที่ไคลเอนต์แล้ว ยังน่าทึ่งด้วยว่าโค้ด React เรียบง่ายขึ้นแค่ไหน เมื่อมี sync engine สถานะฝั่งไคลเอนต์ส่วนใหญ่ก็หายไป และทำให้คิดกับโค้ดคอมโพเนนต์ส่วนใหญ่แบบ synchronous ได้
    • ใช้ Zero มาสักพักแล้ว ตอนแรกตั้งใจจะสร้าง sync engine แบบ Linear ขึ้นมาใช้ภายในเอง แต่ไปเจอ Zero ก่อน
      ถ้าไม่ตั้งทีมเฉพาะขึ้นมาเอง นี่น่าจะเป็นตัวเลือกที่ใกล้เคียงที่สุดแล้ว
    • ในฐานะผู้ใช้ Zero นี่เป็นเครื่องมือที่เหมาะมากเมื่อคุณต้องการ ประสบการณ์ผู้ใช้ ที่ UI อัปเดตทันทีภายในไม่กี่มิลลิวินาทีเมื่อฐานข้อมูลเปลี่ยน
  • ได้ยินมาตลอดว่า Linear เร็ว แต่พอใช้ทุกวันจริง ๆ ความตื่นเต้นก็หายไป การค้นหาค่อนข้างช้า และ UI ก็มักจะหน่วง ๆ แม้จะดูดี แต่ “Pulse” ก็เหมือนกระแสข้อมูลรบกวนมหาศาลแม้ในสเกลเล็ก
    หาสิ่งที่ต้องการได้ยากจนสุดท้ายต้องใส่ดาวไว้ทั้งหมด สำหรับประสบการณ์ติดตามโปรเจ็กต์แล้ว Trello ยุคแรก ๆ ยังดีที่สุดแบบทิ้งห่าง

    • ฉันเกลียดมากเวลาที่พยายามเปิดตั๋วระหว่างประชุมหรือ huddle แล้วต้องนั่งจ้องอย่างกระอักกระอ่วนรออะไรสักอย่างที่ไม่รู้ว่าเป็นการโหลดหรือแคช ซึ่งใช้เวลานานเกินเหตุ
  • ปีที่แล้วมีคนหนึ่ง reverse engineer sync engine ของ Linear แล้วเอาขึ้น GitHub พร้อมคำอธิบายเจ๋ง ๆ
    https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...

    • ตอนนั้นก็มีการคุยกันด้วย: Reverse engineering of Linear's sync engine - https://news.ycombinator.com/item?id=44123131 - พฤษภาคม 2025, 33 ความคิดเห็น
  • เว็บแอป local-first sync แบบนี้น่าสนใจมากและอาจมีประโยชน์มาก แต่ผมคิดว่าสมมติฐานตั้งต้นค่อนข้างผิด
    เป็นสมมติฐานประมาณว่า “ใช้เวลาไม่กี่มิลลิวินาทีก็อัปเดต issue ใน Linear ได้แล้ว แต่แอป CRUD แบบดั้งเดิมใช้เวลาราว 300ms กับงานเดียวกัน” และ “ข้อมูลทุกชิ้นที่วิ่งไปมาระหว่างไคลเอนต์กับเซิร์ฟเวอร์ต้องจ่ายต้นทุนเป็นหลายร้อยมิลลิวินาที”
    แม้จะแก้ปัญหาเวลาไป-กลับระหว่าง HTTP client กับเซิร์ฟเวอร์ที่ยืดออกเพราะความเร็วแสงไม่ได้ แต่ก็ทำให้แบ็กเอนด์อยู่ใกล้ผู้ใช้และทำงานได้เร็วได้
    ตัวอย่างเช่น การรันแบ็กเอนด์ของเว็บแอปให้มีเวลาไป-กลับราว 10ms สำหรับผู้ใช้ส่วนใหญ่ และให้แบ็กเอนด์เรนเดอร์คำตอบได้ภายในราว 10ms ก็เป็นเรื่องที่ทำได้สบาย ๆ นั่นคือแอป CRUD แบบดั้งเดิมก็ทำงานเดียวกันได้ใน ประมาณ 30ms ไม่ใช่ 300ms

    • ตอนเห็นว่าพูดถึง 300ms ว่าเร็ว ผมก็รู้สึกแปลก ๆ เพราะเท่าที่จำได้ TTFB 30ms เป็นเป้าหมายมานานแล้ว
      Linear อาจต้องใช้เวลานานกว่าที่แบ็กเอนด์ด้วยเหตุผลที่สมเหตุสมผล จนต้องพึ่งความช่วยเหลือจากฟรอนต์เอนด์ แต่จะเหมารวมแบบนั้นไม่ได้ JavaScript ทุกชิ้นก็มีต้นทุนของมันเอง
    • คำพูดที่ว่า “สามารถรันแบ็กเอนด์ของเว็บแอปให้อยู่ภายในเวลาไป-กลับราว 10ms สำหรับผู้ใช้ส่วนใหญ่ได้” ฟังดูแปลก ใน AWS region ที่เร็วกว่า us-east-1 น้อยกว่า 10ms แทบจะมีแค่ us-east-2 เท่านั้น: https://www.cloudping.co/
      us-west-1 อยู่ที่ 60ms, eu-centra-1 อยู่ที่ 100ms, เอเชียห่างออกไป 200ms และนี่ก็ยังเป็นทราฟฟิกระหว่างดาต้าเซ็นเตอร์ ส่วน latency บนอินเทอร์เน็ตสาธารณะจริงไปจนถึงอินเทอร์เน็ตตามบ้านยิ่งแย่กว่าอีก
      ฐานข้อมูลต้องอยู่ใน region เดียวอย่างชัดเจน ไม่ว่าจะวางไว้ที่ไหน ผู้ใช้ส่วนใหญ่ของโลกก็จะอยู่ห่างจากที่นั่นเกิน 100ms
      เหตุผลที่ไม่สำคัญว่า endpoint อยู่ที่ไหน ก็เพราะ endpoint ต้องคุยกับฐานข้อมูลเพื่ออ่านและเขียนข้อมูลอยู่ดี ทันทีที่พยายามทำสำเนาข้อมูลไปไว้ใกล้ผู้ใช้ คุณก็ลงเอยด้วยการมี ฐานข้อมูล local-first sync อยู่ดี
      ไม่ว่าจะทำเองหรือใช้ของสำเร็จรูป ฐานข้อมูลแบบทำซ้ำนี้ก็มีปัญหาแบบเดียวกับการซิงก์ฝั่งไคลเอนต์ทั้งหมด และก็ยังเหลือ latency เครือข่ายอยู่มากเหมือนเดิม หนีฟิสิกส์ไม่พ้น ดังนั้นคุณมีทางเลือกแค่ให้ผู้ใช้ส่วนใหญ่ได้ commit ที่ 0.25 วินาที หรือไม่ก็เลือก eventual consistency หรือก็คือการซิงก์
    • จะทำได้ก็ต่อเมื่อผู้ใช้อยู่ค่อนข้างใกล้กัน หรือแบบที่น่าเศร้าแต่พบได้บ่อย คือแค่ให้ผู้ใช้ในอเมริกาเร็วก็พอ ที่เหลือไม่สนใจ
      แน่นอนว่าคุณอาจวาง “แบ็กเอนด์ตรงกลาง” ไว้บนเครือข่าย CDN edge ทั่วโลกได้ แต่ถึงจุดนั้นคุณก็ต้องจ่ายต้นทุนความซับซ้อนแบบเดียวกับแนวทางนี้ที่เอา “แบ็กเอนด์ตรงกลาง” ไปไว้ในไคลเอนต์
    • คุณสามารถวาง แบ็กเอนด์ตรงกลาง ไว้ในไคลเอนต์ เขียนการเปลี่ยนแปลงที่ยังไม่ถูกประมวลผลลง local store แล้วให้ background worker ส่งต่อไปยังแบ็กเอนด์พร้อมกลไก retry ที่จำเป็น
      ในกรณีแย่ที่สุด background worker ก็แค่ส่งข้อความว่าอัปเดตล้มเหลว แล้ว UI thread ค่อยรับไปแสดง เส้นทางความสำเร็จก็ยังคงเร็วระดับสายฟ้าเหมือนเดิม
    • แล้วมันยังทำได้อยู่ไหมเมื่อมีฐานข้อมูลที่ edge backend พวกนี้ทั้งหมดต้องใช้ร่วมกัน?
  • การสร้าง ฐานข้อมูลแบบ eventual consistency นั้นยาก และแม้อาจเหมาะกับกรณีใช้งานของ Linear แต่การไม่รู้ว่าอัปเดตของฉันไปถึงเซิร์ฟเวอร์หรือก็คือทีมแล้วหรือยังเป็นปัญหา
    ในโปรเจ็กต์อื่น ๆ ที่เคยร่วมทำมาก่อน ความล่าช้าในการซิงก์ก่อปัญหานับไม่ถ้วน เลยเลือกใช้วิธีแบบ synchronous มาโดยตลอด ฟีเจอร์หวือหวาจะหยิบมาใช้ก็ต่อเมื่อจำเป็นจริง ๆ เท่านั้น ไม่อย่างนั้นขอปรับแต่งเซิร์ฟเวอร์ให้เร็วสุด ๆ แล้วให้ผู้ใช้รับภาระเรื่องความหน่วงเครือข่ายจะดีกว่า

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

    • ปัญหาที่เจอบ่อยใน Linear คือการเขียนข้อมูลซ้ำ ๆ บางครั้งทับกันเอง พิมพ์ไป หยุดคิดสักครู่ แล้วพิมพ์ต่อ จากนั้น Linear ก็ย้อนข้อมูลกลับไปอยู่ในสภาพหลังพิมพ์ครั้งแรก
      มันแย่ถึงขั้นที่ใน Linear ฉันสร้าง issue พร้อมคำอธิบายแค่ประโยคเดียว แล้วไปกรอกรายละเอียดต่อใน GitHub นั่นคือสิ่งที่ Linear ทำได้ดีและเร็วจริง ๆ
    • Linear กลายเป็นสิ่งที่มันเคยพยายามจะกำจัด นั่นคือ เครื่องมือที่ซับซ้อน
      น่าเสียดาย แต่ถ้าบริษัทจะอยู่รอดและขยับขึ้นไปสู่ตลาดระดับบน ก็แทบไม่มีทางอื่นจริง ๆ
    • ฉันไม่ได้ใช้ Chrome เลยไม่แน่ใจว่าเป็นเพราะเรื่องนั้นไหม แต่หน้า Linear มักค้างหรือใช้เวลาหลายวินาทีในการโหลดครั้งแรก
      ฉันคงไม่ใช้คำว่า “เร็ว” หรอก ในเมื่อแค่โหลดครั้งแรกก็ใช้เวลา 30 วินาทีแล้ว การลดเวลาอัปเดต issue จาก 300ms เหลือ “ไม่กี่” ms ก็ไม่ได้สำคัญนัก
    • ที่ทำงานเก่าเคยเปลี่ยนจาก Linear ไป Jira เพราะ UX แปลก ๆ มีไอคอนมากมายที่เดาความหมายยาก ค้นหาไม่เจอง่าย และแทบไม่มีการบอกเลยว่าทำไมเนื้อหาบนหน้าถึงเปลี่ยนไป
    • มันแย่มากจริง ๆ ฉันต้องถามเพื่อนร่วมงานว่าจะเพิ่ม วันครบกำหนด ให้รายการอย่างไร เพราะมันถูกซ่อนอยู่ในแผงนำทาง
      ดีกว่า Jira ก็จริง แต่ก็เพราะมาตรฐานนั้นต่ำมาก
  • เจ๋งดี ฉันอาจใส่อะไรคล้าย ๆ กันลงไปในเกมเบราว์เซอร์และเอนจินที่กำลังพัฒนาอยู่ เพื่อหลังจากโหลดครั้งแรกแล้วจะได้ตัด สถานะการโหลด ออกไปทั้งหมด ของฉันเป็นโครงสร้างแอสเซ็ตแบบ static ฝั่งไคลเอนต์ล้วน ไม่มีเซิร์ฟเวอร์
    ฉันหมกมุ่นกับประสิทธิภาพของเกมนี้มานาน ก่อนสุดสัปดาห์ที่ผ่านมา ฉันยังลำบากกับการรักษา 120fps บน M1 MacBook Pro ขณะจำลองผู้เล่นพร้อมกัน 128 คน โดยประมวลผลการหาเส้นทาง ลอจิกเชิงกลยุทธ์หนัก ๆ และการเรนเดอร์ทั้งหมดภายใน viewport และก็ยังมีเฟรมดรอปเป็นครั้งคราว โดยใช้เวลาเฟรมราว 4ms
    ช่วงสุดสัปดาห์ฉันทุ่มทำงานด้านประสิทธิภาพอย่างหนัก และตอนนี้สามารถจำลองผู้เล่นพร้อมกันได้ 2048 คน ด้วยเวลาเฟรมต่ำกว่า 1 มิลลิวินาที ตัวเลขนี้รวมทั้งการเรนเดอร์ ลอจิกทั้งหมด และการสร้างเชิงกระบวนการแล้ว
    อีกทั้งเมื่อทำ CPU throttling 11.2 เท่าเพื่อจำลองอุปกรณ์พกพาสเปกต่ำ ก็ยังได้ 60fps ที่เสถียรด้วยเวลาเฟรมราว 5ms ที่จำนวนผู้เล่นพร้อมกัน 256~512 คน ตอนนี้คอขวดหลักคือปัญหาลอจิกเล็กน้อยและเวลาเริ่มต้น/บูตที่ควรปรับปรุงบนอุปกรณ์สเปกต่ำ ซึ่งดูเหมือนจะมีอะไรให้เรียนรู้จาก Linear

  • ฉันรู้สึกว่า Linear จริง ๆ แล้วค่อนข้างช้า เคยมีช่วงหนึ่งที่เปิดแท็บทิ้งไว้นาน ๆ แล้วมันกิน CPU 100%

    • บน Firefox ก็ดูเหมือนจะใช้หน่วยความจำเยอะด้วย เปิดแท็บ Linear พร้อมกันหลายแท็บแทบไม่ได้เลย
  • ก็น่าสนใจนะ แต่พูดตามตรง ฉันไม่เคยคิดว่า Linear “เร็ว” เลย มันมีความหน่วงแบบเดียวกับเว็บแอปส่วนใหญ่ แต่ถ้าเทียบกับ JIRA ก็เร็วราวกับแสงแน่นอน
    ตัว Linear เองยอดเยี่ยม และหลังจากผ่าน การทรมานจาก JIRA มาก็ถือว่าสดชื่นจริง ๆ แต่ถ้าจะพูดถึงการทำ route แบบ optimistic และคำว่า “เร็ว” ฉันว่าควรเริ่มจาก Gmail ก่อนหรือเปล่า

  • คำตอบของความเร็วคือ การโหลดล่วงหน้า โดยพื้นฐานคือดาวน์โหลดฐานข้อมูลฝั่งไคลเอนต์มาตั้งแต่ตอนเริ่มต้น และมีแนวทางสำหรับการทำ cache invalidation
    ฉันสร้าง starfx ขึ้นมาเพื่อจัดการด้าน data synchronization ของพาราไดม์นี้: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...