+ All Categories
Home > Documents > Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing...

Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing...

Date post: 17-Aug-2020
Category:
Upload: others
View: 4 times
Download: 0 times
Share this document with a friend
21
Building Efficient Query Engines in a High-Level Language Yannis Klonatos, Christoph Koch, Tiark Rompf, Hassan Chafi Prashanth Menon, Reading Group Fall 2015 1
Transcript
Page 1: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Building Efficient Query Engines in a High-Level Language

Yannis Klonatos, Christoph Koch, Tiark Rompf, Hassan Chafi

Prashanth Menon, Reading Group Fall 2015

Towards Scalable Real-time Analytics:An Architecture for Scale-out of OLxP Workloads

Anil%K%Goel,%Jeffrey%Pound,%Nathan%Auch,%Peter%Bumbulis,%Scott%MacLean,%SAP%Labs%CanadaFranz%Fa%rber,%Francis%Gropengiesser,%Christian%Mathis,%Thomas%Bodner,%SAP%SE,%Germany%

Wolfgang%Lehner,%TU%Dresden,%Dresden,%Germany%

Joy$Arulraj,"Reading$Group$Fall$2015

1

Page 2: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Background• What happens to your SQL query?

2

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Hash JoinLegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Hash Agg

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Seq Scan S

Seq Scan R( )

Your Paper

You

October 19, 2015

Abstract

Your abstract.

1 Introduction

1 class Operator {

2 virtual void open() = 0;

3 virtual Tuple* next() = 0;

4 virtual void close() = 0;

5 };

1

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Page 3: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Background (2)• Volcano model is powerful, generic and composable

• Designed in an era where disk I/O dominated overhead

• If all data stored in main memory, it doesn’t perform well

• All next() calls are virtual (i.e., vtable lookup)

• Single function call overhead for each tuple, for each operator!

• Pretty poor cache utilization

• Can we do better?

3

Page 4: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Background (3)• Generate a per-query execution engine!

4

Hash JoinLegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Hash Agg

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Seq Scan S

Seq Scan R( )

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Chapter 3. Generating code for holistic query evaluation 29

Listing 3.1: Generic table scan-select

1 // loop ove r pages

2 f o r ( i n t p = start_page ; p <= end_page ; p++) {3 page_struct ⇤page = read_page (p , table ) ;

4 // loop ove r t u p l e s

5 f o r ( i n t t = 1 ; t <= page�>num_tuples ; t++) {6 tuple_struct ⇤tuple = read_tuple (t , page ) ;

7 i f ( ! ( matches ( tuple , predicate_value , predicate_offset ) ) con t i n u e ;

8 add_to_result ( tuple ) ;

9 }}

Listing 3.2: Type-specific table scan-select

1 // loop ove r pages

2 f o r ( i n t p = start_page ; p <= end_page ; p++) {3 page_struct ⇤page = read_page (p , table ) ;

4 // loop ove r t u p l e s

5 f o r ( i n t t = 0 ; t < page�>num_tuples ; t++) {6 vo i d ⇤tuple = page�>data + t ⇤ tuple_size ;

7 i n t ⇤value = tuple + predicate_offset ;

8 i f (⇤ value != predicate_value ) con t i n u e ;

9 memcpy ( . . ) ;

10 }}

array accesses, we can eliminate all function calls (but the unavoidable for loading

pages and generating the output) from the loop over the tuples of each page, saving a

large number of CPU cycles. We also reduce the number of instructions executed, as we

evaluate predicates over primitive data types. Moreover, the use of array computations

allows the code to exploit the processor’s superscalar design. The lack of function

calls in the inner loop, in combination with directly accessing tuples and their fields

by reference, further aids the compiler in optimising the generated code in ways that

efficiently distribute data to registers and favour cache reuse.

All holistic algorithms build upon the code template of Listing 3.2 and extend it

to include more tables (e.g., in join algorithms), perform any necessary predicate(s)

evaluation (Line 8) and manipulate the retrieved tuples as needed (Line 9). For com-

pleteness, we shall provide samples of the C code emitted by the code generator. This

will highlight the efficiency of the generated code, as well as the common code patterns

across different algorithms.

Throughout the subsequent analysis, one must keep in mind the difference in la-

tencies for accessing each level of the memory hierarchy. Recall from Figure 2.2 that

switching from sequential to random access may even double the latency on accesses

Chapter 3. Generating code for holistic query evaluation 34

Listing 3.3: Naıve nested loops join

1 // loop ove r pages

2 f o r ( i n t p_R = start_page_R ; p_R <= end_page_R ; p_R++) {3 page_struct ⇤ page_R = read_page ( p_R , R ) ;

4 f o r ( i n t p_S = start_page_S ; p_S <= end_page_S ; p_S++) {5 page_struct ⇤ page_S = read_page ( p_S , S ) ;

6

7 // loop ove r t u p l e s

8 f o r ( i n t t_R = 1 ; t_R <= page_R�>num_tuples ; t_R++) {9 tuple_struct ⇤ tuple_R = read_tuple ( t_R , page_R ) ;

10 f o r ( i n t t_S = 1 ; t_S <= page_S�>num_tuples ; t_S++) {11 tuple_struct ⇤ tuple_S = read_tuple ( t_S , page_S ) ;

12 i f ( ! ( matches ( tuple_R , offset_R , tuple_S , offset_S ) ) ) cont inue ;

13 add_to_result ( tuple_R , tuple_S ) ;

14 }}}}

Listing 3.4: Holistic nested loops join

1 // loop ove r pages

2 f o r ( i n t p_R = start_page_R ; p_R <= end_page_R ; p_R++) {3 page_struct ⇤ page_R = read_page ( p_R , R ) ;

4 f o r ( i n t p_S = start_page_S ; p_S <= end_page_S ; p_S++) {5 page_struct ⇤ page_S = read_page ( p_S , S ) ;

6

7 // loop ove r t u p l e s

8 f o r ( i n t t_R = 0 ; t_R < page_R�>num_tuples ; t_R++) {9 vo id ⇤ tuple_R = page_R�>data + t_R ⇤ tuple_size_R ;

10 f o r ( i n t t_S = 0 ; t_S < page_S�>num_tuples ; t_S++) {11 vo id ⇤ tuple_S = page_S�>data + t_S ⇤ tuple_size_S ;

12 i n t ⇤ attr_R = tuple_R + offset_R ;

13 i n t ⇤ attr_S = tuple_S + offset_S ;

14 i f (⇤ attr_R != ⇤ attr_S ) cont inue ;

15 add_to_result ( tuple_R , tuple_S ) ; /⇤ i n l i n e d ⇤/16 }}}}

(abstracted by the matches() function call in the code, where offset R and offset S

are the offsets of the join predicate attributes within tuples from R and S respectively)

and proceeding to the next loop only if a match is obtained. We now move on to

presenting how this principle can be applied in conjunction with other join algorithms.

3.5.2.1 Holistic nested loops join

Naıve nested loops can be greatly optimised if the code is generated in a hardware-

friendly way. This is shown in Listing 3.4, where, for simplicity, we assume the join

attributes are integers: (a) since the code is generated per query, the field types are

known a priori, which means we can revert separate function calls to pointer casts and

query.c query

Compile

Page 5: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Background• Compiling queries will yield better performance

• However, template expansion is:

• Brittle

• Very low level (i.e., hard to implement)

• Limited scope of compilation

• Limited adaptivity

5

Page 6: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Goal

• Performance of low-level hand-written query code

• Productivity of high-level language with rich type system guarantees

6

Page 7: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

LegoBase

• Query engine written in Scala

• Cross-compiles Scala query plans into optimized C code

• Four steps: 1. Convert pre-assembled physical query plan to naive Scala-based operator tree

2. Use Lightweight Modular Staging (LMS) to convert operator tree into Scala IR

3. Execute multiple optimization passes on IR

4. Output optimized Scala or C query plan

• Optimizations are written in Scala, operate on Scala types

• Programmatic removal of abstraction overhead

7

materialization points: (a) at the group by and (b) when mate-rializing the left side of the join. However, there is no need tomaterialize the tuples of the aggregation in two different data-structures as the aggregations can be immediately materializedin the data-structure of the join. Such inter-operator optimiza-tions are hard to express using template-based compilers. Byhigh-level programming we can instead easily pattern match onthe operators, as we show in Section 3.1.2.

• Finally, the data-structures have to be generic enough for allqueries. As such, they incur significant abstraction overhead,especially when these structures are accessed millions of timesduring query evaluation. Current query compilers cannot opti-mize the data-structures since these belong to the pre-compiledpart of the DBMS. Our approach eliminates these overheads asit performs whole-program optimization and compiles, alongwith the operators, the data-structures employed by a query.This significantly contrasts our approach with previous work.

The rest of this paper is organized as follows. Section 2 presentsthe overall design of LegoBase in more detail, while Section 3 givesexamples of compiler optimizations in multiple domains. Section 4presents our evaluation, where we experimentally show that our ap-proach can lead to significant benefits compared to (i) an existingquery compiler and (ii) a commercial database system. Section 5presents related work in compilation and compares our approachwith existing query compilers and engines. Finally, Section 6 con-cludes and highlights future work.

2. SYSTEM DESIGNIn this section we present the overall design of LegoBase, shown

in Figure 3. First, we describe the Lightweight Modular Staging(LMS) compiler that is the core of our architecture. Then, we de-scribe how LMS fits in the overall execution workflow of LegoBase(Subsection 2.2), and how we generate the final optimized C code(Subsection 2.3). While doing so, we give an example of how aphysical query operator is implemented in our system.

2.1 Staged Compilation & LMSLegoBase makes key use of the LMS framework [21], which

provides runtime compilation and code generation facilities for theScala programming language. LMS operates as follows. Givensome program written in Scala, LMS first converts the code toa graph-like intermediate representation (IR). In contrast to low-level compilation frameworks like LLVM that offer an IR whichoperates on the level of registers and basic blocks, LMS provideshigh-level IR nodes which correspond to constructs and operationsin Scala. This makes client code that uses LMS for runtime opti-mization similar to regular Scala code. In addition, LMS providesa high-level interface to add custom IR nodes, representing opera-tions on programmer-defined types and abstractions. For example,IR nodes in LMS may represent the creation of a hash map, theupdate of an array element, or operations on primitive values suchas the addition of two integers.

Programmers specify the result of a program transformation asa high-level Scala program, as opposed to a low-level, compiler-internal program representation. These transformations manipu-late the structure of the IR graph and they add, remove or replacenodes, depending on the optimization goal. For example, our data-structure specialization (Section 3.2) replaces IR nodes represent-ing operations on hash maps with IR nodes representing operationson native arrays. By expressing optimizations at a high-level, ourapproach enables a user-friendly way to describe these domain-specific optimizations that humans can easily identify. We use this

QueryOptimizer LegoBase LMS

Compiler

CCompiler(CLang)

PhysicalQueryPlan

ScalaQuery

Runtime ChangeRecompile DBMS

OptimizedC Query

SQLQuery C Binary

Figure 3: Overall system architecture. The domain-specific opti-mizations of LegoBase are applied during the LMS compiler phase.

optimization interface to provide database-specific optimizations asa library and to aggressively optimize our query engine.

LMS then performs consecutive transformation passes where itapplies all possible user-defined optimizations and generates a newIR, which is closer to the optimized final code. This structured ap-proach to optimizing code allows optimizations to be easily com-bined. As we show in Section 4, with a relatively small numberof transformations, we can get significant performance improve-ment. After each optimization pass, the whole set of optimizationsis re-examined, since more of them may now be applicable.

In addition to this high-level optimization framework, LMS pro-vides a programming model for expressing what is placed in theIR. The key programming abstraction for this is to introduce atype distinction for program expressions that will be compiled atruntime. Depending on the type of an expression (T vs. Rep[T],where T represents a type like Integer), we speak of present-stagevs. future-stage expressions and values. In particular, present-stagecomputation is executed right away, while future stage expressions(Rep[T]) are placed in the IR graph, so that they can be optimizedin subsequent optimization passes, as described above. Operationsthat operate on future-stage objects, e.g. an addition, are also con-verted to IR nodes. This programming model can be leveraged in anumber of design patterns: present-stage functions that operate onfuture-stage values are automatically inlined, loops with a present-stage trip count are unrolled, and future-stage data-structures canbe specialized based on present-stage information. A key examplein databases are records: when the schema is fixed, we can modelschema information and records as present-stage objects that ref-erence future-stage field values. Such records can be manipulatedusing object oriented or functional programming without paying aprice at runtime: the type system guarantees that the future-stagecode will contain only those field values that are actually used, andthat these fields will be represented as local variables.

The LMS compiler by default generates Scala code as outputfrom the optimized IR. In this work, we extend LMS so that it gen-erates C code as well. To reach the abstraction level of C code,transformations also include multiple lowering steps that map Scalaconstructs to (a set) of C constructs. For example, classes are con-verted to structs, strings to arrays of bytes, etc. In general, com-posite types are handled in a recursive way, by first lowering theirfields and then wrapping the result in a C struct. The final resultis a struct of only primitive C constructs. This automatic way oflowering does not require any modifications to the database codeor effort from the database developer. After these lowering steps,we can also apply low-level, architecture-dependent optimizations.In the end, the final iteration over the IR nodes emits the C code.

LMS already provides many generic compiler optimizations likefunction inlining, common subexpression and dead code elimina-tion, constant propagation, loop fusion, deforestation, and codemotion. In this work, we extend this set to include DBMS-specificoptimizations (e.g. using the popular columnar layout for data pro-cessing). We describe these in more detail in Section 3.

855

Page 8: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Optimizations• Optimizations are performed in LMS passes

• Similar to LLVM where passes are independent

• Optimizations include: • Inter-operator optimizations • Eliminating redundant materializations • Data structure specialization • Data layout changes • Traditional compiler optimizations (DCE, loop unrolling)

8

Page 9: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Inter-Operator Optimizations• Convert query plan from pull-based to push-based (à la HyPer)

• Operators push data to consumer operators • Better cache locality (no function calls, tuples remain in registers)

9

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next() : Record = {7 var t: Record = null8 if (it == null || !it.hasNext) {9 t = rightChild.findFirst { e =>

10 hm.get(hash(e)) match {11 case Some(hl) => it = hl.iterator; true12 case None => it = null; false13 }14 }15 }16 if (it == null || !it.hasNext) return null17 else return it.collectFirst {18 case e if cond(e,t) => conc(e, t)19 } get20 }21 }

(a) The starting Volcano-style implementation.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 var res: Record = null8 while (res = {9 if (it == null || !it.hasNext) {

10 hm.get(hash(t)) match {11 case Some(hl) => it = hl.iterator12 case None => it = null13 }14 }15 if (it == null || !it.hasNext) null16 else it.collectFirst {17 case e if cond(e,t) => conc(e, t)18 } get19 } != null) parent.next(res)20 }21 }

(b) After the first two steps of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 if (it == null || !it.hasNext) {8 hm.get(hash(t)) match {9 case Some(hl) => it = hl.iterator

10 case None => it = null11 }12 }13 while (it!=null && it.hasNext) it.collectFirst {14 case e if cond(e,t) => parent.next(conc(e,t))15 }16 }17 }

(c) After the third step of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 def next(t: Record) {6 hm.get(hash(t)) match {7 case Some(hl) => hl.foreach { e =>8 if (cond(e,t)) parent.next(conc(e,t))9 }

10 case None => {}11 }12 }13 }

(d) The final result after additional optimizations.

Figure 5: Transforming a HashJoin from a Volcano engine to a Push Engine. The lines highlighted in red and blue are removed and added,respectively. All branches and intermediate iterators are automatically eliminated. The open function (not shown) is handled accordingly.

we reach a materialization point. This organization significantlyimproves cache locality and branch prediction [15].

However, this dataflow optimization comes at the cost of re-quiring an API change (from an iterator to a consumer/producermodel). This in turn necessitates rewriting all operators: with tradi-tional approaches, this is a challenging and error-prone task consid-ering that the logic of each individual operator is likely spread overmultiple code fragments of complicated low-level software [15].

Given the two types of engines, there exists a methodologicalway to obtain one from the other. Thus, LegoBase implementsboth the Volcano model and a push engine, which we mechanicallyderived from Volcano. We present the high-level ideas of this con-version next, using the HashJoin operator as an example (Figure 5).

A physical query plan consists of a set of operators in a treestructure. For each operator, we can extract its children as well asits (single) parent. Operators call the next function of other chil-dren operators in the Volcano model to make progress in process-ing a tuple. An operator can be the caller, the callee or even bothdepending on its position in the tree (e.g. an operator with no chil-dren is only the callee, but an operator in an intermediate positionis both). Given a set of operators, we must take special care to (a)reverse the dataflow (turning callees to callers and vice versa) aswell as (b) handle stateful operators in a proper way. The optimiza-tion handles these cases in the following three steps:

Turning callees to callers: When calling a next function in the Vol-cano model, a single tuple is returned by the callee1. In contrast,in a push model, operators call their parents whenever they have atuple ready. The necessary transformation is straightforward: in-stead of letting callees return a single tuple, we remove this returnstatement. Then, we put the whole operator logic inside a whileloop which continues until the value that would be returned in theoriginal callee operator is null (operator has completed execution).For each tuple encountered in this loop, we call the next function ofthe original parent. For scan operators, who are only callees, thisstep is enough to port these operators to the push-style engine.

Turning callers to callees: The converse of the above modifica-tion should be performed: the original callers should be convertedto callees. To do this, we remove the call to the next function of thechild in the original caller, since in the push engine the callee callsthe next function of the parent. However, we still need a tuple toprocess. Thus, this step changes all next functions to take a recordas argument, which corresponds to the value that would be returnedfrom a callee in the Volcano engine. Observe that the call to nextmay be explicit or implicit through functional abstractions like the

1This assumes no block-style processing, where multiple tuples arefirst materialized and then returned as a unit. In general, LegoBaseavoids materialization whenever possible.

857

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next() : Record = {7 var t: Record = null8 if (it == null || !it.hasNext) {9 t = rightChild.findFirst { e =>

10 hm.get(hash(e)) match {11 case Some(hl) => it = hl.iterator; true12 case None => it = null; false13 }14 }15 }16 if (it == null || !it.hasNext) return null17 else return it.collectFirst {18 case e if cond(e,t) => conc(e, t)19 } get20 }21 }

(a) The starting Volcano-style implementation.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 var res: Record = null8 while (res = {9 if (it == null || !it.hasNext) {

10 hm.get(hash(t)) match {11 case Some(hl) => it = hl.iterator12 case None => it = null13 }14 }15 if (it == null || !it.hasNext) null16 else it.collectFirst {17 case e if cond(e,t) => conc(e, t)18 } get19 } != null) parent.next(res)20 }21 }

(b) After the first two steps of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 if (it == null || !it.hasNext) {8 hm.get(hash(t)) match {9 case Some(hl) => it = hl.iterator

10 case None => it = null11 }12 }13 while (it!=null && it.hasNext) it.collectFirst {14 case e if cond(e,t) => parent.next(conc(e,t))15 }16 }17 }

(c) After the third step of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 def next(t: Record) {6 hm.get(hash(t)) match {7 case Some(hl) => hl.foreach { e =>8 if (cond(e,t)) parent.next(conc(e,t))9 }

10 case None => {}11 }12 }13 }

(d) The final result after additional optimizations.

Figure 5: Transforming a HashJoin from a Volcano engine to a Push Engine. The lines highlighted in red and blue are removed and added,respectively. All branches and intermediate iterators are automatically eliminated. The open function (not shown) is handled accordingly.

we reach a materialization point. This organization significantlyimproves cache locality and branch prediction [15].

However, this dataflow optimization comes at the cost of re-quiring an API change (from an iterator to a consumer/producermodel). This in turn necessitates rewriting all operators: with tradi-tional approaches, this is a challenging and error-prone task consid-ering that the logic of each individual operator is likely spread overmultiple code fragments of complicated low-level software [15].

Given the two types of engines, there exists a methodologicalway to obtain one from the other. Thus, LegoBase implementsboth the Volcano model and a push engine, which we mechanicallyderived from Volcano. We present the high-level ideas of this con-version next, using the HashJoin operator as an example (Figure 5).

A physical query plan consists of a set of operators in a treestructure. For each operator, we can extract its children as well asits (single) parent. Operators call the next function of other chil-dren operators in the Volcano model to make progress in process-ing a tuple. An operator can be the caller, the callee or even bothdepending on its position in the tree (e.g. an operator with no chil-dren is only the callee, but an operator in an intermediate positionis both). Given a set of operators, we must take special care to (a)reverse the dataflow (turning callees to callers and vice versa) aswell as (b) handle stateful operators in a proper way. The optimiza-tion handles these cases in the following three steps:

Turning callees to callers: When calling a next function in the Vol-cano model, a single tuple is returned by the callee1. In contrast,in a push model, operators call their parents whenever they have atuple ready. The necessary transformation is straightforward: in-stead of letting callees return a single tuple, we remove this returnstatement. Then, we put the whole operator logic inside a whileloop which continues until the value that would be returned in theoriginal callee operator is null (operator has completed execution).For each tuple encountered in this loop, we call the next function ofthe original parent. For scan operators, who are only callees, thisstep is enough to port these operators to the push-style engine.

Turning callers to callees: The converse of the above modifica-tion should be performed: the original callers should be convertedto callees. To do this, we remove the call to the next function of thechild in the original caller, since in the push engine the callee callsthe next function of the parent. However, we still need a tuple toprocess. Thus, this step changes all next functions to take a recordas argument, which corresponds to the value that would be returnedfrom a callee in the Volcano engine. Observe that the call to nextmay be explicit or implicit through functional abstractions like the

1This assumes no block-style processing, where multiple tuples arefirst materialized and then returned as a unit. In general, LegoBaseavoids materialization whenever possible.

857

Page 10: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Inter-Operator Optimizations (2)

10

• Convert query plan from pull-based to push-based (à la HyPer) • Operators push data to consumer operators • Better cache locality (no function calls, tuples remain in registers)

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next() : Record = {7 var t: Record = null8 if (it == null || !it.hasNext) {9 t = rightChild.findFirst { e =>

10 hm.get(hash(e)) match {11 case Some(hl) => it = hl.iterator; true12 case None => it = null; false13 }14 }15 }16 if (it == null || !it.hasNext) return null17 else return it.collectFirst {18 case e if cond(e,t) => conc(e, t)19 } get20 }21 }

(a) The starting Volcano-style implementation.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 var res: Record = null8 while (res = {9 if (it == null || !it.hasNext) {

10 hm.get(hash(t)) match {11 case Some(hl) => it = hl.iterator12 case None => it = null13 }14 }15 if (it == null || !it.hasNext) null16 else it.collectFirst {17 case e if cond(e,t) => conc(e, t)18 } get19 } != null) parent.next(res)20 }21 }

(b) After the first two steps of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 if (it == null || !it.hasNext) {8 hm.get(hash(t)) match {9 case Some(hl) => it = hl.iterator

10 case None => it = null11 }12 }13 while (it!=null && it.hasNext) it.collectFirst {14 case e if cond(e,t) => parent.next(conc(e,t))15 }16 }17 }

(c) After the third step of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 def next(t: Record) {6 hm.get(hash(t)) match {7 case Some(hl) => hl.foreach { e =>8 if (cond(e,t)) parent.next(conc(e,t))9 }

10 case None => {}11 }12 }13 }

(d) The final result after additional optimizations.

Figure 5: Transforming a HashJoin from a Volcano engine to a Push Engine. The lines highlighted in red and blue are removed and added,respectively. All branches and intermediate iterators are automatically eliminated. The open function (not shown) is handled accordingly.

we reach a materialization point. This organization significantlyimproves cache locality and branch prediction [15].

However, this dataflow optimization comes at the cost of re-quiring an API change (from an iterator to a consumer/producermodel). This in turn necessitates rewriting all operators: with tradi-tional approaches, this is a challenging and error-prone task consid-ering that the logic of each individual operator is likely spread overmultiple code fragments of complicated low-level software [15].

Given the two types of engines, there exists a methodologicalway to obtain one from the other. Thus, LegoBase implementsboth the Volcano model and a push engine, which we mechanicallyderived from Volcano. We present the high-level ideas of this con-version next, using the HashJoin operator as an example (Figure 5).

A physical query plan consists of a set of operators in a treestructure. For each operator, we can extract its children as well asits (single) parent. Operators call the next function of other chil-dren operators in the Volcano model to make progress in process-ing a tuple. An operator can be the caller, the callee or even bothdepending on its position in the tree (e.g. an operator with no chil-dren is only the callee, but an operator in an intermediate positionis both). Given a set of operators, we must take special care to (a)reverse the dataflow (turning callees to callers and vice versa) aswell as (b) handle stateful operators in a proper way. The optimiza-tion handles these cases in the following three steps:

Turning callees to callers: When calling a next function in the Vol-cano model, a single tuple is returned by the callee1. In contrast,in a push model, operators call their parents whenever they have atuple ready. The necessary transformation is straightforward: in-stead of letting callees return a single tuple, we remove this returnstatement. Then, we put the whole operator logic inside a whileloop which continues until the value that would be returned in theoriginal callee operator is null (operator has completed execution).For each tuple encountered in this loop, we call the next function ofthe original parent. For scan operators, who are only callees, thisstep is enough to port these operators to the push-style engine.

Turning callers to callees: The converse of the above modifica-tion should be performed: the original callers should be convertedto callees. To do this, we remove the call to the next function of thechild in the original caller, since in the push engine the callee callsthe next function of the parent. However, we still need a tuple toprocess. Thus, this step changes all next functions to take a recordas argument, which corresponds to the value that would be returnedfrom a callee in the Volcano engine. Observe that the call to nextmay be explicit or implicit through functional abstractions like the

1This assumes no block-style processing, where multiple tuples arefirst materialized and then returned as a unit. In general, LegoBaseavoids materialization whenever possible.

857

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next() : Record = {7 var t: Record = null8 if (it == null || !it.hasNext) {9 t = rightChild.findFirst { e =>

10 hm.get(hash(e)) match {11 case Some(hl) => it = hl.iterator; true12 case None => it = null; false13 }14 }15 }16 if (it == null || !it.hasNext) return null17 else return it.collectFirst {18 case e if cond(e,t) => conc(e, t)19 } get20 }21 }

(a) The starting Volcano-style implementation.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 var res: Record = null8 while (res = {9 if (it == null || !it.hasNext) {

10 hm.get(hash(t)) match {11 case Some(hl) => it = hl.iterator12 case None => it = null13 }14 }15 if (it == null || !it.hasNext) null16 else it.collectFirst {17 case e if cond(e,t) => conc(e, t)18 } get19 } != null) parent.next(res)20 }21 }

(b) After the first two steps of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 var it: Iterator[Record] = null6 def next(t: Record) {7 if (it == null || !it.hasNext) {8 hm.get(hash(t)) match {9 case Some(hl) => it = hl.iterator

10 case None => it = null11 }12 }13 while (it!=null && it.hasNext) it.collectFirst {14 case e if cond(e,t) => parent.next(conc(e,t))15 }16 }17 }

(c) After the third step of the algorithm.

1 case class HashJoin[B](leftChild: Operator,2 rightChild: Operator, hash: Record=>B,3 cond: (Record,Record)=>Boolean) extends Operator {4 val hm = HashMap[B,ArrayBuffer[Record]]()5 def next(t: Record) {6 hm.get(hash(t)) match {7 case Some(hl) => hl.foreach { e =>8 if (cond(e,t)) parent.next(conc(e,t))9 }

10 case None => {}11 }12 }13 }

(d) The final result after additional optimizations.

Figure 5: Transforming a HashJoin from a Volcano engine to a Push Engine. The lines highlighted in red and blue are removed and added,respectively. All branches and intermediate iterators are automatically eliminated. The open function (not shown) is handled accordingly.

we reach a materialization point. This organization significantlyimproves cache locality and branch prediction [15].

However, this dataflow optimization comes at the cost of re-quiring an API change (from an iterator to a consumer/producermodel). This in turn necessitates rewriting all operators: with tradi-tional approaches, this is a challenging and error-prone task consid-ering that the logic of each individual operator is likely spread overmultiple code fragments of complicated low-level software [15].

Given the two types of engines, there exists a methodologicalway to obtain one from the other. Thus, LegoBase implementsboth the Volcano model and a push engine, which we mechanicallyderived from Volcano. We present the high-level ideas of this con-version next, using the HashJoin operator as an example (Figure 5).

A physical query plan consists of a set of operators in a treestructure. For each operator, we can extract its children as well asits (single) parent. Operators call the next function of other chil-dren operators in the Volcano model to make progress in process-ing a tuple. An operator can be the caller, the callee or even bothdepending on its position in the tree (e.g. an operator with no chil-dren is only the callee, but an operator in an intermediate positionis both). Given a set of operators, we must take special care to (a)reverse the dataflow (turning callees to callers and vice versa) aswell as (b) handle stateful operators in a proper way. The optimiza-tion handles these cases in the following three steps:

Turning callees to callers: When calling a next function in the Vol-cano model, a single tuple is returned by the callee1. In contrast,in a push model, operators call their parents whenever they have atuple ready. The necessary transformation is straightforward: in-stead of letting callees return a single tuple, we remove this returnstatement. Then, we put the whole operator logic inside a whileloop which continues until the value that would be returned in theoriginal callee operator is null (operator has completed execution).For each tuple encountered in this loop, we call the next function ofthe original parent. For scan operators, who are only callees, thisstep is enough to port these operators to the push-style engine.

Turning callers to callees: The converse of the above modifica-tion should be performed: the original callers should be convertedto callees. To do this, we remove the call to the next function of thechild in the original caller, since in the push engine the callee callsthe next function of the parent. However, we still need a tuple toprocess. Thus, this step changes all next functions to take a recordas argument, which corresponds to the value that would be returnedfrom a callee in the Volcano engine. Observe that the call to nextmay be explicit or implicit through functional abstractions like the

1This assumes no block-style processing, where multiple tuples arefirst materialized and then returned as a unit. In general, LegoBaseavoids materialization whenever possible.

857

Page 11: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Redundant Materialization

• Not necessary to materialize aggregations

• Can bypass aggregation node and perform aggregation in build phase of join

• Difficult (probably not impossible) to express when using code templates

11

Hash JoinLegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Hash Agg

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Seq Scan S

Seq Scan R( )

LegoBase

HandwrittenQuery Plans

Query Compilers

Existing DBMSes

DBMS in High-LevelLanguage

Performance

Prod

uctiv

ity

Figure 1: Comparison of performance/productivity tradeoff for allapproaches presented in this paper.

the time saved can be spent implementing more database fea-tures and optimizations. The LegoBase query engine is the firststep towards providing a full DBMS system written in a high-level language.

In addition, high-level programming allows to quickly definesystem modules that are truly reusable (even in contexts verydifferent from the one these were created for) and easily com-posable [16], thus putting an end to the monolithic nature ofimportant DBMS components like the storage manager. Thisproperty makes the overall maintenance of the system signif-icantly easier. More importantly, it grants great flexibility todevelopers so that they can easily choose and experiment with anumber of choices when building query engines.

• We apply generative programming [27] to DBMS development.This approach provides two key benefits over traditional querycompilers: (a) programmatic removal of abstraction overheadand (b) applying optimizations on multiple abstraction levels.

First, the Scala code that constitutes the query engine, despiteits high-level appearance, is actually a program generator thatemits optimized, low-level C code. In contrast to traditionalcompilers, which need to perform complicated and sometimesbrittle analyses before (maybe) optimizing programs, generativemetaprogramming in Scala takes advantage of the type systemof the language in order to provide programmers with strongguarantees about the shape and nature of the generated code.For example, it ensures that certain abstractions (e.g. genericdata-structures and function calls) are definitely optimized awayduring code generation.

Second, generative programming allows optimization and(re-)compilation of code at various execution stages. This is avery important property, as it allows us to view databases as liv-ing organisms. When the system is first developed, high-leveland non-optimal abstractions can be used to simplify the de-velopment process. During deployment, as more informationis gathered (e.g. runtime statistics, configuration and hardwarespecifications), we can continuously “evolve” the query engineby recompiling the necessary components in order to take ad-vantage of up-to-date information. To our knowledge, Lego-Base is the first to support such continuous runtime optimizationof the whole query engine. This design choice differentiatesour system from recent work on compiling only queries [15] orquery optimization frameworks such as Starburst [6].

In our work, we use the Lightweight Modular Staging (LMS)compiler [21] for Scala. In addition to the previous contribu-tions, we leverage the high-level and extensible IR of LMS. Thisdesign property allows us to extend the scope of compilation and

1 select *2 from R, (select S.D,3 sum(1-S.B) as E,4 sum(S.A*(1-S.B)),5 sum(S.A*(1-S.B)*(1+S.C))6 from S group by S.D) T7 where R.Z=T.E and R.B=3

./E=Z

GD,aggs

S

sB=3

R

Figure 2: Motivating example showing missed optimizations op-portunities by existing query compilers.

perform whole-program optimization, by specializing all data-structures and auxiliary algorithms of a query. We do so byspecifying custom, database-specific optimizations. These areimplemented as library components, providing a clean separa-tion from the base code of LegoBase. Optimizations are (a) eas-ily adjustable to the characteristics of workloads and architec-tures, (b) easily configurable, so that they can be turned on andoff at demand and (c) easily composable, so that higher-leveloptimizations can be built from lower-level ones. These proper-ties are very hard to provide using any existing template-basedcompiler. We present examples of optimizations for query plans(inter-operator optimizations), data-structures, and data layout.

• We provide an experimental evaluation with the TPC-H bench-mark [28] which shows that our system, along with the afore-mentioned optimizations, can significantly outperform both acommercial in-memory database, called DBX, and the querycompiler of the HyPer system [15]. This improvement requiresprogramming just a few hundred lines of Scala code for the op-timizations, thus demonstrating the great expressive power ofour optimization framework. An important observation in thiscontext is that developers cannot rely on low-level compilationframeworks, like LLVM, to automatically detect the high-leveloptimizations that we support in LegoBase. In addition, weshow that our query compilation strategy incurs negligible over-head to query execution. These results aim to prove the promiseof the abstraction without regret vision.

Motivating Example. To better understand the differences of ourwork with previous approaches, consider the simple SQL queryshown in Figure 2. This query first calculates some aggregationsfrom relation S in the group by operator G. Then, it joins theseaggregations with relation R, the tuples of which are filtered by thevalue of column B. The results are then returned to the user. Carefulexamination of the execution plan of this query, shown in the samefigure, reveals the following three basic optimization opportunitiesmissed by all existing query compilers:

• First, the limited scope of existing approaches usually resultsin performing the evaluation of aggregations in pre-compiledDBMS code. Thus, each aggregation is evaluated consecutivelyand, as a result, common subexpression elimination cannot beperformed in this case (e.g. in the calculation of expressions1-S.B or S.A*(1-S.B)). This shows that, if we include theevaluation of all aggregations in the compiled final code, wecan get additional performance improvements. This motivatesus to extend the scope of compilation in this work.

• Second, template-based approaches may result in unnecessarycomputation. In this example, the generated code includes two

854

Page 12: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Redundant Materialization

12

findFirst in line 9 of Figure 5(a). In addition, calls to the next func-tion may happen in the open function of the Volcano model forpurposes of state-initialization. We handle the open function sim-ilarly. This step ports the Sort, Map, Aggregate, Select, Window,View and Print operators of LegoBase to the push-engine2.

Managing state: Finally, special care should be taken for state-ful operators. The traditional example of such operators is the joinvariants (semi-join, hash-join, anti-join etc). For these operators,the tuples from the left child are organized in hash lists, matched onthe join condition with tuples from the right child. Then, to avoidmaterialization, the join operator must keep state about how manyelements have already been output from this list whenever there isa match. A nice abstraction for this is the iterator interface3, wherefor each next call in the Volcano model the iterator is advanced byone (and one output tuple is produced). In this optimization wechange this behaviour so that after the iterator is initialized, we ex-haust it by calling the next function of the parent for each tuple in it.

It is important to note that the above methodology, which seemsstraightforward, reveals an important advantage of our staging com-piler infrastructure: that by programming operators at a high-level,it then becomes straightforward to express optimizations for thoseoperators. In this example, the optimization’s code follows closelythe human-readable description given above. Corner cases can thenbe handled on top of this baseline implementation, sacrificing nei-ther the readability of the original operators nor the baseline op-timization itself. In addition, after this optimization, the stagingcompiler can further optimize the generated code, as shown in Fig-ures 5(c) and 5(d). There, the compiler detects that both the itera-tor abstraction and some while loops can be completely removed,and automatically removes them, thus improving branch predic-tion. This is an important advantage of staging compilers comparedto the existing template-based and static query compilers.

3.1.2 Eliminating Redundant MaterializationsConsider again the motivating example of our introduction. We

observed that existing query compilers use template-based gener-ation and, thus, in such schemes operators are not aware of eachother. This can cause redundant computation: in the example thereare two materialization points (in the group by and in the left sideof the hash join) where there could be only a single one.

By expressing optimizations at a higher-level, we can treat op-erators as objects in Scala, and then match specific optimizationsto certain chains of operators. Here, we can completely removethe aggregate operator and merge it with the join. The code of theoptimization is shown in Figure 6.

This optimization operates as follows. First, we call the opti-mize function, passing it the top-level operator as an argument. Thefunction then traverses the tree of Scala operator objects, until it en-counters a proper chain of operators to which the optimization canbe applied to. In the case of the example the chain is (as shown inline 2 of Figure 6) a hash-join operator connected to an aggregateoperator. When this pattern is detected, a new HashJoin operatorobject is created, that is not connected to the aggregate operator,

2All operators initialize their state (if any) from one child in theopen function, and call their other child (if any) in the next function.The only exception is the nested loop joins operator which callsboth children in the next function. We handle this by introducingphases where each phase handles tuples only from one child.3Observe that the iterator itself is an abstraction which introducesoverheads during execution. Our compiler maps this high-levelconstruct to efficient native C loops.

1 def optimize(op: Operator): Operator = op match {2 case hj@HashJoin(aggOp:AggOp,_,h,eq) =>3 new HashJoin(aggOp.child,hj.rightChild,h,eq) {4 override def open() {5 // leftChild is now the child of aggOp6 leftChild foreach { t =>7 val key = hj.leftHash(aggOp.grp(t))8 // Get aggregations from hash map of HJ9 val aggs = hm.getOrElseUpdate(key,

10 new Array[Double](aggOp.aggFuncs.size))11 aggOp.processAggs(aggs,t)12 }13 }14 }15 case x: Operator =>16 x.leftChild = optimize(x.leftChild)17 x.rightChild = optimize(x.rightChild)18 case null => null19 }

Figure 6: Removing redundant materializations by high-level pro-gramming (here between a group by and a join).

but instead to the child of the latter (line 3 of Figure 6). As a result,the materialization point of the aggregate operator is completelyremoved. However, we must still find a place to (a) store the ag-gregate values and (b) perform the aggregation. For this purposewe use the hash map of the hash join operator (line 9), and we justcall the corresponding function of the Aggregate operator (line 11),respectively. Observe that in this optimization there is almost nocode duplication, showing the great merit of abstraction withoutregret. In addition, all low-level compiler optimizations can still beapplied after the application of the optimization presented here.

Finally, we observe that this optimization is programmed in thesame level of abstraction as the rest of the query engine: as normalScala code. This property raises the productivity provided by ourcompiler, and is another example where optimizations are devel-oped in a way that is completely intuitive to programmers. Thisdesign also allows them to use all existing software developmenttools for optimizing the query engine.

3.2 Data-Structure SpecializationData structure optimizations contribute significantly to the com-

plexity of database systems today, as they tend to be heavily spe-cialized to be workload, architecture and (even) query-specific. Ourexperience with the PostgreSQL database management system re-veals that there are many distinct implementations of memory pageabstraction and B-trees. These versions are slightly divergent fromeach other, suggesting that the optimization scope is limited. How-ever, this situation significantly contributes to a maintenance night-mare as in order to apply any code update, many different pieces ofcode have to be modified.

In addition, even though data-structure specialization is impor-tant when targeting high-performance systems, it is not providedby any existing query compilation engine. Since our LMS com-piler can be used to optimize the whole Scala code, and not onlythe operator interfaces, it allows for various degrees of special-ization in data-structures, as has been previous shown in [22]. Inthis paper, we demonstrate such possibilities by showing how hashmaps, which are the most commonly used data-structures alongwith Trees in DBMSes, can be heavily specialized for significantperformance improvements by using schema and query knowledge.Close examination of the generic hash maps in the baseline imple-mentation of our operators (e.g. in the Aggregation of Figure 4)reveals the following three main abstraction overheads.

First, for every insert operation, a hash map must allocate atriplet holding the key, the corresponding value as well as a pointerto the next element in the hash bucket. This introduces a significant

858

• Implemented as IR pass • If we see an HJ node whose left child is Agg grouping on same

join attribute, merge them

Page 13: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Data-Structure Specialization• Use schema and query knowledge to specialize hash maps

• Remove abstraction overhead of generic hash maps • Three main problems:

1. Redundant data storage (key is usually subset of value) 2. Lookups require virtual calls to hashing functions 3. Hash maps require resizing during runtime

• LegoBase solutions: 1. Convert hash map to contiguous array (of buckets)

2. Only store values in nodes 3. Inline hash and equality functions

4. Use runtime statistics to predict and allocate size of hash map at compile time

13

Page 14: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Changing Data Layout• Possible to switch between row and column form at runtime

• Does not require rewriting query engine

• Implemented as an IR optimization pass • Triggered when we see Array[Record] (array of type record) in IR • Possible to implement any new data storage layout as an IR optimization

14

1 trait ArrayOpsExpOpt extends ArrayOpsExp {2 // Override the IR node constructors3 override def array_new[T:Manifest](n:Int) =4 manifest[T] match {5 case Record(attrs) =>6 // Create a new array for each attribute7 val arrays = for (tp<-attrs) yield array_new(n)(tp)8 // Pack everything in a new record9 record(attrs, arrays)

10 case _ => super.array_new(n)11 }12

13 override def array_update[T:Manifest](ar:Array[T],14 n:Int, v:T) =15 manifest[T] match {16 case Record(attrs) =>17 // Get columns and update each one18 val arrays = for (l <- attrs) yield field(ar, l)19 for ((a, l) <- arrays zip attrs)20 a(n) = field(v, l)21 case _ => super.array_update(ar, n, v)22 }23

24 override def array_apply[T:Manifest](ar:Array[T],25 n:Int) =26 manifest[T] match {27 case Record(attrs) =>28 val arrays = for (l <- attrs) yield field(ar, l)29 val elems = for (a <- arrays) yield a(n)30 // Perform record reconstruction31 record(attrs, elems)32 case _ => super.array_apply(ar, n)33 }34

35 // Fill remaining operations accordingly36 }

Figure 8: Changing the data layout (from row to column) expressedas an optimization. ArrayOpsExp is the compiler trait for handlingArrays that we overwrite. Scala manifests carry type information.

Figure 9. Similarly, if LMS can statically determine that some at-tribute is never used (e.g. by having all queries given in advance),then the row layout still has to skip this attribute during query pro-cessing. Instead, after applying this transformation, this attributewill just be an unused field in a record, which the staging compilerwill be able to optimize away (e.g. attribute L2 in Figure 9).

Such optimization opportunities, which are provided for free byLMS, have to be manually encoded with existing query compilers.We argue that this is a benefit of actually using a compiler, insteadof mimicking what a compiler would do inside the query engine.

3.4 Other Compiler OptimizationsThere are several other optimizations that can be expressed with

our compiler framework in order to further boost the performanceof LegoBase. These include loop-fusion, automatic index introduc-tion, automatic parallelization and vectorization. We leave theseoptimizations as future work. However, preliminary results showthat these optimizations can be easily expressed in LegoBase andcan significantly improve performance as expected. In general, webelieve that exploration of even query-specific optimizations is cer-tainly feasible, given the easy extensibility of our framework.

4. EVALUATIONOur experimental platform consists of a server-type x86 machine

equipped with two Intel Xeon E5-2620 v2 CPUs running at 2GHzeach, 256GB of DDR3 RAM at 1600Mhz and two commodity harddisks of 2TB storing the experimental datasets. The operating sys-tem is Red Hat Enterprise 6.5. For compiling the generated pro-grams throughout our evaluation we use version 2.10.3 of the Scalacompiler and version 2.9 of the CLang front-end for LLVM [13],with the default optimization flags for both compilers. For the

val a1 = a.L1val a2 = a.L2val e1 = a1(i)val e2 = a2(i)val r =record(L1->e1,

L2->e2)r.L1

7!

val a1 = a.L1val a2 = a.L2val e1 = a1(i)val e2 = a2(i)val r =record(L1->e1,

L2->e2)e1

7!val a1 = a.L1val e1 = a1(i)e1

Figure 9: Dead code elimination (DCE) can remove intermediatematerializations, e.g. row reconstructions when using a column lay-out. Here a is an array of records and i is an integer. The recordshave two attributes L1 and L2.

Scala programs, we configure the Java Virtual Machine to run with192GB of heap space. Finally, for C data-structures we use theGLib library (version 2.38.2).

For our evaluation we use the TPC-H benchmark [28]. TPC-His a data-warehousing and decision support benchmark that issuesbusiness analytics queries to a database with sales information.This benchmark suite includes 22 queries with a high degree ofcomplexity that express most SQL features. We execute each queryfive times and report the average performance of these runs. As areference point for all results presented in this section, we use acommercial, in-memory, row-store database system called DBX,which does not employ compilation. We assign 192GB of DRAMas memory space in DBX and we use the DBX-specific data typesinstead of generic SQL types. For all experiments, we have dis-abled huge pages in the kernel, since this provided better resultsfor all tested systems and optimizations. As described in Section 2,LegoBase uses query plans from the DBX database.

Our evaluation is divided into three parts. First, we analyze theperformance of LegoBase. More specifically, we show that, by us-ing our compiler framework, we obtain a query engine that signif-icantly outperforms both DBX and the HyPer query compiler. Wealso give insights about the performance improvement each of ouroptimizations provides. Second, we analyze the amount of effortrequired when programming query engines in LegoBase and showthat, by programming in the abstract, we can derive a fully func-tional system in a relatively short amount of time and coding effort.Finally, we evaluate the compilation overheads of our approach toshow that it is practical for efficiently compiling query engines.

4.1 Optimizing Query PlansFirst, we show that low-level compilation frameworks, such as

LLVM, are not adequate for efficiently optimizing database sys-tems. To do so, we generate a traditional Volcano-style engine,which we then compile to a final C binary using LLVM. As shownin Figure 10, the achieved performance is very poor: the LegoBasequery engine system is significantly faster for all TPC-H queries.This is because frameworks like LLVM cannot automatically detectthe data-structure, data flow or operator optimizations that we sup-port in LegoBase: the scope of optimization is too coarse-grainedto be detected by a low-level compiler.

In addition, as shown in the same figure, compiling with LLVMdoes not always yield better results compared to using a traditionalcompiler like GCC5. We see that LLVM outperforms GCC for only11 out of 22 queries (by 14% on average) while, for the remainingones, the binary generated by GCC is faster by 10% in average. Ingeneral, the performance difference between the two compilers canbe significant (e.g. for Q15, there is a 26% difference). We alsoexperimented with manually specifying optimizations flags to the

5For this experiment, we use version 4.4.7 of the GCC compiler.

860

1 trait ArrayOpsExpOpt extends ArrayOpsExp {2 // Override the IR node constructors3 override def array_new[T:Manifest](n:Int) =4 manifest[T] match {5 case Record(attrs) =>6 // Create a new array for each attribute7 val arrays = for (tp<-attrs) yield array_new(n)(tp)8 // Pack everything in a new record9 record(attrs, arrays)

10 case _ => super.array_new(n)11 }12

13 override def array_update[T:Manifest](ar:Array[T],14 n:Int, v:T) =15 manifest[T] match {16 case Record(attrs) =>17 // Get columns and update each one18 val arrays = for (l <- attrs) yield field(ar, l)19 for ((a, l) <- arrays zip attrs)20 a(n) = field(v, l)21 case _ => super.array_update(ar, n, v)22 }23

24 override def array_apply[T:Manifest](ar:Array[T],25 n:Int) =26 manifest[T] match {27 case Record(attrs) =>28 val arrays = for (l <- attrs) yield field(ar, l)29 val elems = for (a <- arrays) yield a(n)30 // Perform record reconstruction31 record(attrs, elems)32 case _ => super.array_apply(ar, n)33 }34

35 // Fill remaining operations accordingly36 }

Figure 8: Changing the data layout (from row to column) expressedas an optimization. ArrayOpsExp is the compiler trait for handlingArrays that we overwrite. Scala manifests carry type information.

Figure 9. Similarly, if LMS can statically determine that some at-tribute is never used (e.g. by having all queries given in advance),then the row layout still has to skip this attribute during query pro-cessing. Instead, after applying this transformation, this attributewill just be an unused field in a record, which the staging compilerwill be able to optimize away (e.g. attribute L2 in Figure 9).

Such optimization opportunities, which are provided for free byLMS, have to be manually encoded with existing query compilers.We argue that this is a benefit of actually using a compiler, insteadof mimicking what a compiler would do inside the query engine.

3.4 Other Compiler OptimizationsThere are several other optimizations that can be expressed with

our compiler framework in order to further boost the performanceof LegoBase. These include loop-fusion, automatic index introduc-tion, automatic parallelization and vectorization. We leave theseoptimizations as future work. However, preliminary results showthat these optimizations can be easily expressed in LegoBase andcan significantly improve performance as expected. In general, webelieve that exploration of even query-specific optimizations is cer-tainly feasible, given the easy extensibility of our framework.

4. EVALUATIONOur experimental platform consists of a server-type x86 machine

equipped with two Intel Xeon E5-2620 v2 CPUs running at 2GHzeach, 256GB of DDR3 RAM at 1600Mhz and two commodity harddisks of 2TB storing the experimental datasets. The operating sys-tem is Red Hat Enterprise 6.5. For compiling the generated pro-grams throughout our evaluation we use version 2.10.3 of the Scalacompiler and version 2.9 of the CLang front-end for LLVM [13],with the default optimization flags for both compilers. For the

val a1 = a.L1val a2 = a.L2val e1 = a1(i)val e2 = a2(i)val r =record(L1->e1,

L2->e2)r.L1

7!

val a1 = a.L1val a2 = a.L2val e1 = a1(i)val e2 = a2(i)val r =record(L1->e1,

L2->e2)e1

7!val a1 = a.L1val e1 = a1(i)e1

Figure 9: Dead code elimination (DCE) can remove intermediatematerializations, e.g. row reconstructions when using a column lay-out. Here a is an array of records and i is an integer. The recordshave two attributes L1 and L2.

Scala programs, we configure the Java Virtual Machine to run with192GB of heap space. Finally, for C data-structures we use theGLib library (version 2.38.2).

For our evaluation we use the TPC-H benchmark [28]. TPC-His a data-warehousing and decision support benchmark that issuesbusiness analytics queries to a database with sales information.This benchmark suite includes 22 queries with a high degree ofcomplexity that express most SQL features. We execute each queryfive times and report the average performance of these runs. As areference point for all results presented in this section, we use acommercial, in-memory, row-store database system called DBX,which does not employ compilation. We assign 192GB of DRAMas memory space in DBX and we use the DBX-specific data typesinstead of generic SQL types. For all experiments, we have dis-abled huge pages in the kernel, since this provided better resultsfor all tested systems and optimizations. As described in Section 2,LegoBase uses query plans from the DBX database.

Our evaluation is divided into three parts. First, we analyze theperformance of LegoBase. More specifically, we show that, by us-ing our compiler framework, we obtain a query engine that signif-icantly outperforms both DBX and the HyPer query compiler. Wealso give insights about the performance improvement each of ouroptimizations provides. Second, we analyze the amount of effortrequired when programming query engines in LegoBase and showthat, by programming in the abstract, we can derive a fully func-tional system in a relatively short amount of time and coding effort.Finally, we evaluate the compilation overheads of our approach toshow that it is practical for efficiently compiling query engines.

4.1 Optimizing Query PlansFirst, we show that low-level compilation frameworks, such as

LLVM, are not adequate for efficiently optimizing database sys-tems. To do so, we generate a traditional Volcano-style engine,which we then compile to a final C binary using LLVM. As shownin Figure 10, the achieved performance is very poor: the LegoBasequery engine system is significantly faster for all TPC-H queries.This is because frameworks like LLVM cannot automatically detectthe data-structure, data flow or operator optimizations that we sup-port in LegoBase: the scope of optimization is too coarse-grainedto be detected by a low-level compiler.

In addition, as shown in the same figure, compiling with LLVMdoes not always yield better results compared to using a traditionalcompiler like GCC5. We see that LLVM outperforms GCC for only11 out of 22 queries (by 14% on average) while, for the remainingones, the binary generated by GCC is faster by 10% in average. Ingeneral, the performance difference between the two compilers canbe significant (e.g. for Q15, there is a 26% difference). We alsoexperimented with manually specifying optimizations flags to the

5For this experiment, we use version 4.4.7 of the GCC compiler.

860

Page 15: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Evaluation Setup• 2x Intel Xeon 2GHz, 256GB RAM, 2TB HDD

• Scala 2.10.3, Clang 2.9

• Evaluate against DBX (in-memory row-store) and HyPer

• All systems get 192 GB RAM

• Run TPCH

15

Page 16: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Optimizing Query Plans

• LegoBase Volcano-style query engine compiled to C

• Compare code compiled with GCC and LLVM against fully optimized LegoBase

• Not all that interesting … really a comparison of GCC and LLVM

16

2

4

8

16

32

64

128

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22

Slow

dow

n to

Leg

oBas

e

LLVMGCC

Figure 10: Performance of a Volcano-styleengine compiled with LLVM and GCC.

Spee

dup

to D

BX

TPCH Queries

Volcano-Style (LLVM)Volcano-Style (GCC)Compiler of HyPerCompiler of HyPer (sim.)LegoBase (Scala)LegoBase (C)

0.125

0.25

0.5

1

2

4

8

16

32

Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 Q12 Q13 Q14 Q15 Q16 Q17 Q18 Q19 Q20 Q21 Q22

Figure 11: Performance comparison of LegoBase (C and Scala programs) with the codegenerated by the query compiler of [15].

two compilers, but this turns out to be a very delicate and com-plicated task as developers can specify flags which actually makeperformance worse. We argue that it is instead more beneficial fordatabase developers to invest their effort in developing high-leveloptimizations, like those presented so far in this paper.

Second, we show that the limited optimization scope of existingquery compilers makes them miss significant optimization oppor-tunities. To do so, we use the compiler of the HyPer database [15]which employs LLVM, a push engine and operator inlining6. Wealso simulate this system in LegoBase by enabling the correspond-ing optimizations in our architecture7. The results are presentedin Figure 11. We see that, for both the simulated and actual Hy-Per compilers, performance is significantly improved by 2.15⇥ and2.44⇥ on average, respectively. In addition, for 10 out of 22 TPC-Hqueries, our simulation actually generates code that performs betterthan that of HyPer. This is because we inline not only the opera-tors’ interfaces but also all data-structures and utilities leading tofewer function calls and better cache locality8.

More importantly, this figure shows that by using the data layoutand data structures optimizations of LegoBase (which are not per-formed by the query compiler of HyPer), we can get an additional5.3⇥ speedup, for a total average 7.7⇥ performance improvementwith all optimizations enabled. This is a result of the improvedcache locality and branch prediction, as shown in Figure 13. Morespecifically, there is an improvement of 30% and 1.54⇥ on aver-age for the two metrics, respectively, between DBX and LegoBase.In addition, the maximum, average and minimum difference in thenumber of CPU instructions executed in HyPer is 2.98⇥, 1.54⇥,and 5% more, respectively compared to LegoBase. The data-stru-cture and column layout optimizations cannot be provided by ex-isting query compilers as they target pre-compiled DBMS compo-nents which exist outside their optimization scope. This shows that,by extending the optimization scope, LegoBase can outperform ex-isting compilation techniques for all TPC-H queries.

Finally, we prove that the abstraction without regret vision ne-cessitates our source-to-source compilation to C. To do so, we pre-sent performance results for the best Scala program; that is the pro-gram generated by applying all optimizations to the Scala output.

6We also experimented with another in-memory DBMS that com-piles SQL queries to native C++ code on-the-fly. However, we wereunable to configure the system so that it performs well compared tothe other systems. Thus, we omit its results from this section.7In its full generality, the transformation between a Volcano and apush engine is still under development. For the results presentedhere, we have implemented the push version directly since, in ourcase, the code of the push engine turns out to be significantly sim-pler and easier to understand than the Volcano code.8We note that the simulated and actual HyPer systems may usedifferent physical query plans and data-structures implementation.These are the main reasons for the different performance observedin Figure 11 between the two systems in some queries.

We observe that the performance of Scala cannot compete with thatof the optimized C code, and is on average 2.5⇥ slower. Profilinginformation gathered with the perf tool of Linux reveals the fol-lowing three reasons for the worse performance of Scala: (a) Thereare 30% to 1.4⇥ more branch mispredictions, (b) The percentage ofLLC misses is 10% to 1.8⇥ higher, and more importantly, (c) Scalaexecutes up to 5.5⇥ more CPU instructions9. Of course, these inef-ficiencies are to a great part due to the Java Virtual Machine and notspecific to Scala. Note that the optimized Scala program is compet-itive to DBX: for 18 out of 22 queries, Scala outperforms the com-mercial DBX system. This is because we remove all abstractionsthat incur significant overhead for Scala. For example, the perfor-mance of Q18, which builds a large hash map, is improved by 45⇥when applying our data-structure specializations.

4.1.1 Impact of Compiler OptimizationsFrom the results presented so far, we observe that our optimiza-

tions do not equally benefit the performance of all queries, howeverthey never result in negative performance impact. Here, we pro-vide additional information about the performance improvementexpected when applying one of our optimizations. These resultsare presented in Figure 12.

In general, the impact of an optimization depends on the char-acteristics of a query. For the data-structure specialization (Fig-ure 12a), the improvement is proportional to the amount of data-structure operations performed. We observe that the hash map ab-straction performs respectably for few operations. However, aswe increase the amount of data that are inserted into these maps,their performance significantly drops and, thus, our specializationgives significant performance benefits. For the column layout opti-mization (Figure 12b), the improvement is proportional to the per-centage of attributes in the input relations that are actually used.TPC-H queries reference 24% - 68% and, for this range, the opti-mization gives a 2.5⇥ to 5% improvement, which degrades as moreattributes are referenced. This is expected as the benefits of the col-umn layout are evident when this layout can “skip” a number ofunused attributes, thus significantly reducing cache misses. Syn-thetic queries on TPC-H data referencing 100% of the attributesshow that, in this case, the column layout actually yields no ben-efit, and it is slightly worse than the row layout. This figure alsoshows that the performance improvement of both optimizations isnot directly dependent on the number of operators, as queries withthe same number of operators can exhibit completely different be-haviour regarding data-structure and attributes references.

For the inlining optimization (Figure 12c) we observe that, whenall operators are considered, inlining does not improve performanceas we move from three to seven operators. This is because the im-provement obtained from inlining depends on which operators are

9These results were confirmed with Intel’s VTune profiler.

861

Page 17: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

TPCH Query Optimization

• Simulated HyPer is faster than HyPer • Due to data-structure specialization

• LegoBase is 5.3x-7.7x faster than HyPer • Due to data-structure specialization, data layout optimization • Better cache locality, branch prediction, fewer instructions executed

• LegoBase Scala 2.5x slower than LegoBase C • 1.3x-1.4x more branch mispredictions • 1.1x-1.8x more LLC misses • 5.5x more CPU instructions

17

2

4

8

16

32

64

128

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22

Slow

dow

n to

Leg

oBas

e

LLVMGCC

Figure 10: Performance of a Volcano-styleengine compiled with LLVM and GCC.

Spee

dup

to D

BX

TPCH Queries

Volcano-Style (LLVM)Volcano-Style (GCC)Compiler of HyPerCompiler of HyPer (sim.)LegoBase (Scala)LegoBase (C)

0.125

0.25

0.5

1

2

4

8

16

32

Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 Q12 Q13 Q14 Q15 Q16 Q17 Q18 Q19 Q20 Q21 Q22

Figure 11: Performance comparison of LegoBase (C and Scala programs) with the codegenerated by the query compiler of [15].

two compilers, but this turns out to be a very delicate and com-plicated task as developers can specify flags which actually makeperformance worse. We argue that it is instead more beneficial fordatabase developers to invest their effort in developing high-leveloptimizations, like those presented so far in this paper.

Second, we show that the limited optimization scope of existingquery compilers makes them miss significant optimization oppor-tunities. To do so, we use the compiler of the HyPer database [15]which employs LLVM, a push engine and operator inlining6. Wealso simulate this system in LegoBase by enabling the correspond-ing optimizations in our architecture7. The results are presentedin Figure 11. We see that, for both the simulated and actual Hy-Per compilers, performance is significantly improved by 2.15⇥ and2.44⇥ on average, respectively. In addition, for 10 out of 22 TPC-Hqueries, our simulation actually generates code that performs betterthan that of HyPer. This is because we inline not only the opera-tors’ interfaces but also all data-structures and utilities leading tofewer function calls and better cache locality8.

More importantly, this figure shows that by using the data layoutand data structures optimizations of LegoBase (which are not per-formed by the query compiler of HyPer), we can get an additional5.3⇥ speedup, for a total average 7.7⇥ performance improvementwith all optimizations enabled. This is a result of the improvedcache locality and branch prediction, as shown in Figure 13. Morespecifically, there is an improvement of 30% and 1.54⇥ on aver-age for the two metrics, respectively, between DBX and LegoBase.In addition, the maximum, average and minimum difference in thenumber of CPU instructions executed in HyPer is 2.98⇥, 1.54⇥,and 5% more, respectively compared to LegoBase. The data-stru-cture and column layout optimizations cannot be provided by ex-isting query compilers as they target pre-compiled DBMS compo-nents which exist outside their optimization scope. This shows that,by extending the optimization scope, LegoBase can outperform ex-isting compilation techniques for all TPC-H queries.

Finally, we prove that the abstraction without regret vision ne-cessitates our source-to-source compilation to C. To do so, we pre-sent performance results for the best Scala program; that is the pro-gram generated by applying all optimizations to the Scala output.

6We also experimented with another in-memory DBMS that com-piles SQL queries to native C++ code on-the-fly. However, we wereunable to configure the system so that it performs well compared tothe other systems. Thus, we omit its results from this section.7In its full generality, the transformation between a Volcano and apush engine is still under development. For the results presentedhere, we have implemented the push version directly since, in ourcase, the code of the push engine turns out to be significantly sim-pler and easier to understand than the Volcano code.8We note that the simulated and actual HyPer systems may usedifferent physical query plans and data-structures implementation.These are the main reasons for the different performance observedin Figure 11 between the two systems in some queries.

We observe that the performance of Scala cannot compete with thatof the optimized C code, and is on average 2.5⇥ slower. Profilinginformation gathered with the perf tool of Linux reveals the fol-lowing three reasons for the worse performance of Scala: (a) Thereare 30% to 1.4⇥ more branch mispredictions, (b) The percentage ofLLC misses is 10% to 1.8⇥ higher, and more importantly, (c) Scalaexecutes up to 5.5⇥ more CPU instructions9. Of course, these inef-ficiencies are to a great part due to the Java Virtual Machine and notspecific to Scala. Note that the optimized Scala program is compet-itive to DBX: for 18 out of 22 queries, Scala outperforms the com-mercial DBX system. This is because we remove all abstractionsthat incur significant overhead for Scala. For example, the perfor-mance of Q18, which builds a large hash map, is improved by 45⇥when applying our data-structure specializations.

4.1.1 Impact of Compiler OptimizationsFrom the results presented so far, we observe that our optimiza-

tions do not equally benefit the performance of all queries, howeverthey never result in negative performance impact. Here, we pro-vide additional information about the performance improvementexpected when applying one of our optimizations. These resultsare presented in Figure 12.

In general, the impact of an optimization depends on the char-acteristics of a query. For the data-structure specialization (Fig-ure 12a), the improvement is proportional to the amount of data-structure operations performed. We observe that the hash map ab-straction performs respectably for few operations. However, aswe increase the amount of data that are inserted into these maps,their performance significantly drops and, thus, our specializationgives significant performance benefits. For the column layout opti-mization (Figure 12b), the improvement is proportional to the per-centage of attributes in the input relations that are actually used.TPC-H queries reference 24% - 68% and, for this range, the opti-mization gives a 2.5⇥ to 5% improvement, which degrades as moreattributes are referenced. This is expected as the benefits of the col-umn layout are evident when this layout can “skip” a number ofunused attributes, thus significantly reducing cache misses. Syn-thetic queries on TPC-H data referencing 100% of the attributesshow that, in this case, the column layout actually yields no ben-efit, and it is slightly worse than the row layout. This figure alsoshows that the performance improvement of both optimizations isnot directly dependent on the number of operators, as queries withthe same number of operators can exhibit completely different be-haviour regarding data-structure and attributes references.

For the inlining optimization (Figure 12c) we observe that, whenall operators are considered, inlining does not improve performanceas we move from three to seven operators. This is because the im-provement obtained from inlining depends on which operators are

9These results were confirmed with Intel’s VTune profiler.

861

Page 18: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Impact of Compiler Optimizations

18

1

2

4

8

16

0.125 0.5 2 8 32 128

Spee

dup

Number of DS operations (millions)

2 4 6 8

10 12

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(a) Data Structure Opt.

0 0.5

1 1.5

2 2.5

3 3.5

10 20 30 40 50 60 70 80 90 100

Spee

dupPercentage of attributes used

TPC-H queries

1

1.5

2

2.5

3

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operatorsTPC-H queries

(b) Change Data Layout

1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Spee

dup

Number of joins

1 2 3 4 5 6 7

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(c) Operator Inlining

1

1.5

2

2.5

3

3.5

Q1

Q6

Q4

Q12

Q13

Q14

Q15

Q17

Q19 Q3

Q22

Q16

Q18

Q10

Q11 Q2

Q20 Q5

Q7

Q9

Q21 Q8

Spee

dup

More operators

(d) Push Engine Opt.

Figure 12: Impact of different optimizations on query execution time. The baseline is a Volcano-style engine.

30 40 50 60 70 80 90

100

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22

Cac

he M

isse

s

0 0.5

1 1.5

2 2.5

3

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22Br

anch

Mis

pred

. DBX Hyper LegoBase

Figure 13: Percentage of cache misses and branch mispredictionsfor DBX, HyPer and LegoBase for all 22 TPC-H queries.

being inlined. We observe that if we consider inlining only join op-erators then the performance improves almost linearly as the num-ber of join operators in a query plan increases. This is an impor-tant observation, as for very large queries, our system may have tochoose which operators to inline (e.g. to avoid the code not fittingin the instruction cache). If that is the case, this experiment showsthat the compiler framework should merit inlining joins instead ofsimpler operators (e.g. scans or aggregations).

Finally, the performance improvement gained by the pull to pushoptimization (Figure 12d) depends on the complexity of the execu-tion path of a query. This is a hard metric to visualize, as the im-provement depends not only on how many operators are used, butalso on their type, their position in the overall query plan and howmuch each of them affects branch prediction and cache locality. Forinstance, queries Q5 to Q21 in the figure have the same number ofoperators, but the performance improvement gained varies signifi-cantly. At the same time Q13 has half the number of operators, butthis optimization helps more: the push engine significantly simpli-fies the complex execution paths of the Left Outer Join operatorused by this query. A similar observation about the complexity ofexecution paths holds for Q2 as well.

4.2 Productivity EvaluationAn important point of this paper is that the performance of query

engines can be improved without much programming effort. Next,we present the productivity/performance evaluation of our system,which is summarized in Table 1.

We observe two things. First, by programming at a high-levelwe can provide a fully functional system within a small amountof time and lines of code required. For LegoBase, the majority ofthis effort was invested in extending the LMS compiler so that itgenerates C code (LMS by default outputs Scala). As a result ofthe reduced code size, we spent less time on debugging the system,thus focusing on developing new useful optimizations. Develop-ment of LegoBase required, including debugging time, four monthsfor only one programmer. Second, each optimization requires only

Coding Effort Scala LOC Average Speedup

Operator Inlining – 0 2.07⇥Push Engine Opt. 1 Week ⇠400 [6] 2.26⇥Data Structure Opt. 4 Days 259 2.16⇥Change Data Layout 3 Days 102 1.81⇥Other Misc. Opt. 3 Days 124 –10

LegoBase Operators 1 Month 428 –LMS Modifications 2 Months 3953 –Various Utilities 1 Week 538 –

Total ⇠4 Months 5831 7.7⇥

Table 1: Programming effort required for each LegoBase compo-nent along with the average speedup obtained from using it.

a few hundred lines of high-level code to provide significant per-formance improvements. More specifically, for ⇠900 LOC Lego-Base is improved by 7.7⇥, as we described in the previous section.Source-to-source compilation is critical to achieving this behaviour,as the combined size of the operators and optimizations of Lego-Base is 40 times less than the code size for all 22 TPC-H querieswritten in C. Finally, in contrast to low-level query compilers whichmust themselves provide operator inlining, LMS provides this op-timization for free. We believe these properties prove the produc-tivity merit of the abstraction without regret vision.

4.3 Compilation OverheadsFinally, we analyze the compilation time for the C programs

of all 22 TPC-H queries. Our results are presented in Figure 14,where the y-axis corresponds to the time to (a) optimize an incom-ing query in our system and generate the C code, and, (b) the timeCLang requires before producing the final C executable.

We see that, in general, all TPC-H queries require less than 2.5seconds to compile. We argue that this is an acceptable compilationoverhead, especially for analytical queries like those in TPC-H thatare typically known in advance and which process huge amountsof data. In this case, a compilation overhead of some seconds isnegligible compared to the total execution time. This result provesthat our approach can be used in practice for quickly compilingquery engines. In addition, the optimization time is, as expected,proportional to the number of joins in its physical query plan. Thisis because our compiler must optimize more data-structures andoperators as the number of joins increases11.

Finally, we note that if we generate Scala code instead of C, thencompiling the final optimized Scala programs requires 7.2⇥ moretime on average. To some extent this is expected as calling the Scala

10The improvement of these optimizations is counted among theother optimizations.

11One exception to this rule is Q11. This query uses the WindowOperator which is expensive to optimize in our implementation.

862

1

2

4

8

16

0.125 0.5 2 8 32 128

Spee

dup

Number of DS operations (millions)

2 4 6 8

10 12

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(a) Data Structure Opt.

0 0.5

1 1.5

2 2.5

3 3.5

10 20 30 40 50 60 70 80 90 100

Spee

dup

Percentage of attributes used

TPC-H queries

1

1.5

2

2.5

3

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operatorsTPC-H queries

(b) Change Data Layout

1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Spee

dup

Number of joins

1 2 3 4 5 6 7

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(c) Operator Inlining

1

1.5

2

2.5

3

3.5

Q1

Q6

Q4

Q12

Q13

Q14

Q15

Q17

Q19 Q3

Q22

Q16

Q18

Q10

Q11 Q2

Q20 Q5

Q7

Q9

Q21 Q8

Spee

dup

More operators

(d) Push Engine Opt.

Figure 12: Impact of different optimizations on query execution time. The baseline is a Volcano-style engine.

30 40 50 60 70 80 90

100

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22

Cac

he M

isse

s

0 0.5

1 1.5

2 2.5

3

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22Br

anch

Mis

pred

. DBX Hyper LegoBase

Figure 13: Percentage of cache misses and branch mispredictionsfor DBX, HyPer and LegoBase for all 22 TPC-H queries.

being inlined. We observe that if we consider inlining only join op-erators then the performance improves almost linearly as the num-ber of join operators in a query plan increases. This is an impor-tant observation, as for very large queries, our system may have tochoose which operators to inline (e.g. to avoid the code not fittingin the instruction cache). If that is the case, this experiment showsthat the compiler framework should merit inlining joins instead ofsimpler operators (e.g. scans or aggregations).

Finally, the performance improvement gained by the pull to pushoptimization (Figure 12d) depends on the complexity of the execu-tion path of a query. This is a hard metric to visualize, as the im-provement depends not only on how many operators are used, butalso on their type, their position in the overall query plan and howmuch each of them affects branch prediction and cache locality. Forinstance, queries Q5 to Q21 in the figure have the same number ofoperators, but the performance improvement gained varies signifi-cantly. At the same time Q13 has half the number of operators, butthis optimization helps more: the push engine significantly simpli-fies the complex execution paths of the Left Outer Join operatorused by this query. A similar observation about the complexity ofexecution paths holds for Q2 as well.

4.2 Productivity EvaluationAn important point of this paper is that the performance of query

engines can be improved without much programming effort. Next,we present the productivity/performance evaluation of our system,which is summarized in Table 1.

We observe two things. First, by programming at a high-levelwe can provide a fully functional system within a small amountof time and lines of code required. For LegoBase, the majority ofthis effort was invested in extending the LMS compiler so that itgenerates C code (LMS by default outputs Scala). As a result ofthe reduced code size, we spent less time on debugging the system,thus focusing on developing new useful optimizations. Develop-ment of LegoBase required, including debugging time, four monthsfor only one programmer. Second, each optimization requires only

Coding Effort Scala LOC Average Speedup

Operator Inlining – 0 2.07⇥Push Engine Opt. 1 Week ⇠400 [6] 2.26⇥Data Structure Opt. 4 Days 259 2.16⇥Change Data Layout 3 Days 102 1.81⇥Other Misc. Opt. 3 Days 124 –10

LegoBase Operators 1 Month 428 –LMS Modifications 2 Months 3953 –Various Utilities 1 Week 538 –

Total ⇠4 Months 5831 7.7⇥

Table 1: Programming effort required for each LegoBase compo-nent along with the average speedup obtained from using it.

a few hundred lines of high-level code to provide significant per-formance improvements. More specifically, for ⇠900 LOC Lego-Base is improved by 7.7⇥, as we described in the previous section.Source-to-source compilation is critical to achieving this behaviour,as the combined size of the operators and optimizations of Lego-Base is 40 times less than the code size for all 22 TPC-H querieswritten in C. Finally, in contrast to low-level query compilers whichmust themselves provide operator inlining, LMS provides this op-timization for free. We believe these properties prove the produc-tivity merit of the abstraction without regret vision.

4.3 Compilation OverheadsFinally, we analyze the compilation time for the C programs

of all 22 TPC-H queries. Our results are presented in Figure 14,where the y-axis corresponds to the time to (a) optimize an incom-ing query in our system and generate the C code, and, (b) the timeCLang requires before producing the final C executable.

We see that, in general, all TPC-H queries require less than 2.5seconds to compile. We argue that this is an acceptable compilationoverhead, especially for analytical queries like those in TPC-H thatare typically known in advance and which process huge amountsof data. In this case, a compilation overhead of some seconds isnegligible compared to the total execution time. This result provesthat our approach can be used in practice for quickly compilingquery engines. In addition, the optimization time is, as expected,proportional to the number of joins in its physical query plan. Thisis because our compiler must optimize more data-structures andoperators as the number of joins increases11.

Finally, we note that if we generate Scala code instead of C, thencompiling the final optimized Scala programs requires 7.2⇥ moretime on average. To some extent this is expected as calling the Scala

10The improvement of these optimizations is counted among theother optimizations.

11One exception to this rule is Q11. This query uses the WindowOperator which is expensive to optimize in our implementation.

862

Page 19: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Productivity

• Optimizations all done in a high-level language • Easier to program, fewer lines of code • High speedup-per-line-of-code

19

1

2

4

8

16

0.125 0.5 2 8 32 128

Spee

dup

Number of DS operations (millions)

2 4 6 8

10 12

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(a) Data Structure Opt.

0 0.5

1 1.5

2 2.5

3 3.5

10 20 30 40 50 60 70 80 90 100

Spee

dup

Percentage of attributes used

TPC-H queries

1

1.5

2

2.5

3

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operatorsTPC-H queries

(b) Change Data Layout

1 2 3 4 5 6 7

0 1 2 3 4 5 6 7

Spee

dup

Number of joins

1 2 3 4 5 6 7

2 4 6 8 10 12 14 16 18

Spee

dup

Number of operators

(c) Operator Inlining

1

1.5

2

2.5

3

3.5

Q1

Q6

Q4

Q12

Q13

Q14

Q15

Q17

Q19 Q3

Q22

Q16

Q18

Q10

Q11 Q2

Q20 Q5

Q7

Q9

Q21 Q8

Spee

dup

More operators

(d) Push Engine Opt.

Figure 12: Impact of different optimizations on query execution time. The baseline is a Volcano-style engine.

30 40 50 60 70 80 90

100

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22

Cac

he M

isse

s

0 0.5

1 1.5

2 2.5

3

Q1

Q2

Q3

Q4

Q5

Q6

Q7

Q8

Q9

Q10

Q11

Q12

Q13

Q14

Q15

Q16

Q17

Q18

Q19

Q20

Q21

Q22Br

anch

Mis

pred

. DBX Hyper LegoBase

Figure 13: Percentage of cache misses and branch mispredictionsfor DBX, HyPer and LegoBase for all 22 TPC-H queries.

being inlined. We observe that if we consider inlining only join op-erators then the performance improves almost linearly as the num-ber of join operators in a query plan increases. This is an impor-tant observation, as for very large queries, our system may have tochoose which operators to inline (e.g. to avoid the code not fittingin the instruction cache). If that is the case, this experiment showsthat the compiler framework should merit inlining joins instead ofsimpler operators (e.g. scans or aggregations).

Finally, the performance improvement gained by the pull to pushoptimization (Figure 12d) depends on the complexity of the execu-tion path of a query. This is a hard metric to visualize, as the im-provement depends not only on how many operators are used, butalso on their type, their position in the overall query plan and howmuch each of them affects branch prediction and cache locality. Forinstance, queries Q5 to Q21 in the figure have the same number ofoperators, but the performance improvement gained varies signifi-cantly. At the same time Q13 has half the number of operators, butthis optimization helps more: the push engine significantly simpli-fies the complex execution paths of the Left Outer Join operatorused by this query. A similar observation about the complexity ofexecution paths holds for Q2 as well.

4.2 Productivity EvaluationAn important point of this paper is that the performance of query

engines can be improved without much programming effort. Next,we present the productivity/performance evaluation of our system,which is summarized in Table 1.

We observe two things. First, by programming at a high-levelwe can provide a fully functional system within a small amountof time and lines of code required. For LegoBase, the majority ofthis effort was invested in extending the LMS compiler so that itgenerates C code (LMS by default outputs Scala). As a result ofthe reduced code size, we spent less time on debugging the system,thus focusing on developing new useful optimizations. Develop-ment of LegoBase required, including debugging time, four monthsfor only one programmer. Second, each optimization requires only

Coding Effort Scala LOC Average Speedup

Operator Inlining – 0 2.07⇥Push Engine Opt. 1 Week ⇠400 [6] 2.26⇥Data Structure Opt. 4 Days 259 2.16⇥Change Data Layout 3 Days 102 1.81⇥Other Misc. Opt. 3 Days 124 –10

LegoBase Operators 1 Month 428 –LMS Modifications 2 Months 3953 –Various Utilities 1 Week 538 –

Total ⇠4 Months 5831 7.7⇥

Table 1: Programming effort required for each LegoBase compo-nent along with the average speedup obtained from using it.

a few hundred lines of high-level code to provide significant per-formance improvements. More specifically, for ⇠900 LOC Lego-Base is improved by 7.7⇥, as we described in the previous section.Source-to-source compilation is critical to achieving this behaviour,as the combined size of the operators and optimizations of Lego-Base is 40 times less than the code size for all 22 TPC-H querieswritten in C. Finally, in contrast to low-level query compilers whichmust themselves provide operator inlining, LMS provides this op-timization for free. We believe these properties prove the produc-tivity merit of the abstraction without regret vision.

4.3 Compilation OverheadsFinally, we analyze the compilation time for the C programs

of all 22 TPC-H queries. Our results are presented in Figure 14,where the y-axis corresponds to the time to (a) optimize an incom-ing query in our system and generate the C code, and, (b) the timeCLang requires before producing the final C executable.

We see that, in general, all TPC-H queries require less than 2.5seconds to compile. We argue that this is an acceptable compilationoverhead, especially for analytical queries like those in TPC-H thatare typically known in advance and which process huge amountsof data. In this case, a compilation overhead of some seconds isnegligible compared to the total execution time. This result provesthat our approach can be used in practice for quickly compilingquery engines. In addition, the optimization time is, as expected,proportional to the number of joins in its physical query plan. Thisis because our compiler must optimize more data-structures andoperators as the number of joins increases11.

Finally, we note that if we generate Scala code instead of C, thencompiling the final optimized Scala programs requires 7.2⇥ moretime on average. To some extent this is expected as calling the Scala

10The improvement of these optimizations is counted among theother optimizations.

11One exception to this rule is Q11. This query uses the WindowOperator which is expensive to optimize in our implementation.

862

Page 20: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Compilation Overhead

• Compilation time ~ 2.5 seconds

20

0

0.5

1

1.5

2

2.5

Q8

Q5

Q7

Q9

Q21 Q

2Q

20Q

10 Q3

Q11

Q16

Q18 Q

4Q

12Q

13Q

14Q

15Q

17Q

19Q

22 Q1

Q6

LMS

+ CL

ang

[sec

onds

]

LMS OptimizationCLang C Program Compilation

7

5 5 5 54 4

3

2

2

2 2

1 1 11 1 1 1 1

0 0

Figure 14: Compilation time for all C programs of TPC-H. Queriesare sorted according to the number of join operators in them.

compiler is a heavyweight process: for every query compiled thereis significant startup overhead for loading the necessary Scala andJava libraries. In addition, Scala has to perform additional transfor-mations in order to convert a Scala program to Java bytecode. Byjust optimizing a Scala program in the form of an AST, our two-level architecture allows us to avoid these overheads, providing amuch more lightweight compilation process.

5. RELATED WORKWe outline related work in three areas: (a) Previous query com-

pilers, (b) Frameworks for applying intra-operator optimizationsand, finally, (c) Orthogonal techniques to speed-up query process-ing. We briefly discuss these areas below.

Previous Compilation Frameworks. Historically, System R [2]first proposed code generation for query optimization. However,the Volcano iterator model eventually dominated over compilation,since code generation was very expensive to maintain. The Day-tona [5] system revisited compilation in the late nineties, howeverit heavily relied on the operating system for functionality that istraditionally provided by the DBMS itself, like buffering.

The shift towards pure in-memory computation in databases, evi-dent in the space of data analytics and transaction processing12, haslead developers to revisit compilation. The reason is that, as moreand more data is put in memory, query performance is increasinglydetermined by the effective throughput of the CPU. In this context,compilation strategies aim to remove unnecessary CPU overhead.

Rao et al. propose to remove the overhead of virtual functions inthe Volcano iterator model by using a compiled execution enginebuilt on top of the Java Virtual Machine (JVM) [20]. Krikellas etal. take a step further and completely eliminate the Volcano iteratormodel in the generated code [12]. They do so by translating thealgebraic representation to C++ code using templates in the HIQUEsystem. In addition, Zane et al. have shown how compilation canalso be used to additionally improve operator internals [29].

The HyPer database system also uses query compilation, as de-scribed in [15]. This work targets minimizing the CPU overheadof the Volcano operator model while maintaining low compila-tion times. The authors use a mixed LLVM/C++ execution enginewhere the algebraic representation of the operators is first translatedto low-level LLVM code, while the complex part of the database(e.g. management of data-structures and memory allocation) is stillpre-compiled C++ code called periodically from the LLVM codewhenever needed. Two basic optimizations are presented: operatorinlining and reversing the data flow (to a push engine).

All these works aim to improve database systems by removingunnecessary abstraction overheads. However, these template-based

12Examples of systems in the area since mid-2000s include SAPHANA [3], VoltDB [9, 26] and Oracle’s TimesTen [17].

approaches require writing low-level code which is hard to main-tain and extend. This fact significantly limits their applicability.Furthermore, their static nature makes them miss significant op-timization opportunities that can only be detected by taking intoaccount runtime information. In contrast, our approach advocates anew methodology for programming query engines where the queryengine and its optimizations are written in a high-level language.This provides a programmer-friendly way to express optimizationsand allows extending the scope of optimization to cover the wholequery engine. In addition, our staging compiler is used to con-tinuously optimize our system at runtime. Finally, in contrast toprevious work, we separate the optimization and code generationphases. Even though [15] argues that optimizations should happencompletely before code generation (e.g. in the algebraic represen-tation), there exist many optimization opportunities that occur onlyafter one considers the complete generated code, e.g. after operatorinlining. Our compiler can detect such optimizations, thus provid-ing additional performance improvement over existing techniques.

Intra-operator optimizations. There has recently been extensivework on how to specialize the code of query operators in a sys-tematic way by using an approach called Micro-Specialization [31,30, 32]. In this line of work, the authors propose a framework toencode DBMS-specific intra-operator optimizations, like unrollingloops and removing if conditions, as pre-compiled templates inan extensible way. All these optimizations are performed by de-fault by the LMS compiler in LegoBase. However, in contrast toour work, there are two main limitations in Micro-Specialization.First, the low-level nature of the approach makes the developmentprocess very time-consuming: it can take days to code a singleintra-operator optimization [30]. Such optimizations are very fine-grained, and it should be possible to implement them quickly: forthe same amount of time we are able to provide much more coarse-grained optimizations in LegoBase. Second, the optimizations arelimited to those that can be statically determined by examining theDBMS code and cannot be changed at runtime. Our architecturemaintains all the benefits of Micro-Specialization, while it is notaffected by the aforementioned two limitations.

Techniques to speed up query processing. Finally, there are manyworks that aim to speed-up query processing in general, by focus-ing mostly on improving the way data are processed, rather thanindividual operators. Examples of such work include block-wiseprocessing [18], vectorized execution [23], compression techniquesto provide constant-time query processing [19] or combination ofthe above along with a column-oriented data layout [14]. We be-lieve all these approaches are orthogonal to this work, since ourframework aims to provide a high-level framework for encodingall such optimizations in a user friendly way (e.g. we present thetransition from row to column data layout in Section 3.3).

6. CONCLUSIONSLegoBase is a new analytical database system currently under

development at EPFL. In this paper, we presented the current pro-totype of the query execution subsystem of LegoBase. Our sys-tem allows programmers to develop high-level abstractions with-out having to pay an abstraction penalty. To achieve this visionof abstraction without regret, LegoBase performs source-to-sourcecompilation of the high-level Scala code to very efficient low-levelC code. In addition, it uses state-of-the-art compiler technologyin the form of an extensible staging compiler implemented as a li-brary in which optimizations can be expressed naturally at a highlevel. Our approach admits a productivity/efficiency combination

863

Page 21: Towards Scalable Real-time Analytics: Building Efficient ...€¦ · Query Compilers Existing DBMSes DBMS in High-Level Language Performance ... posable [16], thus putting an end

Conclusions• Possible to build query engine in high-level language with

performance of hand-written low-level C

• Use LMS to transform naive query engine to IR • Optimize IR in independent stages • Specialize types, change data layouts at runtime • Emit optimize C code

• Performance beats existing main-memory DBMS and modern query compiler HyPer

21


Recommended