4. Storage architecture for simulation data

CAE data science platforms rarely fail because of missing algorithms. They fail because the storage layer cannot answer three questions fast enough: Where is the raw result?, Which subset do I need?, and What does this field actually mean? A single crash simulation may produce 40–200 GB of transient field data across hundreds of timesteps, while a DOE campaign of 500 runs produces tens of thousands of files. Treating all of this as "files on a share" collapses under concurrency, partial reads, and schema drift. The practical answer is a polyglot storage architecture: each storage technology owns the access pattern it was designed for, and a metadata catalog stitches them together.

Matching Storage Technology to Access Pattern

The core design rule is that storage format follows access pattern, not file size. Four patterns dominate CAE workloads:

  • Bulk immutable artifacts — solver decks, restarts, full-field results, CAD/STEP geometry. Written once, read whole or by byte range, rarely mutated. → Object store.
  • Multi-dimensional field arrays — displacement, stress, temperature over mesh × timestep × component. Read as hyperslabs (a spatial region across selected timesteps). → HDF5.
  • Tabular scalar results — one row per run or per probe point: max von Mises stress, mass, drag coefficient, solver wall-clock. Read as columns with filters and aggregation. → Parquet.
  • High-frequency scalar streams — sensor channels, co-simulation coupling variables, convergence residuals sampled every iteration. Append-heavy, time-ordered, range-queried. → Time-series database (TSDB).

Metadata — mesh hash, solver version, unit system, boundary-condition set, material card, run status — belongs in none of the above as primary storage. It belongs in a catalog that indexes all four.

CAE Storage Layer: Producers, Stores, and Access Paths Producers Solver (Abaqus/OpenFOAM) Co-simulation coupling Test-rig DAQ Post-processing scripts DOE orchestrator Object Store (S3/MinIO) decks, restarts, full-field key: run_id/artifact_type HDF5 /mesh/nodes, /fields/U hyperslab reads, chunked Parquet run_id, max_stress, mass column pruning + predicate Time-Series DB residual, sensor_ch07 timestamp + tag index Metadata Catalog run_id, mesh_hash solver + version unit_system bc_set, material_card storage_uri (all 4) status, wall_clock schema_version lineage: parent_run_id Consumers ML training Surrogate model Dashboard Optimization loop Audit / replay Post-processor resolve direct byte-range / hyperslab read after catalog lookup

Object Stores: The Immutable Backbone

Object stores (S3, MinIO, Azure Blob, GCS) provide cheap, durable, horizontally scalable storage with HTTP range reads. They are the correct home for anything written once and read many times. The critical discipline is key naming, because object stores have no real directories — prefixes are just string conventions that determine listing performance and lifecycle policies.

s3://cae-platform/
  runs/2024/crash_frontal/A017/
    deck/A017.inp
    restart/A017.res
    results/A017_fields.h5
    scalars/A017_summary.parquet
    logs/solver.log
  runs/2024/crash_frontal/A017/_manifest.json

Partitioning by year/loadcase/run_id keeps listing operations bounded and lets lifecycle rules move cold campaigns to infrequent-access tiers. A _manifest.json per run records artifact checksums and the catalog record version, so a partially uploaded run is detectable rather than silently consumed.

Never write a solver directly to an object store over a POSIX mount and assume atomicity. Upload to a local scratch path, verify the checksum, then perform a single PutObject. Multi-part uploads that fail midway leave orphaned parts that still incur storage cost.

HDF5: Multi-Dimensional Field Data

HDF5 remains the workhorse for mesh-based field results because it supports chunked, compressed, multi-dimensional datasets with hyperslab selection. A well-structured CAE HDF5 file separates mesh topology (static) from field data (time-varying):

/mesh/nodes          float32 [N_nodes, 3]
/mesh/elements       int32   [N_elem, 8]
/mesh/connectivity   int32   [N_elem, 8]
/fields/U            float32 [N_timesteps, N_nodes, 3]   chunk=(1, 65536, 3), gzip-4
/fields/S_vm         float32 [N_timesteps, N_nodes]      chunk=(1, 65536)
/time                float64 [N_timesteps]
/attrs/unit_system   "mm-tonne-s"
/attrs/mesh_hash     "sha256:9f3c..."

Chunk shape is the single most consequential tuning decision. Chunking along the timestep axis with a large node block means a request for "all timesteps at nodes 10000–20000" touches few chunks. Chunking the other way (large timestep block, small node block) optimizes the opposite query. Because CAE post-processing usually extracts a spatial region across many timesteps, the first layout is generally correct. Compression at level 4 typically yields 3–6× reduction on float32 field data with negligible read penalty.

Two operational cautions: HDF5 is not safe for concurrent writers, and a single corrupted metadata block can render the entire file unreadable. For parallel solvers, write per-rank files and merge, or use MPI-IO with a single writer rank. Always keep the raw solver output in the object store as the recovery source.

Parquet: Scalar Results and Analytics

Once results are reduced to scalars — one row per run, or one row per probe point — Parquet becomes far more efficient than HDF5 or CSV. Columnar layout with dictionary encoding and row-group statistics enables predicate pushdown: a query for max_stress > 450 skips entire row groups without decompressing them.

run_id        loadcase   max_stress  mass_kg  drag_coeff  wall_clock_s  status
A017          frontal    487.3       12.41    0.312       8420          converged
A018          frontal    502.1       12.44    0.318       9110          converged
A019          frontal    NULL        12.39    NULL        3600          diverged

Partition the dataset by loadcase and status. A DOE analysis then reads only converged frontal runs, touching a fraction of the bytes. Keep the schema stable: adding a column is cheap, renaming one breaks every downstream query. Record the schema version in the catalog and validate on ingest.

Time-Series Databases: Streams and Convergence

TSDBs (InfluxDB, TimescaleDB, Prometheus for metrics) handle append-heavy, timestamp-indexed scalar data. In CAE they serve two distinct roles. First, solver convergence monitoring: residuals, contact forces, and energy balance sampled every iteration, queried as "show me the last 200 iterations for run A017." Second, physical test data: strain-gauge and accelerometer channels at 10–100 kHz, aligned to simulation time for validation.

The schema pattern is a measurement with tags and fields:

measurement: solver_residual
  tags:   run_id=A017, equation=Ux, loadcase=frontal
  fields: value=1.2e-4, iterations=340
  time:   2024-06-11T09:14:22Z

Tags are indexed and used for filtering; fields are the measured values. Cardinality is the classic failure mode — putting a unique node_id in a tag creates millions of series and destroys query performance. High-cardinality dimensions belong in HDF5 or Parquet, not in TSDB tags.

The Metadata Catalog: The Glue

The catalog is a relational or graph database (PostgreSQL is sufficient for most platforms) that holds one authoritative record per run and points to every storage location. It answers the questions that no individual store can: Which runs used mesh hash 9f3c? Which are diverged? What is the lineage of the surrogate model trained on campaign 2024-Q2?

FieldTypePurpose
run_idtext (PK)Stable identifier across all stores
mesh_hashtextDetect mesh reuse and invalidate stale results
solver, solver_versiontextReproducibility and regression triage
unit_systemtextPrevent silent unit-mixing in ML features
object_uri, hdf5_uri, parquet_uri, tsdb_seriestextResolve every artifact from one record
statusenumconverged / diverged / running / failed
schema_versionintGuard downstream parsers
parent_run_idtext (FK)Lineage for restarts and derived runs

Putting It Together: A Query Walkthrough

Consider a request to train a surrogate for peak stress across a frontal-crash DOE. The platform first queries the catalog for loadcase='frontal' AND status='converged', returning 480 run records with their Parquet URIs. It reads the Parquet dataset with column pruning to build the feature matrix (mass, thickness, material grade) and target vector (max_stress). For runs flagged as needing full-field validation, it resolves the HDF5 URI and performs a hyperslab read of /fields/S_vm at the final timestep only. Convergence history for the ten worst-performing runs is pulled from the TSDB to explain outliers. Every artifact is fetched by byte range from the object store — no full-file downloads.

The design principle throughout is that the catalog is the entry point and the stores are the payload. Consumers never guess a path; they resolve one from metadata. This keeps the architecture evolvable: migrating HDF5 to a new chunking scheme, or swapping MinIO for S3, changes URIs in the catalog without rewriting analysis code.

Start with three stores, not five. Object store + Parquet + catalog covers most early-stage platforms. Add HDF5 when full-field hyperslab access becomes a bottleneck, and a TSDB when convergence or sensor streams exceed what Parquet scans can serve interactively.