--- title: "How tables are stored" slug: "dev-database-concepts-architecture-element-store-layout" description: "Learn how VAST DataBase organizes tables, schemas, and queries, optimizing data storage and retrieval across clusters for efficient performance." updated: 2026-10-30T18:19:01Z published: 2026-10-30T18:19:01Z canonical: "docs.vastdata.com/dev-database-concepts-architecture-element-store-layout" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # How tables are stored *Applies to VAST Cluster 5.5. Examples use the VAST DataBase Python SDK 2.1.0.* This page explains how VAST DataBase lays out a table on the cluster, and what that layout means for the queries and loads you write. It describes how things work, not the internal sizes, which can change between releases. **Why it matters.** A VAST DataBase table is an element in the cluster's [Element Store](/develop/docs/dev-database-concepts-architecture-cluster-architecture#one-element-store), next to files and objects, and it's stored there in a layout of its own, described on this page. That layout decides how much data your queries read. A query reads only the columns it uses, because each column is stored separately. A filter skips data in small chunks, using each chunk's minimum and maximum, but only where matching values sit together, because of the order rows were inserted in, a sorted table or a projection. So which columns you select, how you sort the table and which types you store decide what a query costs; [what this means for you](/develop/docs/dev-database-concepts-architecture-element-store-layout#what-this-means-for-you) lists the consequences. ## In the Element Store ![A database is a bucket on a view with the Database protocol; a schema is stored like a directory and a table like a file, so the view's permissions apply. The table is stored as row groups, and within each row group each column is stored separately in small column chunks, each with its minimum and maximum. New rows land in the write buffer (storage-class memory) and background work moves them to flash, into this layout. The chunks sit on the cluster's shared storage, which every CNode reaches, so the splits of one query run on several CNodes at once and each reads only the columns it needs, skipping chunks whose minimum and maximum can't match.](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/es-table-in-store.svg?sv=2026-02-06&spr=https&st=2026-11-08T08%3A55%3A28Z&se=2026-11-08T09%3A55%3A28Z&sr=c&sp=r&sig=ShiUmy%2BSsBds8CAxrKUYgAhhhfdrRUzTwsCBoyrfBuc%3D) *A table in the Element Store: where it lives, how it's laid out, where it's stored.* What the Element Store means for a table: - **It lives in the same namespace as files and objects.** A database is a bucket on a view, and the table sits under it like a file (below), so the view's permissions and the cluster's snapshots apply to it as they do to files ([the cluster underneath](/develop/docs/dev-database-concepts-architecture-cluster-architecture#one-element-store)). - **It's stored on shared storage, not on a server.** Every CNode reaches every SSD, and a table's row groups are spread across the cluster, so many CNodes work on one table at once and nothing is re-sharded as it grows ([shared everything](/develop/docs/dev-database-concepts-architecture-cluster-architecture#shared-everything)). - **New rows reach the layout in two steps.** They land in the write buffer, storage-class memory, when you write them; after the commit, background work moves them to flash, into the layout this page describes. Committed rows can be queried straight away ([write path](/develop/docs/dev-database-concepts-architecture-write-path)). - **The cluster reduces the stored chunks** like any other data, after the write, so a table has no codec setting of its own ([compression and data reduction](/develop/docs/dev-database-concepts-architecture-compression)). ## Databases, schemas and tables A database is a bucket on a Database-enabled view ([configuring database access](/docs-5-5/docs/configuring-the-vast-cluster-for-database-access)). Inside it: - a **schema** is stored like a directory, - a **table** is stored like a file. A table looks like a file on a file system, so view permissions apply to it. Tables, files and objects are all elements in the cluster's Element Store; see [the cluster underneath](/develop/docs/dev-database-concepts-architecture-cluster-architecture#one-element-store). A table can't sit directly in the bucket; it always belongs to a schema. Each database keeps its own catalog of schemas, tables, projections and views, so there's no separate metastore ([schema management](/docs-5-5/docs/schema-management)). Some structures are attached to their table as hidden objects instead of appearing in the schema: semi-sorted projections, the sorted-table index, the partitions of a partitioned table, and the imports table. They're managed with the table and aren't separate tables you can see in a listing. ## Rows are grouped, and each column is stored separately A table's rows are grouped into **row groups** of consecutive row IDs. Within each row group, each column is stored separately, one column after another. So a table is stored column by column inside each group of rows. ![A grid of column chunks. Columns id, city and temperature run down the left; row groups 1, 2 and 3 run across the top. Each cell is a column chunk holding one column's values for the rows in that row group, with its minimum and maximum. One row is highlighted at the same position in every column of row group 2: that position is the row's ID.](https://cdn.us.document360.io/89b91113-5dc6-49db-86ec-57b16dc8ce56/Images/Documentation/table-storage-layout.svg?sv=2026-02-06&spr=https&st=2026-11-08T08%3A55%3A28Z&se=2026-11-08T09%3A55%3A28Z&sr=c&sp=r&sig=ShiUmy%2BSsBds8CAxrKUYgAhhhfdrRUzTwsCBoyrfBuc%3D) *Each column is stored separately within each row group. A chunk holds one column's values for a run of rows, plus their minimum and maximum.* A row's ID is its position in this layout: it says which row group the row is in and where in the group. Every column of a row is found at the same position. Each column's values for a run of rows are kept together in a **column chunk**, encoded in a compact, Arrow-like way. Chunks are small, at most around a hundred kilobytes, not whole files. Nested columns (lists, structs and maps) are stored as their leaf values plus nesting information. Values too large for a chunk are kept in separate overflow storage, with a reference in the chunk; that's how cells up to the [5 MB cell limit](/develop/docs/dev-database-reference-limits#sizes) are stored. A table's storage is spread across the cluster in slices of row groups, so a large table is worked on by many CNodes in parallel. ## Statistics kept with every chunk For each column chunk, the cluster records the minimum and maximum value and whether the chunk has nulls. Queries use these statistics to skip chunks that can't match a filter; see [read path and pruning](/develop/docs/dev-database-concepts-architecture-read-path-and-pruning#skipping-data-with-minmax-statistics). The statistics are kept for numbers, decimals, strings, booleans, dates, times, timestamps and fixed-size binary values. Strings keep only a short prefix, which is why sorting uses only the first eight bytes of a value ([sorted tables](/docs-5-5/docs/sorted-tables)). On sorted tables, the statistics for the sort columns are also rolled up to coarser levels, so whole ranges of the table can be skipped at once. ## Hidden markers for rows Each table has hidden columns that record whether a row exists and whether it has been deleted. Inserting a row sets the first; deleting a row sets the second. The deleted row's data isn't removed at that moment; see [write path](/develop/docs/dev-database-concepts-architecture-write-path#deletes). ## What this means for you | Because... | ...this happens | | --- | --- | | Each column is stored separately | A query that reads a few columns of a wide table reads only those columns. Wide tables cost little for queries that don't touch every column. | | Chunks are small, not whole files | An update or delete rewrites only the small chunks it touches, not a file of hundreds of megabytes, and a filter skips data at that grain. | | Statistics are kept per column chunk | A filter can skip data only where that column's values are grouped together by row, through insert order, a sorted table or a projection. | | String statistics keep only a prefix | Filters on long strings that share a prefix can't skip data. Store timestamps and numbers in their own types. | | A missing chunk reads as null | Adding a column changes only the table's column definitions. Existing rows read null for the new column; nothing is rewritten. | | Updates write only the columns they change | Updating one column of a row is cheaper than rewriting the row. See [write path](/develop/docs/dev-database-concepts-architecture-write-path#updates). | | A row's ID is its position | With [user-defined row IDs](/develop/docs/dev-database-guides-data-modeling#row-ids), the IDs you choose decide where rows are stored. Dense, ascending IDs keep each column's chunks full. | ## See also - [Write path](/develop/docs/dev-database-concepts-architecture-write-path): what happens to rows after you insert, update or delete them. - [Read path and pruning](/develop/docs/dev-database-concepts-architecture-read-path-and-pruning): how queries use this layout. - [Table structures](/develop/docs/dev-database-concepts-architecture-table-structures): sorted, partitioned, projection and vector-indexed tables.