Skip to main content

Command Palette

Search for a command to run...

Column-oriented processing.

Learning from DBMS series.

Updated
2 min read
Column-oriented processing.

It's tempting to believe that there is a single unifying theory of everything and while we haven't found it yet, there are numerous principles shared between different disciplines responsible for significant performance wins. With this post I'd like to start a series covering such principles. Columnar database management systems (DBMS) store data tables by column rather than by row. column-oriented-database-2.jpeg This way of storing data has many advantages for online analytical processing, including:

  • less IO. Since data is read in pages, in case of row-based storage, in addition to requested column, OS would also fetch other columns that are part of rows.
  • cache-friendly. Cache space is not wasted on storing unnecessary columns.
  • encoding-friendly. Delta-encoding, run-length encoding, delta-of-delta and ZigZag encodings are just a few schemes that are used to significantly reduce storage requirements.

Turns out that a very similar approach known in game development as a parallel array, where instead of array of structs, a struct of arrays is used. In Go this would look something like:

type User struct {
    name string
    age int
}
type Users []User

vs

type ParallelUsers struct {
    names []string
    ages []int
}

The benefits of such organization while doing linear scans of age values like in the function below

func SumUserAges(users Users) uint64 {
    var total uint64 = 0
    for _, user := range users {
        total += uint64(user.age)
    }
    return total
}

are very similar to those of columnar storage in DBMS:

  • reduced IO. While doing a linear scan over ages only relevant data will be fetched from memory.
  • cache-friendly. Only age values are stored in cache, they are also loaded in blocks and cache prefetcher is effective at speculative loading of age values that are going to be used soon.
  • vectorization. Compilers are able to leverage SIMD instructions to perform additions on of multiple age values.

To be continued...

A

Thanks, Taras, for the article.

I have few questions:

  • How does indexing work in columnar databases? What if I need to query 'Hire_date' info for Bob and Jim in a huge table. In RDBMS, I could use column index(B tree etc.) to fetch data very quickly.

  • You mentioned "array of structs vs. a struct of arrays" for column-based data. IIUC customer has to do proper mapping between columns on the client-side. If you use incorrect indexes to map columns for a specific row, it will introduce hidden bugs that are very difficult to debug. I wonder what is the best practice to fetch column-based data.

  • How are join queries performed? What would be the joining criteria?

Thanks!

1
T

Great questions, Agshin!

  • most columnar DBMSs do not use indexing, since they are focused on OLAP loads where it's common to scan massive amounts of data anyways. At the same time, it's totally possible to use either sort or hash based indexing scheme depending on data cardinality and specific use-case. I actually plan to write about hash-based indexing in one of the future posts.
  • the only advise I have is building hard-to-misuse APIs. For the most part it's possible to hide the difference completely.
  • they are performed in a similar way to row-based DBMSs. Why would the criteria be any different? :)

http://db.csail.mit.edu/projects/cstore/abadi-sigmod08.pdf has more details on indexing and join strategies used in popular solutions.

1
A

Thanks for the detailed reply.

In the RDBMS world, SQL joins are very slow for big data, and sometimes the preferred way is to use denormalization to avoid expensive joins. I was wondering how columnar structure deals with big joins.

I am looking forward to reading about hashing-based indexes. It may answer my question regarding optimization of join queries :)