Is your feature request related to a problem? Please describe.
Prior to this enhancement, handling large datasets in Neo.data.Store and Neo.grid.Container presented several performance challenges:
- Eager Record Instantiation: All
Neo.data.Record instances were created upfront, leading to significant initial load times and memory consumption for large datasets.
- UI Freezes during Large Data Adds: Synchronously adding extremely large numbers of raw data objects to the store could cause noticeable UI freezes.
- VDom Reconciliation Errors with Chunking: The existing chunking mechanism (intended to mitigate UI freezes) could lead to VDom reconciliation errors (e.g.,
RangeError, infinite loops) due to component ID collisions in Neo.grid.column.Component.
- Component Instance Overhead: Component columns could create and retain numerous component instances, contributing to memory overhead and rendering performance issues.
Describe the solution you'd like
This feature introduces a comprehensive set of enhancements to Neo.data.Store and Neo.grid.Container to address the aforementioned performance bottlenecks and improve overall responsiveness when dealing with large datasets.
Key aspects of the solution include:
Lazy Record Instantiation (GET-driven approach):
Neo.data.Store now stores raw data objects directly. Neo.data.Record instances are created lazily, only when an item is explicitly accessed via store.get(), store.getAt(), store.first(), store.last(), store.find(), store.findBy(), or store.forEach().
- Once a raw data object is converted to a
Record instance, the Record instance replaces the raw data in the store's internal collection for subsequent faster access.
Configurable Data Chunking:
- The
Neo.data.Store now includes an initialChunkSize config (Number, default 0). When set to a non-zero value, Store.add() will add data in chunks, mitigating UI freezes for very large synchronous data loads.
- The
postChunkLoad event is used to signal the completion of a chunked load.
Robust Component ID Management:
Neo.grid.column.Component.mjs#getComponentId now conditionally generates component IDs. If the store is in a chunking phase (store.chunkingTotal is truthy), it uses the rowIndex directly for unique IDs, preventing collisions. Otherwise, it uses the modulo-based ID generation for virtual scrolling.
Automated Component Instance Cleanup:
Neo.grid.Body now includes clearComponentColumnMaps() and cleanupComponentInstances() methods.
clearComponentColumnMaps() is called when the grid body is destroyed or when the store is completely cleared, ensuring all component instances are destroyed.
cleanupComponentInstances() is called after a chunked load (postChunkLoad) to destroy component instances that are no longer visible or needed (i.e., outside the mountedRows range), reducing memory overhead.
Grid.Body#afterSetStore also triggers clearComponentColumnMaps() when the store changes, ensuring proper cleanup of components from the old store.
Describe alternatives you've considered
(No specific alternatives considered as this is a comprehensive set of optimizations addressing multiple interconnected performance aspects.)
Additional context
This refactoring significantly improves the performance and memory footprint of applications dealing with large datasets by deferring the creation of Record instances and optimizing VDom reconciliation for chunked data.
Benchmark Results (from examples/grid/bigData):
| Scenario |
Before (Eager Instantiation) |
After (Lazy Instantiation, no chunking) |
After (Lazy Instantiation, with chunking) |
| 1,000 rows, 50 columns |
49ms (Record creation) |
2ms (Data gen + add) |
2ms (Data gen + add) |
| 50,000 rows, 50 columns |
2391ms (Record creation) |
93ms (Data gen + add) |
1252ms (Data gen + add) |
| 50,000 rows, 100 columns |
4377ms (Record creation) |
95ms (Data gen + add) |
1289ms (Data gen + add) |
| 1,000,000 rows, 50 columns |
N/A (too slow) |
~1.86s (estimated Data gen + add) |
1433ms (Data gen + add) |
| Observed UI Freeze (1M rows, 50 cols) |
N/A (too slow) |
~5s (observed) |
~10s (observed) |
Note: "Record creation" refers to the time taken for Neo.data.Record instantiation. "Data gen + add" refers to the time taken to generate raw data and add it to the store's collection.
The benchmarks demonstrate a dramatic reduction in initial data processing time due to lazy instantiation. While synchronous adding of 1M rows still causes a UI freeze, the configurable chunking mechanism provides a way to manage this for perceived performance. The resolution of VDom errors and component cleanup further enhance stability and efficiency.
Is your feature request related to a problem? Please describe.
Prior to this enhancement, handling large datasets in
Neo.data.StoreandNeo.grid.Containerpresented several performance challenges:Neo.data.Recordinstances were created upfront, leading to significant initial load times and memory consumption for large datasets.RangeError, infinite loops) due to component ID collisions inNeo.grid.column.Component.Describe the solution you'd like
This feature introduces a comprehensive set of enhancements to
Neo.data.StoreandNeo.grid.Containerto address the aforementioned performance bottlenecks and improve overall responsiveness when dealing with large datasets.Key aspects of the solution include:
Lazy Record Instantiation (GET-driven approach):
Neo.data.Storenow stores raw data objects directly.Neo.data.Recordinstances are created lazily, only when an item is explicitly accessed viastore.get(),store.getAt(),store.first(),store.last(),store.find(),store.findBy(), orstore.forEach().Recordinstance, theRecordinstance replaces the raw data in the store's internal collection for subsequent faster access.Configurable Data Chunking:
Neo.data.Storenow includes aninitialChunkSizeconfig (Number, default0). When set to a non-zero value,Store.add()will add data in chunks, mitigating UI freezes for very large synchronous data loads.postChunkLoadevent is used to signal the completion of a chunked load.Robust Component ID Management:
Neo.grid.column.Component.mjs#getComponentIdnow conditionally generates component IDs. If the store is in a chunking phase (store.chunkingTotalis truthy), it uses therowIndexdirectly for unique IDs, preventing collisions. Otherwise, it uses the modulo-based ID generation for virtual scrolling.Automated Component Instance Cleanup:
Neo.grid.Bodynow includesclearComponentColumnMaps()andcleanupComponentInstances()methods.clearComponentColumnMaps()is called when the grid body is destroyed or when the store is completely cleared, ensuring all component instances are destroyed.cleanupComponentInstances()is called after a chunked load (postChunkLoad) to destroy component instances that are no longer visible or needed (i.e., outside themountedRowsrange), reducing memory overhead.Grid.Body#afterSetStorealso triggersclearComponentColumnMaps()when the store changes, ensuring proper cleanup of components from the old store.Describe alternatives you've considered
(No specific alternatives considered as this is a comprehensive set of optimizations addressing multiple interconnected performance aspects.)
Additional context
This refactoring significantly improves the performance and memory footprint of applications dealing with large datasets by deferring the creation of
Recordinstances and optimizing VDom reconciliation for chunked data.Benchmark Results (from
examples/grid/bigData):Note: "Record creation" refers to the time taken for
Neo.data.Recordinstantiation. "Data gen + add" refers to the time taken to generate raw data and add it to the store's collection.The benchmarks demonstrate a dramatic reduction in initial data processing time due to lazy instantiation. While synchronous adding of 1M rows still causes a UI freeze, the configurable chunking mechanism provides a way to manage this for perceived performance. The resolution of VDom errors and component cleanup further enhance stability and efficiency.