யாரும் பேசாத DOM Bottleneck
பத்தாயிரம் லாக் பதிவுகளை (log entries) இழுக்கும் ஒரு சப்போர்ட் டேஷ்போர்டை கற்பனை செய்து பாருங்கள். அல்லது ஒரு CRM ஒவ்வொரு தொடர்பையும் (contact) ஒரே ஸ்க்ரோலபிள் அட்டவணையில் காட்ட முயற்சிப்பதாகக் கொள்வோம். React-இல், இதை உருவாக்குவதற்கான குறியீடு பார்ப்பதற்குப் பாதுகாப்பானது போலவே இருக்கும். நீங்கள் ஒரு அரே (array) மீது மேப் (map) செய்து, சில JSX-களைத் திருப்பித் தந்துவிட்டு, பிரேம்வொர்க்கை அதன் வேலையைச் செய்ய விடுவீர்கள். நூறு வரிசைகள் இருக்கும்போது டெவலப்மென்ட்டில் எல்லாம் சரியாகவே இருக்கும். ஆனால் ப்ரொடக்ஷன் டேட்டா (production data) வரும்போது, பக்கம் மிகவும் மந்தமாகிவிடும்.
பிரவுசர் சோம்பேறியாகச் செயல்படவில்லை. நீங்கள் கேட்டதைத்தான் அது சரியாகச் செய்கிறது, அதுதான் பிரச்சனை. ஒவ்வொரு வரிசையும் ஒரு DOM நோடாக மாறுகிறது. ஒவ்வொரு நோடிற்கும் ஸ்டைல் செய்யப்படுகிறது, லேஅவுட் செய்யப்படுகிறது, பெயிண்ட் செய்யப்படுகிறது மற்றும் மெமரியில் கண்காணிக்கப்படுகிறது. நீங்கள் ஸ்க்ரோல் செய்யும்போது, பிரவுசர் நீங்கள் பார்க்கும் பகுதியை மட்டும் அல்லாமல், முழு மரத்தையும் (entire tree) மீண்டும் கணக்கிடுகிறது. ஈவென்ட் லிசனர்ஸ்கள் (Event listeners) குவிகின்றன. மெமரி உயர்கிறது. இறுதியில் மெயின் த்ரெட் (main thread) திணறிப்போவதால், இன்டர்ஃபேஸ் கிளிக்ஸ், கீஸ்ட்ரோக்ஸ் அல்லது ஸ்க்ரோல் செய்வதற்கே பதிலளிக்காமல் நின்றுவிடும். தொழில்நுட்ப ரீதியாக அப்ளிகேஷன் கிராஷ் ஆகவில்லை என்றாலும், அதை முன்னால் அமர்ந்து பயன்படுத்தும் பயனருக்கு அனுபவம் முற்றிலும் சிதைந்து போனதாகவே இருக்கும்.
பிரவுசர் ஒவ்வொரு தனித்தனி உறுப்பையும் (element) ஒரே நேரத்தில் ஆக்டிவ் மெமரியில் வைத்திருக்க முயற்சிப்பதால் இது நிகழ்கிறது. உங்கள் UI-ன் மெய்நிகர் விளக்கங்களை (virtual descriptions) உருவாக்குவதில் React திறமையாக இருக்கலாம், ஆனால் அந்த விளக்கங்கள் ஆவணத்தில் உண்மையான நோட்களாக மாறும்போது, அவை கைப்பட எழுதப்பட்ட HTML-க்கு இணையான செலவையே ஏற்படுத்தும். பிரேம்வொர்க்கிற்குள்ளேயே இதிலிருந்து தப்பிக்க வழி இல்லை. நீங்கள் பட்டியலை DOM-க்கு வழங்கும் முறையில் ஒரு கட்டமைப்பு மாற்றத்தை ஏற்படுத்த வேண்டும்.
Virtualization என்பது உண்மையில் என்ன?
Virtualization என்பது அந்த கட்டமைப்பு மாற்றம் தான். முழு அரேவையும் ரெண்டர் செய்ய React-இடம் கேட்பதற்குப் பதிலாக, வியூபோர்ட்டிற்குள் (viewport) பொருந்தக்கூடிய ஐட்டம்களையும், அதற்கு மேலே மற்றும் கீழே உள்ள ஒரு சிறிய பஃபரையும் (buffer) மட்டும் நீங்கள் ரெண்டர் செய்கிறீர்கள். பயனர் ஸ்க்ரோல் செய்யும்போது, பார்வையில் இருந்து வெளியேறும் நோட்களை அப்ளிகேஷன் நீக்கிவிட்டு, எதிர் திசையில் இருந்து உள்ளே வரும் புதிய நோட்களை உருவாக்குகிறது. பயனருக்கு இது இன்னும் ஒரு தொடர்ச்சியான பட்டியலாகவே தோன்றும், ஏனெனில் மொத்த ஸ்க்ரோலபிள் உயரம் (scrollable height) ஒரு பெரிய கண்டெய்னர் அல்லது கவனமாக கணக்கிடப்பட்ட ஸ்பேஸர் (spacer) மூலம் பராமரிக்கப்படுகிறது. கண்ணுக்குத் தெரியும் ஐட்டம்கள் என்பது தரவுத்தொகுப்பின் (dataset) குறுக்கே நகரும் ஒரு ஜன்னல் போன்றது.
ஒரு ப்ரொஜெக்டர் கேட் வழியாகச் செல்லும் ஒரு ஃபிலிம்ஸ்ட்ரிப் (filmstrip) போல இதைக் கருதுங்கள். பார்வையாளர்கள் மென்மையான இயக்கத்தைப் பார்க்கிறார்கள், ஆனால் இயந்திரம் தற்போது நிலையில் உள்ள பிரேமை மட்டுமே ஒளிரச் செய்கிறது. மீதமுள்ள ரீல் (reel) ஃபீட் மற்றும் டேக்-அப் ஸ்பூல்களில் (take-up spools) உள்ளது, ஒளிப் பாதையில் இல்லை. Virtualized பட்டியல்களும் அதே வழியில் செயல்படுகின்றன. தரவுத்தொகுப்பு என்பது ரீல். வியூபோர்ட் என்பது கேட்.
இது பாரம்பரியமான லேஸி லோடிங் (lazy loading) கிடையாது. லேஸி லோடிங் என்பது பயனர் அதற்கு அருகில் ஸ்க்ரோல் செய்யும் வரை தரவை எடுப்பதைத் தள்ளிப்போடுகிறது. Virtualization என்பது உங்களிடம் ஏற்கனவே தரவு இருப்பதாகவே கருதுகிறது, ஆனால் எந்தத் துண்டுகள் உண்மையான DOM உறுப்புகளாக மாற்றப்பட வேண்டும் என்பதில் நீங்கள் கவனமாக இருக்கிறீர்கள். இந்த இரண்டு நுட்பங்களும் இணைந்து செயல்படலாம், ஆனால் அவை வெவ்வேறு பிரச்சனைகளைத் தீர்க்கின்றன.
இந்த வித்தியாசம் ஏன் உடனடியாக உணரப்படுகிறது?
இதன் நன்மைகள் நான்கு இடங்களில் தெரியவருகின்றன, இவை அனைத்தும் ஒரே அடிப்படையிலான நிம்மதியுடன் தொடர்புடையவை: பயனர் பார்க்க முடியாத ஒன்றிற்காக நீங்கள் செலவு செய்வதை நிறுத்துகிறீர்கள்.
வேகமான ஆரம்பக்கட்ட லோட் நேரங்கள். பிரவுசர் பக்கத்தைத் திறக்கும்போது, பதினைந்து ஆயிரம் வரிசைகளுக்குப் பதிலாக பதினைந்து வரிசைகளை மட்டும் பெயிண்ட் செய்கிறது. முதல் பயனுள்ள பெயிண்ட் (first meaningful paint) விரைவாக வருகிறது. ஜாவாஸ்கிரிப்ட் இன்ஜின் நோட்களை உருவாக்குவதற்கும் அவற்றை ஆவணத்துடன் இணைப்பதற்கும் குறைவான நேரத்தைச் செலவிடுவதால், time-to-interactive குறைகிறது.
குறைந்த நினைவகப் பயன்பாடு. ஒரு DOM நோட் என்பது ஒரு விலை உயர்ந்த ஆப்ஜெக்ட் (object). ஒவ்வொன்றும் ஸ்டைல் விதிகள், லேஅவுட் அளவீடுகள் மற்றும் ஈவென்ட் பைண்டிங்க்களுக்கான குறிப்புகளைக் கொண்டுள்ளது. ஆக்டிவ் நோட் எண்ணிக்கையை சில டஜன் வரை குறைத்துவிட்டால், மெமரி பயன்பாடு பெருமளவு குறையும். குறைந்த திறன் கொண்ட சாதனங்கள் அல்லது நீண்ட நேரப் பயன்பாட்டின் போது, இதுவே டேப் (tab) இயங்குதளத்தால் மூடப்படுவதைத் தடுக்கலாம்.
மென்மையான ஸ்க்ரோலிங் செயல்திறன். மரத்தில் குறைவான நோட்கள் இருப்பதால், ஸ்க்ரோல் நிகழ்வுகளின் போது பிரவுசர் லேஅவுட் மற்றும் பெயிண்ட் நிலைகளில் குறைவான நேரத்தைச் செலவிடுகிறது. மறைக்கப்பட்ட உள்ளடக்கத்தின் வடிவியலை (geometry) தொடர்ந்து கணக்கிடாமல், காம்போசிட்டர் த்ரெட் (compositor thread) இயக்கத்தைக் கையாள முடியும். இதன் விளைவாக மானிட்டரின் ரிஃப்ரெஷ் ரேட்டிற்கு (refresh rate) நெருக்கமான ஸ்க்ரோலிங் கிடைக்கிறது.
நிலையான பிரேம் ரேட்கள். மெயின் த்ரெட் இனி லேஅவுட் வேலைகளில் மூழ்கிப் போவதில்லை என்பதால், மற்ற செயல்பாடுகளுக்குத் தேவையான இடம் கிடைக்கிறது. அனிமேஷன்கள் சீராக இருக்கும். நெட்வொர்க் பதில்களைச் செயலாக்க முடியும். ரெண்டர் பாதை இனி ஒரு தடையாக இல்லாததால், புதிய தரவு வரும்போது UI உறையாது.
சரியான முறையில் செயல்படுத்துதல் (Implementation)
React சூழலில், react-window மற்றும் அதிக வசதிகள் கொண்ட react-virtualized போன்ற லைப்ரரிகள் இந்த முறையைச் செயல்படுத்தத் தேவையான கட்டமைப்பை வழங்குகின்றன. இதன் அடிப்படை யோசனை நிலையானது: நீங்கள் ஒரு ஐட்டம் ரெண்டரரை (item renderer) வரையறுக்கிறீர்கள், மொத்த ஐட்டம் எண்ணிக்கையை வழங்குகிறீர்கள், மேலும் லைப்ரரி windowing கணிதத்தை நிர்வகிக்கிறது. ஆனால் சில நுணுக்கங்கள் மக்களைக் குழப்பமடையச் செய்கின்றன.
முதலாவதாக, container-க்கு ஒரு வரையறுக்கப்பட்ட உயரம் தேவை. ஒரு list, அதன் உள்ளே இருக்கும் உறுப்புகளுக்கு ஏற்ப விரிவடையும் parent-க்குள் இருந்தால், viewport எல்லை இல்லாததால் எந்த உறுப்புகள் திரையில் தெரிகின்றன என்பதைக் கணக்கிட virtualization-ஆல் முடியாது. நீங்கள் list-ஐ ஒரு நிலையான உயரம் கொண்ட container அல்லது தெரிந்த கட்டுப்பாடுகளைக் கொண்ட flex container-க்குள் வைத்திருக்க வேண்டும்.
இரண்டாவதாக, உறுப்புகளின் அளவு (item sizing) மிகவும் முக்கியமானது. நிலையான உயரமுள்ள வரிசைகள் (Fixed-height rows) மிகவும் எளிமையானவை. Library, வரிசையின் உயரத்தை அதன் index-ஆல் பெருக்கி, ஒவ்வொரு உறுப்பையும் சரியாக எங்கு வைக்க வேண்டும் என்பதைத் துல்லியமாகத் தெரிந்துகொள்ளும். படங்கள் அல்லது கருத்துப் பதிவுகள் (comment threads) போன்ற மாறுபடும் உயரமுள்ள உள்ளடக்கம் இருந்தால், mount செய்யப்பட்ட பிறகு அளவீடு செய்து, அதற்கேற்ப மாற்றங்களைச் செய்ய library-க்கு வேண்டியிருக்கும். இந்த அளவீட்டுப் படிநிலை மிகவும் தாமதமாக நடந்தால், scroll jitter ஏற்படலாம். உங்கள் தரவு (data) அனுமதித்தால், ஒரே மாதிரியான உயரங்களை அல்லது குறைந்தபட்ச உயரங்களை உறுதிப்படுத்தவும். இல்லையெனில், variable-height virtualizer-ஐப் பயன்படுத்தி கூடுதல் சிக்கல்களை ஏற்றுக்கொள்ளுங்கள்.
மூன்றாவதாக, overscanning உங்களுக்கு உதவும். திரையில் சரியாகத் தெரிவதைத் மட்டும் render செய்வது, பயனர் வேகமாக scroll செய்யும்போது வெற்று வெள்ளை பட்டைகளை உருவாக்கும். பெரும்பாலான libraries, திரைக்கு மேலே மற்றும் கீழே சில கூடுதல் உறுப்புகளை render செய்ய அனுமதிக்கின்றன. DOM-ஐ அதிகப்படியாகப் பெருக்காமல், தையல்களை (seams) மறைக்க இரண்டு அல்லது மூன்று வரிசைகளின் overscan போதுமானதாக இருக்கும்.
நான்காவதாக, key prop-ஐப் புறக்கணிக்காதீர்கள். ஒரு virtualized list-இல், நீங்கள் scroll செய்யும்போது உறுப்புகள் DOM nodes-களை மீண்டும் பயன்படுத்துகின்றன. நிலையான keys (Stable keys), React-ன் reconciliation செயல்பாட்டின் போது தவறாகக் கணிப்பதையும், வரிசை உறுப்புகளுக்குள் இருக்கும் state-ஐ அழிப்பதையும் தடுக்கின்றன. உங்கள் list வரிசைகளில் inputs, toggles அல்லது expandable sections இருந்தால், தவறான keys உங்கள் UI state-ஐப் பாதிக்கும். இது உங்கள் தரவுப் பிழையாகத் தெரியலாம், ஆனால் உண்மையில் இது rendering பிழையாகும்.
ஒரு நுட்பமான சிக்கல் browser find-in-page ஆகும். மறைக்கப்பட்ட உறுப்புகள் DOM-இல் இல்லாததால், browser-ன் தேடல் பெட்டி (search box) அவற்றைக் காணாது. உங்கள் பயனர்கள் ஒரு பெரிய list-க்குள் உள்ள உரையைத் தேட Ctrl+F-ஐப் பயன்படுத்துபவர்கள் என்றால், நீங்கள் document-க்கு பதிலாக dataset-க்கு எதிராகச் செயல்படும் ஒரு தனிப்பயன் தேடலை (custom search) உருவாக்க வேண்டும். List semantics சரியாகக் கையாளப்படாவிட்டால், screen readers-ஆலும் சூழலைப் புரிந்துகொள்ள முடியாமல் போகலாம், எனவே assistive technology மூலம் சோதித்து, dynamic loading-க்காக live region announcements-களைச் சேர்ப்பதைக் கருத்தில் கொள்ளுங்கள்.
எப்போது இதைத் தவிர்க்க வேண்டும்
Virtualization என்பது இலவசமானது அல்ல. இது dependency weight, coordinate math மற்றும் constraint overhead ஆகியவற்றைச் சேர்க்கிறது. உங்கள் list ஐம்பது அல்லது நூறு உறுப்புகளுக்குள் இருந்தால், browser அதை உதவியின்றி கையாள முடியும். முழுவதையும் render செய்துவிட்டு அடுத்த வேலையைப் பார்க்கவும். உங்கள் list உறுப்புகள் தனித்தனியாக மிகவும் சிக்கலானதாக இருந்தால் கூட இது பொருந்தும். Virtualization ஆயிரக்கணக்கான nodes-லிருந்து உங்களைக் காப்பாற்றும், ஆனால் ஒரு பெரிய chart அல்லது video element கொண்ட ஒரு single node-லிருந்து உங்களைக் காப்பாற்ற முடியாது. முதலில் item bloat-ஐச் சரிசெய்யுங்கள்.
மேலும், list scroll செய்யாதபோது virtualization-ஐத் தவிர்க்கவும். நீங்கள் next மற்றும் previous பொத்தான்களைக் கொண்டு paginating செய்து, ஒரு பக்கத்திற்கு இருபது உறுப்புகளை மட்டும் காட்டினால், அங்கு window செய்யத் தேவையில்லை. பயனர் ஒரு பெரிய தொடர்ச்சியான வரிசையை (contiguous sequence) scroll செய்ய எதிர்பார்க்கும்போது மட்டுமே இந்தத் தொழில்நுட்பம் பயனுள்ளதாக இருக்கும்.
உண்மையான கருத்து
Virtualization என்பது ஒரு libraryத் தேர்வு என்பதை விட, அது ஒரு மனநிலை (mindset). DOM என்பது ஒரு வரையறுக்கப்பட்ட வளமே தவிர, முடிவற்றத் திரை (infinite canvas) அல்ல என்பதை இது உங்களுக்கு உணர்த்துகிறது. இதைச் சேர்ப்பதற்கு முன், Chrome DevTools-ஐத் திறந்து, ஒரு performance profile-ஐப் பதிவு செய்து, layout அல்லது paint time தான் உண்மையில் சிக்கல் என்பதை உறுதிப்படுத்தவும். DOM தான் தடையாக (bottleneck) இருக்கிறது என்று தெரிந்தவுடன், கட்டுப்பாடுகளைப் பின்பற்றத் தொடங்குங்கள். உங்கள் உயரங்களைத் தீர்மானியுங்கள், keys-களைக் கவனியுங்கள், மிதமான overscan செய்யுங்கள் மற்றும் உங்கள் accessibility-ஐச் சோதியுங்கள். சரியாகச் செய்யப்பட்டால், ஒரு virtualized list பயன்படுத்த முடியாத தரவுச் சுவரை (data wall), ஒரு native scroll view போல லேசான ஒன்றாக மாற்றும். Browser போராடுவதை நிறுத்தும், உங்கள் பயனர்கள் காத்திருப்பதை நிறுத்துவார்கள், மேலும் உங்கள் ஆப் நீங்கள் திட்டமிட்டது போலவே வேகமான இடைமுகமாக (fast interface) மாறும்.
