跪拜 Guibai
← All articles
Frontend · Backend

Rendering 26,000 Satellites at 60fps in a Browser

By Imchendiana ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Rendering tens of thousands of dynamic objects in a browser without frame drops requires abandoning standard reactivity and per-object coordinate transforms. The ICRF-to-modelMatrix trick and shallowRef store pattern apply to any WebGL dashboard dealing with high-frequency, large-volume positional updates.

Summary

A satellite network simulation platform built with Vue 3, Cesium, and NestJS renders multi-layer constellations of up to 26,000 satellites in a browser at 60fps. The frontend pairs a fine-grained Entity mode for small counts with a batch Primitive mode that offloads coordinate transforms to the GPU, cutting CPU usage by over 80% at scale. A class-based store using shallowRef replaces deep Pinia reactivity to handle 50+ state fields without dropping frames during high-frequency timeline updates.

On the backend, simulation timelines spanning gigabytes are split across dynamically created MySQL tables keyed by case and scene ID, keeping queries in the millisecond range. A WebSocket gateway with a room-and-provider pattern handles real-time data push and provider failover. Shared TypeScript types published as an npm package prevent interface drift between frontend and backend, with a planned migration to a pnpm monorepo to eliminate the manual publish step.

A three-layer style inheritance system drives visual diffs automatically: when a user compares optimization versions, satellites and links change color to show additions, removals, and congestion shifts without any manual annotation.

Takeaways
Switching from Pinia's deep reactive store to a class-based store with shallowRef cut reactivity tracking costs enough to handle 10,000+ items at high update rates.
Batch Primitive rendering with a shared modelMatrix reduces 26,000 per-frame CPU matrix multiplications to a single GPU-side transform, keeping the scene at 60fps.
Pre-allocating Cartesian3 scratch objects and reusing them across frames eliminates garbage collection pauses during real-time orbit propagation.
Dynamic MySQL sharding by case and scene ID keeps timeline queries fast even when individual timelines reach multiple gigabytes.
A three-layer style system merges type defaults, per-element overrides, and version-diff extend tags so visual changes during architecture comparison are automatic.
Streaming gigabyte JSONL files line-by-line with Node.js readline avoids memory exhaustion during bulk imports.
Sharing TypeScript definitions via a dedicated npm package catches interface mismatches at compile time, though a monorepo removes the manual publish step entirely.
Conclusions

Most Cesium applications treat modelMatrix as a per-entity concern; applying it at the Collection level to defer coordinate transforms to the GPU is an underused pattern that changes the performance ceiling for large-scale space visualization.

The decision to keep the npm type package as a transitional step rather than forcing an immediate monorepo refactor is a practical reminder that architecture should serve delivery cadence, not purity.

Hardcoding visual styles in rendering functions is a trap that only becomes visible when the number of entity types grows; a config-driven inheritance system pays for itself after the second new satellite type.

Concepts & terms
ICRF (International Celestial Reference Frame)
An Earth-centered inertial coordinate system used as the native output of SGP4 orbit propagation. Unlike Earth-fixed coordinates, ICRF does not rotate with the planet, so satellite positions in this frame remain stable for batch GPU transforms.
shallowRef
A Vue 3 reactivity primitive that only triggers updates when the entire value is replaced, not when nested properties change. Used for large arrays to avoid the cost of deep reactive tracking.
BillboardCollection / LabelCollection
Cesium primitives that batch thousands of billboards or labels into a single GPU draw call. A shared modelMatrix applied to the entire collection transforms all positions in one operation.
Dynamic Table Sharding
Creating and dropping MySQL tables at runtime based on composite keys (case ID + scene ID), so each table stays small enough for fast queries even when total data reaches gigabytes.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗