<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Databricks Engineering - Medium]]></title>
        <description><![CDATA[Technical gems from Databricks’ Engineering Team - Medium]]></description>
        <link>https://medium.com/databricks-engineering?source=rss----a94c8b85dab4---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Databricks Engineering - Medium</title>
            <link>https://medium.com/databricks-engineering?source=rss----a94c8b85dab4---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 10 Nov 2026 05:49:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/databricks-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Introducing Command Line Interface for Databricks Developers]]></title>
            <link>https://medium.com/databricks-engineering/introducing-command-line-interface-for-databricks-developers-659b29af2560?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/659b29af2560</guid>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[cli]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Jules S. Damji]]></dc:creator>
            <pubDate>Mon, 13 Nov 2017 23:21:17 GMT</pubDate>
            <atom:updated>2017-11-13T23:48:31.384Z</atom:updated>
            <content:encoded><![CDATA[<p>Original Author: Andrew Chen</p><p>As part of <a href="https://databricks.com/product/unified-analytics-platform">Unified Analytics Platform</a>, <a href="https://docs.databricks.com/user-guide/workspace.html">Databricks Workspace</a> along with <a href="https://docs.databricks.com/user-guide/dbfs-databricks-file-system.html">Databricks File System</a> (DBFS) are critical components that facilitate collaboration among data scientists and data engineers: Databricks Workspace manages users’ notebooks, whereas DBFS manages files; both have REST API endpoints to manage notebooks and files respectively.</p><p>While the REST APIs are principally designed for general programmatic use, ad-hoc tasks of exploring and manipulating desired content is not that easy with REST APIs without writing repetitive boilerplate code. So to simplify this task for Databricks developers, we have implemented an easy command line interface that interacts with Databricks workspaces and filesystem APIs.</p><p>In part 1 of our blog aimed at Databricks developers, we outline some use cases where you can employ a command line interface to interact with Databricks workspaces and manipulate files and folders.</p><h3>Usage Scenarios</h3><h3>Checking in notebooks to version control systems (VCS)</h3><p>One of the most common usages of the Databricks CLI is to enable an alternative integration point to VCS.</p><p>To check notebooks into VCS, you need a canonical text-based file for the notebook. With the workspace APIs, you can export and import notebooks to this canonical text-based file format. The Databricks CLI builds on this idea further by wrapping these APIs into an easy to use command line interface with support for recursive import and export.</p><p>For example, consider a scenario with two users’ workspace and a production workspace: Alice with workspace <strong>A</strong>, Bob with workspace <strong>B</strong>, and a production workspace <em>P</em> with notebooks that are run through <a href="https://docs.databricks.com/user-guide/jobs.html">Databricks Job Scheduler</a>. After developing code in her workspace <strong>A</strong>, Alice may export her code with databricks workspace export_dir to her git repository and initiate a pull request. Bob can then review and approve the PR, after which Alice can merge her changes to the master. This merge will trigger a Continuous Delivery job in which production workspace <strong>P</strong> will initiate a databricks workspace import_dir, bringing all new changes into production.</p><h3>Copying small datasets to DBFS</h3><p>Another use case for the CLI is importing small datasets to DBFS.</p><p>For example:</p><ul><li>(Recursively) copying datasets/files between local file system and DBFS</li></ul><h3>Installation</h3><p>To begin, install the CLI by running the following command on your local machine.<br> pip install --upgrade databricks-cli</p><p>Note that the Databricks CLI currently cannot run with <strong>Python 3</strong>.</p><p>After installation is complete, the next step is to provide authentication information to the CLI. The first and recommended way is to use an <a href="https://docs.databricks.com/api/latest/authentication.html#token-management">access token generated from Databricks</a>. To do this run databricks configure --token. A second way is to use your username and password pair. To do this run databricks configure and follow the prompts. After following the prompts, your access credentials will be stored in the file ~/.databrickscfg.</p><h3>Workspace CLI examples</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/474/0*UOd6Umn9H1DE2xYi.png" /></figure><p>The implemented commands for the Workspace CLI can be listed by running databricks workspace -h. Commands are run by appending them to databricks workspace. To make it easier to use the workspace CLI, feel free to alias databricks workspace to something shorter. For more information reference <a href="https://docs.databricks.com/user-guide/databricks-cli.html#aliasing-command-groups">Aliasing Command Groups and Workspace API</a>.</p><pre>$ databricks workspace -h Usage: databricks workspace [OPTIONS] COMMAND [ARGS]... Utility to interact with the Databricks Workspace. Workspace paths must be absolute and be prefixed with `/`. Options: -v, --version -h, --help Show this message and exit. Commands: delete Deletes objects from the Databricks... export Exports a file from the Databricks workspace... export_dir Recursively exports a directory from the... import Imports a file from local to the Databricks... import_dir Recursively imports a directory from local to... list List objects in the Databricks Workspace ls List objects in the Databricks Workspace mkdirs Make directories in the Databricks Workspace. rm Deletes objects from the Databricks...</pre><h3>Listing Workspace Files</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/710/0*rqznHv0jWAjzrq9h.png" /></figure><h3>Exporting a workspace directory to the local filesystem</h3><p>The databricks workspace export_dir command will recursively export a directory from the Databricks workspace to the local filesystem. Only notebooks are exported and when exported, the notebooks will have the appropriate extensions (.scala, .py, .sql, .R) appended to their names).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0GeAIssBJcDParX7.png" /></figure><h3>Importing a local directory of notebooks</h3><p>Similarly, the databricks workspace import_dir command will recursively import a directory from the local filesystem to the Databricks workspace. Only directories and files with the extensions of .scala, .py, .sql, .r, .R are imported. When imported, these extensions will be stripped off the name of the notebook.</p><p>To overwrite existing notebooks at the target path, the flag -o must be added.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DoR66klxu-dXmp9I.png" /></figure><h3>DBFS CLI Examples</h3><p>The implemented commands for the DBFS CLI can be listed by running databricks fs -h. Commands are run by appending them to databricks fs and all dbfs paths should be prefixed with dbfs:/. To make the command less verbose, we’ve gone ahead and aliased dbfs to databricks fs. For more information reference DBFS API.</p><pre>$ databricks fs -h Usage: databricks fs [OPTIONS] COMMAND [ARGS]... Utility to interact with DBFS. DBFS paths are all prefixed with dbfs:/. Local paths can be absolute or local. Options: -v, --version -h, --help Show this message and exit. Commands: configure cp Copy files to and from DBFS. ls List files in DBFS. mkdirs Make directories in DBFS. mv Moves a file between two DBFS paths. rm Remove files from dbfs.</pre><h3>Copying a file to DBFS</h3><p>It’s possible to copy files from your localhost to DBFS both file by file and recursively. For example to copy a CSV to DBFS, you can run the following command.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/567/0*ycoqdqf-5dm3GkA7.png" /></figure><p>For recursive copying, add the -r flag.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/526/0*Acd9tz0gVHq-q27J.png" /></figure><h3>Copying a file from DBFS</h3><p>Similarly, it is possible to copy files from DBFS back to the local filesystem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/0*Hi8VLpYkPOQcUDSb.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/0*4fJAEuqkitiW-zRb.png" /></figure><h3>What’s Next</h3><p>We’re actively developing new features for the Databricks CLI for developers. The next item immediately on our roadmap is to support cluster and jobs APIs endpoints. We will cover in the second part of this blog series.</p><p>In the spirit of our open source <a href="https://spark.apache.org/">Apache Spark</a> heritage, the source code for the CLI is released on <a href="https://github.com/databricks/databricks-cli">Github</a>. If you have feedback on a feature, please leave an issue on our <a href="https://github.com/databricks/databricks-cli">Github project</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/30/0*4R3H_TpemURZhLVJ." /></figure><p>Try Databricks for free. <a href="https://databricks.com/try-databricks">Get started today</a></p><p><em>Originally published at </em><a href="https://databricks.com/blog/2017/11/08/introducing-command-line-interface-for-databricks-developers.html"><em>databricks.com</em></a><em> on November 8, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=659b29af2560" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/introducing-command-line-interface-for-databricks-developers-659b29af2560">Introducing Command Line Interface for Databricks Developers</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Accelerating R Workflows on Databricks]]></title>
            <link>https://medium.com/databricks-engineering/accelerating-r-workflows-on-databricks-9d6680e98017?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/9d6680e98017</guid>
            <category><![CDATA[sparkr]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[databricks-runtime]]></category>
            <category><![CDATA[r]]></category>
            <dc:creator><![CDATA[Jules S. Damji]]></dc:creator>
            <pubDate>Thu, 12 Oct 2017 00:10:20 GMT</pubDate>
            <atom:updated>2017-10-12T00:16:24.563Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*M7rnz-1S52ddyg_o0MnOUA.png" /></figure><p>Original Authors: <a href="https://databricks.com/blog/author/hossein-falaki">Hossein Falaki</a> and <a href="https://databricks.com/blog/author/xiangrui">Xiangrui Meng</a></p><p>At Databricks we strive to make our <a href="https://databricks.com/product/databricks">Unified Analytics Platform</a> the best place to run big data analytics. For big data, Apache Spark has become the de-facto computing engine, while for advanced analytics, R is one of the <a href="https://www.tiobe.com/tiobe-index/">most widely used languages and environments</a>. R’s <a href="https://cran.r-project.org/web/packages/available_packages_by_name.html">package ecosystem</a> boasts more than 10k packages ranging from implementation of simple statistical functions to sophisticated packages in verticals such as <a href="https://www.bioconductor.org/">genomics</a> and <a href="https://cran.r-project.org/web/views/Finance.html">finance</a>.</p><p>The Unified Analytics Platform, with <a href="https://databricks.com/blog/2017/05/24/databricks-runtime-3-0-beta-delivers-enterprise-grade-apache-spark.html">Databricks Runtime</a> (DBR) at its core, accelerates and unifies the strengths of both Apache Spark and R. The DBR helps customers, in a wide range of industries and verticals, to extract value from their big data efficiently.</p><p>Many Databricks R users take advantage of the strengths of Apache Spark and R’s rich ecosystem with a two-step workflow. First, they perform all large data operations on distributed <a href="http://spark.apache.org/docs/latest/api/R/SparkDataFrame.html">SparkDataFrames</a> using SparkR API. These usually include loading data from its sources, parsing and transforming it into desired format and shape. In many cases, the interesting parts of the final structured dataset can fit in a single machine’s memory. At this stage, users convert distributed datasets to a local R data.frames and pass them to other (single-node) R packages for further analysis or visualization. Often, the conversions between R data.frames and Spark DataFrames happen multiple times. For example, results of an R package functions are parallelized and joined with distributed datasets.</p><p>In this blog post, we introduce two new improvements in <a href="https://docs.databricks.com/release-notes/runtime/3.3.html">Databricks Runtime 3.3</a> (DBR) that accelerate these common workflows. First, we added support for R packages as part of Databricks library management. Second, as part of our <a href="https://databricks.com/blog/2017/05/24/databricks-runtime-3-0-beta-delivers-enterprise-grade-apache-spark.html">DBIO accelerator module</a>, we have accelerated the performance of <a href="http://spark.apache.org/docs/latest/api/R/collect.html">SparkR::collect()</a> and <a href="http://spark.apache.org/docs/latest/api/R/createDataFrame.html">SparkR::createDataFrame()</a>. These two APIs are the bridges between single-node R and distributed Spark applications and are among the most frequently used functions of SparkR.</p><h3>R Package Management in Databricks</h3><p>On Databricks workspace, users can now define a library that points to their desired CRAN repository and package. When this library is attached to a cluster, all workers and the driver node will install the CRAN package automatically. This functionality is accessible through the <a href="https://docs.databricks.com/api/latest/libraries.html#rcranlibrary">REST API</a> as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*k6jwWGCqFKLuP23V.png" /></figure><p>With managed R libraries, integrating workflows with third-party R packages will be much easier on Databricks. This is especially the case with auto-scaling clusters where new workers may be added to a cluster dynamically.</p><h3>High-performance SparkR with DBIO</h3><p>We used the <a href="http://stat-computing.org/dataexpo/2009/">airlines dataset</a> for the benchmark. The dataset consists of over 120 million rows and 29 columns of numeric and character types in CSV format, which is common and popular among R users. We progressively used larger fractions of the dataset to evaluate throughput and latency with varying data size and also found the limit after which the calls fail. We compared DBR 3.3 with DBR 3.0 on clusters comprising of four i3.xlarge workers.</p><h3>Measuring SparkR::collect() performance</h3><p>We first load data using Spark’s CSV data source with automatic schema inference. We cache and materialize the Spark DataFrame and then collect it to a local R data.frame. We measure the total elapsed time of the collect step.</p><h3>Measuring SparkR::createDataFrame() performance</h3><p>We load the file from the local file system using R’s tabular data reading functionality with a default configuration, which automatically infers schema. We then parallelize the data.frame with SparkR::createDataFrame() and count the number of rows. For this benchmark, We measure elapsed time of last two steps combined.</p><h3>Benchmark Results</h3><p>First, we compare average throughput of parallelizing R data.frames on DBR 3.3 and DBR 3.0. DBIO can achieve 300x higher average throughput in DBR 3.3 compared to DBR 3.0. When collecting Spark DataFrames, we observed 24X higher average throughput on Databricks Runtime 3.3 compared to the older version.</p><p>The plots below show end-to-end latency. This is what users perceive when calling SparkR API. On each DBR version, we progressively increased input size until the call would fail. We measured elapsed time for each successful run.</p><p>On DBR 3.0 SparkR::createDataFrame() failed with data larger than 750K rows (about 70MB). On DBR 3.3 the call did not fail for any R data.frame. Overall createDataFrame() is 100X faster on DBR 3.3— and can handle much larger data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*yQocqeoh7n67FmHQ.png" /></figure><p>The failure point of SparkR::collect() has not changed. At about 6M rows (550MB) the R process cannot handle the single in-memory object, and we observed failure when collecting Spark DataFrames. In this experiment, DBR 3.3 is 10x faster than older versions across varying input size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*ZiuTjYWj_pp3J95b.png" /></figure><h3>Conclusions</h3><p>We are continuously working to improve different R workflows on Databricks. We recently announced <a href="https://databricks.com/blog/2017/05/25/using-sparklyr-databricks.html">integration with sparklyr</a>; R package management and improved SparkR performance are our most recent steps toward that goal.</p><p>As shown above, the DBIO in Databricks Runtime 3.3 significantly accelerates the performance of two of the most important SparkR calls: SparkR::collect() and SparkR::createDataFrame(). These calls transfer data from Spark’s JVM to R and vice-versa and are the most popular SparkR APIs.</p><h3>Read More</h3><p>To read more about our efforts with SparkR on Databricks, we refer you to the following assets:</p><p>Try Databricks for free. <a href="https://databricks.com/try-databricks">Get started today</a></p><p><em>Originally published at </em><a href="https://databricks.com/blog/2017/10/06/accelerating-r-workflows-on-databricks.html"><em>databricks.com</em></a><em> on October 6, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9d6680e98017" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/accelerating-r-workflows-on-databricks-9d6680e98017">Accelerating R Workflows on Databricks</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cost Based Optimizer in Apache Spark 2.2]]></title>
            <link>https://medium.com/databricks-engineering/cost-based-optimizer-in-apache-spark-2-2-619cffb961f6?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/619cffb961f6</guid>
            <category><![CDATA[spark-sql]]></category>
            <category><![CDATA[cost-based-optimization]]></category>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[apache-spark]]></category>
            <dc:creator><![CDATA[Jules S. Damji]]></dc:creator>
            <pubDate>Fri, 01 Sep 2017 20:48:30 GMT</pubDate>
            <atom:updated>2017-09-02T20:43:28.935Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/573/1*IbhRS9o8Ir105yIJI1GSUQ.png" /></figure><p><strong>Original authors</strong>: Ron Hu, Zhenhua Wang, Wenchen Fan, and Sameer Agarwal</p><p><em>This is a joint engineering effort between Databricks’ Apache Spark engineering team (Sameer Agarwal and Wenchen Fan) and Huawei’s engineering team (Ron Hu and Zhenhua Wang)</em></p><p><a href="https://databricks.com/blog/2017/07/11/introducing-apache-spark-2-2.html">Apache Spark 2.2</a> recently shipped with a state-of-art cost-based optimization framework that collects and leverages a variety of per-column data statistics (e.g., cardinality, number of distinct values, NULL values, max/min, average/max length, etc.) to improve the quality of query execution plans. Leveraging these statistics helps Spark to make better decisions in picking the most optimal query plan. Examples of these optimizations include selecting the correct build side in a hash-join, choosing the right join type (broadcast hash-join vs. shuffled hash-join) or adjusting a multi-way join order, among others.</p><p>In this blog, we’ll take a deep dive into Spark’s Cost Based Optimizer (CBO) and discuss how Spark collects and stores these statistics, optimizes queries, and show its performance impact on TPC-DS benchmark queries.</p><h3>A Motivating Example</h3><p>At its core, <a href="https://databricks.com/blog/2015/04/13/deep-dive-into-spark-sqls-catalyst-optimizer.html">Spark’s Catalyst optimizer</a> is a general library for representing query plans as trees and sequentially applying a number of optimization rules to manipulate them. A majority of these optimization rules are based on heuristics, i.e., they only account for a query’s structure and ignore the properties of the data being processed, which severely limits their applicability. Let us demonstrate this with a simple example. Consider a query shown below that filters a table <em>t1</em> of size 500GB and joins the output with another table <em>t2</em> of size 20GB. Spark implements this query using a hash join by choosing the smaller join relation as the <em>build side</em> (to build a hash table) and the larger relation as the <em>probe side</em> <a href="https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html#fn-12034-1">1</a>. Given that <em>t2</em> is smaller than <em>t1</em>, Apache Spark 2.1 would choose the right side as the build side without factoring in the effect of the filter operator (which in this case filters out the majority of <em>t1</em>‘s records). Choosing the incorrect side as the <em>build side</em> often forces the system to give up on a fast hash join and turn to sort-merge join due to memory constraints.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/488/0*DXacEjRZmdJJVBgh.png" /></figure><p>Apache Spark 2.2, on the other hand, collects statistics for each operator and figures out that the left side would be only 100MB (1 million records) after filtering, and the right side would be 20GB (100 million records). With the correct size/cardinality information for both sides, Spark 2.2 would choose the left side as the build side resulting in significant query speedups.</p><p>In order to improve the quality of query execution plans, we enhanced the Spark SQL optimizer with detailed statistics information. From the detailed statistics information, we propagate the statistics to other operators as we traverse the query tree upwards. Once propagated, we can estimate the number of output records and output size for each database operator, enabling and optimizing an efficient query plan.</p><h3>Statistics Collection Framework</h3><h3>ANALYZE TABLE command</h3><p>CBO relies on detailed statistics to optimize a query plan. To collect these statistics, users can issue these new SQL commands described below:</p><p>ANALYZE TABLE table_name COMPUTE STATISTICS</p><p>The above SQL statement can collect table level statistics such as number of rows and table size in bytes. Note that ANALYZE, COMPUTE, and STATISTICS are reserved keywords and can take specific column names as arguments, storing all the table level statistics in the metastore.</p><p>ANALYZE TABLE table_name COMPUTE STATISTICS FOR COLUMNS column-name1, column-name2, ….</p><p>Note that it’s not necessary to specify every column of a table in the ANALYZE statement — only those that are used in a filter/join condition, or in group by clauses etc.</p><h4>Types of Statistics</h4><p>The following tables list the detailed statistics information we collect for numeric/date/timestamp and string/binary data types respectively.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/706/0*Org_5L9v2PcQJaeX.png" /></figure><p>Given that CBO traverses Spark’s LogicalPlan in a post-order fashion, we can propagate these statistics bottom-up to other operators. Although there are numerous operators for which we can estimate their statistics and corresponding costs, here we’ll describe the process to estimate statistics for two of the most complex and interesting operators: FILTER and JOIN.</p><h3>FILTER Selectivity</h3><p>A filter condition is a predicate expression specified in the WHERE clause of a SQL SELECT statement. A predicate can be a compound logical expression with logical AND, OR, NOT operators combining multiple single conditions. A single condition usually has comparison operators such as =, &lt;, &lt;=, &gt;, &gt;= or &lt;=&gt;<a href="https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html#fn-12034-2">2</a>. Therefore, it can be quite complex to estimate the selectivity for the overall filter expression.</p><p>Let’s examine a few computations of filter selectivity for a compound logical expression comprising of multiple single logical expressions.</p><ul><li>For logical AND expression, its filter selectivity is the selectivity of left condition multiplied by the selectivity of the right condition, i.e., fs(a AND b) = fs(a) * fs (b).</li><li>For logical-OR expression, its filter selectivity is the selectivity of left condition, plus the selectivity of the right condition, and minus the selectivity of left condition logical-AND right condition,<br> i.e., fs (a OR b) = fs (a) + fs (b) - fs (a AND b) = fs (a) + fs (b) – (fs (a) * fs (b))</li><li>For logical-NOT expression, its filter factor is 1.0 minus the selectivity of the original expression, i.e., fs (NOT a) = 1.0 - fs (a)</li></ul><p>Next, let’s examine a single logical condition that can have comparison operators, such as =, &lt;, &lt;=, &gt;, &gt;= or &lt;=&gt;. In order to drive intuition, we will compute the filter selectivity for equality (=) and less than (&lt;) operators for the case with one operand as column and the other operand as a literal. Other comparison operators can be derived similarly.</p><ul><li><strong>Equal-to (=) condition:</strong> Here we check if the literal constant value of the condition falls within the qualified range between the current minimal and maximal column value. This is necessary because the range may change due to a previous condition applied earlier. If the constant value is outside the qualified range, then the filter selectivity is simply 0.0. Otherwise, it is the inverse of the number of distinct values (note that without additional histogram information, we just assume a uniform distribution for the column values). Future releases will leverage histograms to further improve estimation accuracy.</li><li><strong>Less-than (&lt;) condition:</strong> Here we check where the constant literal value of the condition falls. If it is smaller than the current minimal column value, then the filter selectivity is simply 0.0 (and if it’s larger than the current maximal value, then the selectivity is just 1.0). Otherwise, we compute the filter factor based on the available information. If there is no histogram, we prorate and set filter selectivity to: (constant – minimum) / (maximum – minimum). On the other hand, if there is a histogram, we can calculate the filter selectivity by adding up the density of histogram buckets between current column minimal values and the constant value. Also, note that the constant on the right hand side of the condition would now become the new maximal column value.</li></ul><h3>Join Cardinality</h3><p>Now that we’ve discussed filter selectivity, let’s talk about join output cardinality. Before computing the cardinality of a two-way join output, we should already have the output cardinalities of both its child nodes. The cardinality of each join side is no greater than the number of records in the original join table; rather, it is the number of qualified records after applying all execution operators before this join operator. Here we are specifically interested in computing the cardinality of inner join operation as it is often used to derive the cardinalities of other join types. We compute the number of rows of A join B on A.k = B.k as:</p><p>num(A IJ B) = num(A)*num(B)/max(distinct(A.k),distinct(B.k))</p><p>where num(A) is the number of qualified records in <strong>table A</strong> immediately before join operation, and distinct is the number of distinct values of the join column k.</p><p>By computing the inner join cardinality, we can similarly derive the join cardinality for other join types as below:</p><ul><li><strong>Left-Outer Join:</strong> num(A LOJ B) = max(num(A IJ B),num(A)) meaning the larger value between the inner join output cardinality and cardinality of the left join outer side A. This is because we still need to factor in every record of the outer side although some of them do not produce join output records.</li><li><strong>Right-Outer Join:</strong> num(A ROJ B) = max(num(A IJ B),num(B))</li><li><strong>Full-Outer Join:</strong> num(A FOJ B) = num(A LOJ B) + num(A ROJ B) - num(A IJ B)</li></ul><h3>Optimal Plan Selection</h3><p>Now that we are armed with intermediate data statistics, let us discuss how Spark SQL uses this information to pick the best possible query plan. Earlier we explained selecting build side based on accurate cardinality and statistics for a hash join operation.</p><p>Similarly, with accurate cardinality and size estimation for all operators preceding a join operator, we can better estimate the size of a join side to decide whether or not that side can meet the broadcast criteria.</p><p>These statistics also help us leverage cost-based join reordering optimizations. We adapted the dynamic programming algorithm in [Selinger 1979]<a href="https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html#fn-12034-3">3</a>to pick the best m-way join order. To be more specific, when building m-way joins, we only keep the best plan (with the lowest cost) for the same set of m items. For instance, for 3-way joins, we only keep the best join plan with its order for items {A, B, C} among plans (A join B) join C, (A join C) join B and (B join C) join A. Our adapted algorithm considers all combinations including <em>left-deep trees</em>, <em>bushy trees</em>, and <em>right-deep-trees</em>. We also prune cartesian product candidates when building a new plan if there exists no join condition involving references from both left and right subtrees. This pruning strategy significantly reduces the search space.</p><p>Most database optimizers factor in the CPU and I/O costs separately to estimate the overall operator cost. In Spark, we estimate the cost of a join operator with a simple formula:</p><p>cost = weight * cardinality + (1.0 - weight) * size <a href="https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html#fn-12034-4">4</a></p><p>The first portion of the join cost formula roughly corresponds to the CPU cost and the second portion roughly corresponds to the I/O cost. The cost of a join tree is the summation of costs of all intermediate joins.</p><h3>Query Benchmark and Analysis</h3><p>We took a non-intrusive approach while adding these cost-based optimizations to Spark by adding a global config spark.sql.cbo.enabled to enable/disable this feature. In Spark 2.2, this parameter is set to false by default. This was a conscious short-term choice keeping in mind that Spark is being used in production at thousands of companies and changing the default behavior might be undesirable for existing production workloads.</p><h3>Configuration and Methodology</h3><p>We ran all TPC-DS benchmark queries with Apache Spark 2.2 on a 4 node cluster (Huawei FusionServer RH2288 with 40 cores and 384 GB memory each) and a scale factor of 1000 (i.e., 1TB data). It took us ~14 minutes to collect the statistics on all 24 tables (and a total of 425 columns).</p><p>Before examining the end-to-end results, let us first look at a specific TPC-DS query (Q25; shown below) to better understand the impact of cost-based join ordering. This query involves three fact tables: store_sales (2.9 billion rows), store_returns (288 million rows) and catalog_sales (1.44 billion rows). It also involves three dimension tables: date_dim (73K rows), store (1K rows) and item (300K rows).</p><pre>SELECT i_item_id, <br> i_item_desc, <br> s_store_id, <br> s_store_name, <br> sum(ss_net_profit) AS store_sales_profit, <br> sum(sr_net_loss) AS store_returns_loss, <br> sum(cs_net_profit) AS catalog_sales_profit <br>FROM <br> store_sales, store_returns, catalog_sales, date_dim d1, date_dim   d2, date_dim d3, store, item <br>WHERE <br> d1.d_moy = 4<br>   AND d1.d_year = 2001<br>   AND d1.d_date_sk = ss_sold_date_sk<br>   AND i_item_sk = ss_item_sk<br>   AND s_store_sk = ss_store_sk<br>   AND ss_customer_sk = sr_customer_sk<br>   AND ss_item_sk = sr_item_sk<br>   AND ss_ticket_number = sr_ticket_number<br>   AND sr_returned_date_sk = d2.d_date_sk<br>   AND d2.d_moy BETWEEN 4 AND 10<br>   AND d2.d_year = 2001<br>   AND sr_customer_sk = cs_bill_customer_sk<br>   AND sr_item_sk = cs_item_sk<br>   AND cs_sold_date_sk = d3.d_date_sk<br>   AND d3.d_moy BETWEEN 4 AND 10<br>   AND d3.d_year = 2001<br>GROUP BY<br> i_item_id, i_item_desc, s_store_id, s_store_name<br>ORDER BY<br> i_item_id, i_item_desc, s_store_id, s_store_name<br>LIMIT 100</pre><h3>Q25 without CBO</h3><p>Let us first look at the unoptimized join tree for Q25 without CBO (shown below). This is also commonly referred to as a left-deep tree. Here, joins #1 and #2 are large fact-to-fact table joins that join the 3 fact tables store_sales, store_returns, and catalog_sales together, and produce very large intermediate tables. Both these joins executed as shuffle joins with the large join #1 outputting 199 million rows. Overall, the query takes 241 seconds when CBO is <em>disabled</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/469/0*7A0bT71I6-LHu3OD.png" /></figure><h3>Q25 with CBO</h3><p>On the other hand, with cost-based optimization, Spark creates an optimal join plan that reduces intermediary data size (shown below). In this case, Spark creates a <em>bushy tree</em> instead of a <em>left-deep tree</em>. When CBO is enabled, Spark joins the fact tables with their corresponding date_dim dimension table first (before attempting any fact-to-fact joins). Avoiding large fact-to-fact joins means avoiding large expensive shuffles. In this query, this reduces the intermediate data size to about 1/6th (compared to the previous case). As a result, Q25 took only <strong>71 seconds</strong>, representing a speedup of <strong>3.4X</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/573/0*korCip_ssRCdpV8H.png" /></figure><h3>TPC-DS Query Performance</h3><p>Now that we’ve established an intuition behind where the improvements come from, let us look at the end-to-end TPC-DS query results. The figure below shows query times for all TPC-DS queries with and without CBO:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/710/0*V3cKcwJ401UNuaz2.png" /></figure><p>First of all, note that about half of TPC-DS benchmark queries do not show performance change. This is because there is no change in the query plan when running queries with or without CBO (i.e., even without CBO, existing heuristics in Spark’s Catalyst optimizer were able to optimize these queries well). For the remaining queries, while there were improvements across the board, what’s perhaps more interesting is the set of 16 queries where CBO brought substantial changes to the query plan and improved performance by more than <strong>30%</strong> (shown below). Overall, we observed that the geometric mean speedup for these 16 queries was around 2.2X with Q72 achieving a maximal speedup of <strong>8X</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/690/0*6359WPA6EVIuk8bG.png" /></figure><h3>Conclusion</h3><p>To recap, this blog post highlighted various aspects of the new cost-based optimizer in Apache Spark 2.2. We discussed details of the statistics collection framework, cardinality propagation for filters and joins, the cost-based optimizations they enable (build side selection and multi-way join reordering), and its performance impact on TPC-DS benchmark queries.</p><p>In the past year, we’ve resolved a total of 32 sub-tasks spanning over 50+ patches and 7000+ line of code under the CBO umbrella <a href="https://issues.apache.org/jira/browse/SPARK-16026">JIRA SPARK-16026</a>. That said, leveraging cost-based optimizations in distributed databases is an extremely complex problem and this is just a first step in this direction. In future releases, we plan on continuing work in this direction by adding more complex statistics (histograms, count-min sketches, partition-level statistics etc.) and refining our cost formulas.</p><p>We are excited about the progress made and hope you will enjoy the improvements. We encourage you to try out the new Cost-Based Optimizer in Apache Spark 2.2!</p><h3>Read More</h3><p>You can view our Spark Summit 2017 talk: <a href="https://spark-summit.org/2017/events/cost-based-optimizer-in-apache-spark-22/">Cost Based Optimizer in Spark 2.2</a></p><p>Try Databricks for free. <a href="https://databricks.com/try-databricks">Get started today</a></p><p><em>Originally published at </em><a href="https://databricks.com/blog/2017/08/31/cost-based-optimizer-in-apache-spark-2-2.html"><em>databricks.com</em></a><em> on August 31, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=619cffb961f6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/cost-based-optimizer-in-apache-spark-2-2-619cffb961f6">Cost Based Optimizer in Apache Spark 2.2</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Developing Custom Machine Learning Algorithms in PySpark]]></title>
            <link>https://medium.com/databricks-engineering/developing-custom-machine-learning-algorithms-in-pyspark-b9b1cca320e6?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/b9b1cca320e6</guid>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Jules S. Damji]]></dc:creator>
            <pubDate>Thu, 31 Aug 2017 19:08:27 GMT</pubDate>
            <atom:updated>2017-08-31T19:09:30.296Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-3yJf8MBLwJ3rf047Fpopg.png" /></figure><p>Original authors: <a href="https://databricks.com/blog/author/ajay-saini">Ajay Saini</a> and <a href="https://databricks.com/blog/author/joseph">Joseph Bradley</a></p><p>Developing custom Machine Learning (ML) algorithms in PySpark — the <a href="http://spark.apache.org/docs/2.1.0/api/python/index.html">Python API</a> for Apache Spark — can be challenging and laborious. In this blog post, we describe our work to improve PySpark APIs to simplify the development of custom algorithms. Our key improvement reduces hundreds of lines of boilerplate code for persistence (saving and loading models) to a single line of code. These changes are expected to be available in the next <a href="http://spark.apache.org/versioning-policy.html">Apache Spark release</a>.</p><h3>Background: PySpark developer APIs</h3><p>In recent years, Python has become the most popular language for data scientists worldwide, with over a million developers contributing to thousands of open source ML projects. Despite Python’s immense popularity, the developer APIs of <a href="http://spark.apache.org/docs/latest/ml-guide.html">Apache Spark MLlib</a> remain Scala-dominated, with all algorithms implemented first in Scala and then made available in Python via wrappers. As a result, it has been very difficult for data scientists to develop ML algorithms in Python without having to write Scala code as well.</p><p>This blog post introduces several improvements to PySpark that facilitate the development of custom ML algorithms and 3rd-party ML packages using Python. After introducing the main algorithm APIs in MLlib, we discuss current challenges in building custom ML algorithms on top of PySpark. We then describe our key improvements to PySpark for simplifying such customization.</p><h3>MLlib algorithm APIs</h3><p>Before discussing the specific changes to PySpark, it helps to understand the main APIs for ML algorithms in Spark. There are two major types of algorithms: <a href="http://spark.apache.org/docs/2.2.0/ml-pipeline.html">Transformers and Estimators</a>.</p><p>Transformers are algorithms that take an input dataset and modify it via a transform() function to produce an output dataset. For example, <a href="http://spark.apache.org/docs/2.2.0/ml-features.html#binarizer">Binarizer</a> reads an input column of feature values from a dataset, and it outputs a dataset with a new column of 0/1 features based on thresholding the original features.</p><p>Estimators are ML algorithms that take a training dataset, use a fit() function to train an ML model, and output that model. That model is itself a Transformer; for models, calling transform() will “transform” the dataset by adding a new column of predictions. Popular examples of Estimators are <a href="https://docs.databricks.com/spark/latest/mllib/binary-classification-mllib-pipelines.html#logistic-regression">Logistic Regression</a> and <a href="https://docs.databricks.com/spark/latest/mllib/binary-classification-mllib-pipelines.html#random-forest">Random Forests</a>.</p><p>Users often combine multiple Transformers and Estimators into a data analytics workflow. ML Pipelines provide an API for chaining algorithms, feeding the output of each algorithm into following algorithms. For more details on these types of algorithms, check out the <a href="https://docs.databricks.com/spark/latest/mllib/index.html">Databricks docs</a>.</p><p>Below, we show a simple Pipeline with 2 feature Transformers (Tokenizer, HashingTF) and 1 Estimator (LogisticRegression) from the <a href="http://spark.apache.org/docs/latest/ml-pipeline.html">MLlib guide on Pipelines</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RdQgMtYV59wkK99T.png" /></figure><h3>The obstacle: ML Persistence</h3><p>Let’s say a data scientist wants to extend PySpark to include their own custom Transformer or Estimator. First, the data scientist writes a class that extends either Transformer or Estimator and then implements the corresponding transform() or fit() method in Python. In simple cases, this implementation is straightforward. For example, many feature Transformers can be implemented by using a simple <a href="https://docs.databricks.com/spark/latest/spark-sql/udf-in-python.html">User-Defined Function</a> to add a new column to the input DataFrame.</p><p>One critical functionality in MLlib, however, is ML Persistence. Persistence allows users to save models and Pipelines to stable storage, for loading and reusing later or for passing to another team. The API is simple; the following code snippet fits a model using CrossValidator for parameter tuning, saves the fitted model, and loads it back:</p><pre>val cvModel = cv.fit(training) cvModel.save(&quot;myCVModelPath&quot;) val sameCVModel = CrossValidatorModel.load(&quot;myCVModelPath&quot;)</pre><p>ML Persistence saves models and Pipelines as JSON metadata + Parquet model data, and it can be used to transfer models and Pipelines across Spark clusters, deployments, and teams. For info on persistence, see our <a href="https://databricks.com/blog/2016/05/31/apache-spark-2-0-preview-machine-learning-model-persistence.html">blog post</a> and <a href="http://go.databricks.com/apache-spark-mllib-migrating-ml-workloads-to-dataframes-webinar">webinar</a>.</p><p>Adding support for ML Persistence has traditionally required a Scala implementation. Up until now, the simplest way to implement persistence required the data scientist to implement the algorithm in Scala and write a Python wrapper. Implementing the algorithm in Scala would require knowing both languages, understanding the Java — Python communication interface, and writing duplicate APIs in the two languages.</p><h3>The solution: Python Persistence mixins</h3><p>To support Python-only implementations of ML algorithms, we implemented a persistence framework in the PySpark API analogous to the one in the Scala API. With this framework, when implementing a custom Transformer or Estimator in Python, it is no longer necessary to implement the underlying algorithm in Scala. Instead, one can use <a href="https://en.wikipedia.org/wiki/Mixin#In_Python">mixin classes</a> with a custom Transformer or Estimator to enable persistence.</p><p>For simple algorithms for which all of the parameters are JSON-serializable (simple types like string, float), the algorithm class can extend the classes DefaultParamsReadable and DefaultParamsWritable (<a href="https://issues.apache.org/jira/browse/SPARK-21542">SPARK-21542</a>; <a href="https://github.com/apache/spark/blob/94439997d57875838a8283c543f9b44705d3a503/python/pyspark/ml/util.py">code on Github</a>) to enable automatic persistence. (If you are unfamiliar with Params in ML Pipelines, they are standardized ways to specify algorithm options or properties. Refer to the Param section of the MLlib guide for more info.) The default implementation of persistence will allow the custom algorithm to be saved and loaded back within PySpark.</p><p>These mixins dramatically reduce the development effort required to create custom ML algorithms on top of PySpark. Persistence functionality that used to take many lines of extra code can now be done in a single line in many cases.</p><p>The code snippets below demonstrate the code length of persisting an algorithm with a Scala implementation and a Python wrapper:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aEabRl00kpRNtZIe.png" /></figure><p>And this code snippet demonstrates using these mixins for a Python-only implementation of persistence:</p><pre>class MyShiftTransformer(UnaryTransformer, DefaultParamsReadable, DefaultParamsWritable):</pre><p>Adding the mixins DefaultParamsReadable and DefaultParamsWritable to the MyShiftTransformer class allows us to eliminate a lot of code.</p><p>For complex algorithms with parameters or data which are not JSON-serializable (complex types like DataFrame), the developer can write custom save() and load() methods in Python. Previously, even with save() and load() implemented, custom Python implementations could not be saved within ML Pipelines. Our fixes (<a href="https://issues.apache.org/jira/browse/SPARK-17025">SPARK-17025</a>) correct this issue, allowing smooth integration of custom Python algorithms with the rest of MLlib.</p><h3>Looking forward</h3><p>With these improvements, developers will soon be able to write custom machine learning algorithms in Python, use them in Pipelines, and save and load them without touching Scala. We believe this will unblock many developers and encourage further efforts to develop Python-centric <a href="https://spark-packages.org/">Spark Packages</a> for machine learning.</p><p>Try Databricks for free. <a href="https://databricks.com/try-databricks">Get started today</a></p><p><em>Originally published at </em><a href="https://databricks.com/blog/2017/08/30/developing-custom-machine-learning-algorithms-in-pyspark.html"><em>databricks.com</em></a><em> on August 30, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b9b1cca320e6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/developing-custom-machine-learning-algorithms-in-pyspark-b9b1cca320e6">Developing Custom Machine Learning Algorithms in PySpark</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Anthology of Technical Assets on Apache Spark’s Structured Streaming]]></title>
            <link>https://medium.com/databricks-engineering/anthology-of-technical-assets-on-apache-sparks-structured-streaming-82f4a56088eb?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/82f4a56088eb</guid>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[structured-streaming]]></category>
            <category><![CDATA[streaming-etl]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[apache-spark]]></category>
            <dc:creator><![CDATA[Jules S. Damji]]></dc:creator>
            <pubDate>Thu, 24 Aug 2017 21:20:09 GMT</pubDate>
            <atom:updated>2017-08-24T21:23:57.048Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ndBOd1HhWzbAigbwxEN9Hg.jpeg" /></figure><p>Older anthologies collated a collection of contributions from various authors around a theme — bounded then as a journal or periodical. Newer anthologies, however, include multiple modals of expressions — digitized now as an ebook or a blog. Both offer an exposition of the subject matter. No matter their form, they provide a single source of focused content.</p><p>In this anthology, we have compiled a collection of videos, technical blogs, podcasts, and articles that focus on <a href="https://databricks.com/blog/2016/07/28/structured-streaming-in-apache-spark.html">Apache Spark’s Structured Streaming</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0T8e9I7fLSAcQpUQ.png" /></figure><h3><a href="https://youtu.be/qAZ5XUz32yM?list=PLTPXxbhUt-YV6RdCNARfSKs3-3Old6XTk">Spark Summit 2017 Keynote: Apache Spark 2.2 and Structured Streaming Demo</a></h3><p>Databricks’ Chief Technologist Matei Zaharia thanks the community’s contributions and announces Structured Streaming as ready for production.</p><h3><a href="https://www.youtube.com/watch?v=8o-cyjMRJWg&amp;feature=youtu.be">Easy, Scalable, Fault-tolerant Stream Processing with Structured Streaming in Apache Spark 2.2</a></h3><p>In less than 10 lines of code, you can read streams from Apache Kafka, parse JSON payload data into separate columns, transform it, enrich it by joining with static data and write it out as a table ready for batch or ad-hoc queries. Apache Spark committers and Databricks’ engineers Michael Armbrust and Tathagata Das discuss and demonstrate that with concrete examples.</p><p>Also, they explain features that allow event-time based aggregations, arbitrary stateful operations, and automatic state management using event-time watermarks.</p><h3><a href="https://databricks.com/blog/2016/07/28/continuous-applications-evolving-streaming-in-apache-spark-2-0.html">Continuous Applications: Evolving Streaming in Apache Spark 2.x</a></h3><p>Last year, Databricks’ Chief Technologist Matei Zaharia shared his vision of where Apache Spark streaming is heading: Continuous Applications with Structured Streaming is the next step, he wrote.</p><h3><a href="https://databricks.com/blog/2016/07/28/structured-streaming-in-apache-spark.html">Structured Streaming In Apache Spark: A new high-level API for streaming</a></h3><p>Databricks’ engineers and Apache Spark committers Matei Zaharia, Tathagata Das, Michael Armbrust and Reynold Xin expound on why streaming applications are difficult to write, and how Structured Streaming addresses all the underlying complexities.</p><h3><a href="https://databricks.com/blog/2017/01/19/real-time-streaming-etl-structured-streaming-apache-spark-2-1.html">Real-time Streaming ETL with Structured Streaming in Apache Spark 2.1: Part 1 of Scalable Data @ Databricks</a></h3><p>Databricks’ engineers Tathagata Das, Michael Armbrust and Tyson Condie show how to do streaming ETL with real-time data at scale.</p><h3><a href="https://databricks.com/blog/2017/02/23/working-complex-data-formats-structured-streaming-apache-spark-2-1.html">Working with Complex Data Formats with Structured Streaming in Apache Spark 2.1: Part 2 of Scalable Data @ Databricks</a></h3><p>Learn from Databricks engineers and Apache Spark contributors Burak Yavuz, Michael Armbrust, Tathagata Das, and Tyson Condie how to handle complex and nested data formats with Structured Streaming.</p><h3><a href="https://databricks.com/blog/2017/04/26/processing-data-in-apache-kafka-with-structured-streaming-in-apache-spark-2-2.html">Processing Data in Apache Kafka with Structured Streaming in Apache Spark 2.2: Part 3 of Scalable Data @ Databricks</a></h3><p>Databricks engineers and Spark contributors Kunal Khamar, Tyson Condie and Michael Armbrust show how easily you can read streams from Apache Kafka using Structured Streaming APIs in Apache Spark 2.2.</p><h3><a href="https://databricks.com/blog/2017/05/08/event-time-aggregation-watermarking-apache-sparks-structured-streaming.html">Event-time Aggregation and Watermarking in Apache Spark’s Structured Streaming: Part 4 of Scalable Data @ Databricks</a></h3><p>How to do event-time aggregations and watermarking using simple Structured Streaming APIs? Databricks engineer and Spark committer Tathagata Das explains how.</p><h3><a href="https://databricks.com/blog/2017/05/18/taking-apache-sparks-structured-structured-streaming-to-production.html">Taking Apache Spark’s Structured Streaming to Production: Part 5 of Scalable Data @ Databricks</a></h3><p>How do you ensure your Structured Streaming Application is ready for production. Product Manager Bill Chambers and Apache Spark committer Michael Armbrust lay out the vital steps, using simple APIs for alerts and monitoring streaming query states.</p><h3><a href="https://databricks.com/blog/2017/05/22/running-streaming-jobs-day-10x-cost-savings.html">Running Streaming Jobs Once a Day For 10x Cost Savings: Part 6 of Scalable Data @ Databricks</a></h3><p>Apache Spark contributors Burak Yavuz and Tyson Condie demonstrate how to control and curb costs by using simple APIs such as Run Once trigger feature added to Structured Streaming in Spark 2.2. You get all the benefits of the Catalyst Optimizer incrementalizing your workload and the cost savings of not having an idle cluster lying around.</p><h3><a href="https://databricks.com/blog/2017/04/04/real-time-end-to-end-integration-with-apache-kafka-in-apache-sparks-structured-streaming.html">Real-Time End-to-End Integration with Apache Kafka in Apache Spark’s Structured Streaming</a></h3><p>Databricks Senior Solution Architect Sunil Sitaula guides you through an end-to-end integration with Apache Kafka, consuming messages from it, doing simple to complex windowing ETL, and pushing the desired output to various sinks such as memory, console, file, databases, and back to Kafka itself.</p><h3><a href="https://databricks.com/blog/2017/06/06/simple-super-fast-streaming-engine-apache-spark.html">Making Apache Spark the Fastest Open Source Streaming Engine</a></h3><p>Databricks’ lead on Structured Streaming and Spark committer Michael Armbrust avers why Structured Streaming is the fastest open source engine compared to other streaming engines.</p><h3><a href="https://databricks.com/blog/2017/08/09/apache-sparks-structured-streaming-with-amazon-kinesis-on-databricks.html">Apache Spark’s Structured Streaming with Amazon Kinesis on Databricks: A quick guide on how to get started with Kinesis Connector</a></h3><p>Databricks’ Spark Community Evangelist outlines steps to use AWS Kinesis with Structured Streaming in Apache Spark 2.2 on Databricks Runtime 3.0.</p><h3><a href="https://databricks.com/blog/2017/05/26/bay-area-apache-spark-meetup-summary.html">Arbitrary Stateful Aggregations in Structured Streaming in Apache Spark</a></h3><p>In this Bay Area Apache Spark Meetup talk, Burak Yavuz, Spark committer and Databricks software engineer, expands on how to use Structured Streaming APIs to maintain stateful aggregations.</p><h3><a href="https://www.oreilly.com/ideas/structured-streaming-comes-to-apache-spark-2-0">Structured Streaming Comes to Apache Spark 2.0</a></h3><p>O’Reilly’s Chief Data Scientist Ben Lorica sits down with Michael Armbrust and talks about life and structured streaming.</p><h3><a href="http://www.infoworld.com/article/3052924/analytics/what-sparks-structured-streaming-really-means.html">What Spark’s Structured Streaming Really Means</a></h3><p><a href="http://www.infoworld.com/author/Ian-Pointer/">Ion Pointer</a> (contributor for InfoWorld) advocates why DataFrames are the best choice for Apache Spark Streaming in Spark 2.0, and why structured streaming makes sense.</p><h3><a href="https://www.datanami.com/2016/02/25/spark-2-0-to-introduce-new-structured-streaming-engine/">Apache Spark 2.0 to Introduce New ‘Structured Streaming’ Engine</a></h3><p><a href="https://www.datanami.com/">Datanami</a> sits down with Chief Technologist and Co-founder of Databricks Matei Zaharia to discuss all aspects of Structured Streaming in Apache Spark</p><h3>What’s Next?</h3><p>You might want to bookmark this page, as we will update it with part 7 of our series on Structured Streaming. If you want to try some of the notebooks in these assets to explore Spark 2.2’s Structured Streaming features on <a href="https://docs.databricks.com/release-notes/runtime/3.0.html">Databricks Runtime 3.0</a>, you can sign up for a <a href="https://databricks.com/try">free trial</a>.</p><p>Try Databricks for free. <a href="https://databricks.com/try-databricks">Get started today</a></p><p><em>Originally published at </em><a href="https://databricks.com/blog/2017/08/24/anthology-of-technical-assets-on-apache-sparks-structured-streaming.html"><em>databricks.com</em></a><em> on August 24, 2017.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=82f4a56088eb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/anthology-of-technical-assets-on-apache-sparks-structured-streaming-82f4a56088eb">Anthology of Technical Assets on Apache Spark’s Structured Streaming</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integrating Apache Airflow with Databricks]]></title>
            <link>https://medium.com/databricks-engineering/integrating-apache-airflow-with-databricks-dc4de9ca8e2e?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/dc4de9ca8e2e</guid>
            <category><![CDATA[job-scheduling]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[airflow]]></category>
            <dc:creator><![CDATA[Jake Bellacera]]></dc:creator>
            <pubDate>Wed, 16 Aug 2017 13:01:02 GMT</pubDate>
            <atom:updated>2017-08-16T13:01:01.642Z</atom:updated>
            <content:encoded><![CDATA[<h4>An easy, step-by-step tutorial to manage Databricks workloads with Airflow</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZTzW3Z7rFCA3A8t3ZMTbVw.jpeg" /></figure><p><strong>Original author:</strong> <a href="https://databricks.com/blog/author/andrew-chen">Andrew Chen</a></p><p><em>This blog post is part of our series of internal engineering blogs on Databricks platform, infrastructure management, integration, tooling, monitoring, and provisioning.</em></p><p>Today, we are excited to announce native Databricks integration in <a href="https://airflow.incubator.apache.org/">Apache Airflow</a>, a popular open source workflow scheduler. This blog post illustrates how you can set up Airflow and use it to trigger Databricks jobs.</p><p>One very popular feature of Databricks’ <a href="https://databricks.com/unified-analytics-platform">Unified Analytics Platform</a> (UAP) is the ability to convert a data science notebook directly into production jobs that can be run regularly. While this feature unifies the workflow from exploratory data science to production data engineering, some data engineering jobs can contain complex dependencies that are difficult to capture in notebooks. To support these complex use cases, we provide REST APIs so jobs based on notebooks and libraries can be triggered by external systems. Of these, one of the most common schedulers used by our customers is Airflow. We are happy to share that we have also extended Airflow to support Databricks out of the box.</p><h3>Airflow Basics</h3><p>Airflow is a generic workflow scheduler with dependency management. Besides its ability to schedule periodic jobs, Airflow lets you express explicit dependencies between different stages in your data pipeline.</p><p>Each ETL pipeline is represented as a directed acyclic graph (DAG) of tasks (not to be mistaken with Spark’s own DAG scheduler and tasks). Dependencies are encoded into the DAG by its edges — for any given edge, the downstream task is only scheduled if the upstream task completed successfully. For example, in the example, DAG below, task <strong>B</strong> and <strong>C</strong> will only be triggered after task <strong>A</strong> completes successfully. Task <strong>D</strong> will then be triggered when task <strong>B</strong> and <strong>C</strong> both complete successfully.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/431/0*SfWrejsMlYZfShga.png" /></figure><p>The tasks in Airflow are instances of “operator” class and are implemented as small Python scripts. Since they are simply Python scripts, operators in Airflow can perform many tasks: they can poll for some precondition to be true (also called a sensor) before succeeding, perform ETL directly, or trigger external systems like Databricks.</p><blockquote><em>For more information on Airflow, please take a look at their </em><a href="http://airflow.readthedocs.io/en/latest/"><em>documentation</em></a><em>.</em></blockquote><h3>Native Databricks Integration in Airflow</h3><p>We implemented an Airflow operator called <a href="http://airflow.readthedocs.io/en/latest/integration.html#databrickssubmitrunoperator"><em>DatabricksSubmitRunOperator</em></a>, enabling a smoother integration between Airflow and Databricks. Through this operator, we can hit the Databricks <a href="https://docs.databricks.com/api/latest/jobs.html">Runs Submit API</a> endpoint, which can externally trigger a single run of a jar, python script, or notebook. After making the initial request to submit the run, the operator will continue to poll for the result of the run. When it completes successfully, the operator will return allowing for downstream tasks to run.</p><p>We’ve contributed the <em>DatabricksSubmitRunOperator</em> upstream to the open-source Airflow project. However, the integrations will not be cut into a release branch until Airflow 1.9.0 is released. Until then, to use this operator you can install Databricks’ fork of Airflow, which is essentially Airflow version 1.8.1 with our <em>DatabricksSubmitRunOperator</em> patch applied.</p><pre>pip install --upgrade &quot;git+git://github.com/databricks/incubator-airflow.git@1.8.1-db1#egg=apache-airflow[databricks]&quot;</pre><h3>Airflow with Databricks Tutorial</h3><p>In this tutorial, we’ll set up a toy Airflow 1.8.1 deployment which runs on your local machine and also deploy an example DAG which triggers runs in Databricks.</p><p>The first thing we will do is initialize the sqlite database. Airflow will use it to track miscellaneous metadata. In a production Airflow deployment, you’ll want to edit the configuration to point Airflow to a <a href="https://airflow.incubator.apache.org/configuration.html#setting-configuration-options">MySQL or Postgres database</a> but for our toy example, we’ll simply use the default sqlite database. To perform the initialization run:</p><pre>airflow initdb</pre><p>The SQLite database and default configuration for your Airflow deployment will be initialized in ~/airflow.</p><p>In the next step, we’ll write a DAG that runs two Databricks jobs with one linear dependency. The first Databricks job will trigger a notebook located at /Users/airflow@example.com/PrepareData, and the second will run a jar located at dbfs:/lib/etl-0.1.jar. To save time, we’ve already gone ahead and written the DAG for you <a href="https://github.com/apache/incubator-airflow/blob/master/airflow/contrib/example_dags/example_databricks_operator.py">here</a>.</p><p>From a mile high view, the script DAG essentially constructs two <em>DatabricksSubmitRunOperator</em> tasks and then sets the dependency at the end with the <a href="https://github.com/apache/incubator-airflow/blob/master/airflow/contrib/example_dags/example_databricks_operator.py#L82">set_dowstream method</a>. A skeleton version of the code looks something like this:</p><pre>notebook_task = DatabricksSubmitRunOperator(<br>    task_id=&#39;notebook_task&#39;,<br>    …)<br><br>spark_jar_task = DatabricksSubmitRunOperator(<br>    task_id=&#39;spark_jar_task&#39;,<br>    …)<br>notebook_task.set_downstream(spark_jar_task)</pre><p>In reality, there are some other details we need to fill in to get a working DAG file. The first step is to set some default arguments which will be applied to each task in our DAG.</p><pre>args = {<br>    &#39;owner&#39;: &#39;airflow&#39;,<br>    &#39;email&#39;: [&#39;airflow@example.com&#39;],<br>    &#39;depends_on_past&#39;: False,<br>    &#39;start_date&#39;: airflow.utils.dates.days_ago(2)<br>}</pre><p>The two interesting arguments here are <a href="https://airflow.incubator.apache.org/code.html?highlight=depends_on_past#baseoperator">depends_on_past</a> and <a href="https://airflow.incubator.apache.org/code.html?highlight=start_date#baseoperator">start_date</a>. If depends_on_past is true, it signals Airflow that a task should not be triggered unless the previous instance of a task completed successfully. The start_date argument determines when the first task instance will be scheduled.</p><p>The next section of our DAG script actually instantiates the <a href="https://airflow.incubator.apache.org/code.html?highlight=dag#airflow.models.DAG">DAG</a>.</p><pre>dag = DAG(<br>    dag_id=&#39;example_databricks_operator&#39;, default_args=args,<br>    schedule_interval=&#39;@daily&#39;)</pre><p>In this DAG, we give it a unique ID, attach the default arguments we declared earlier, and give it a daily schedule. Next, we’ll specify the specifications of the cluster that will run our tasks.</p><pre>new_cluster = {<br>    &#39;spark_version&#39;: &#39;2.1.0-db3-scala2.11&#39;,<br>    &#39;node_type_id&#39;: &#39;r3.xlarge&#39;,<br>    &#39;aws_attributes&#39;: {<br>        &#39;availability&#39;: &#39;ON_DEMAND&#39;<br>    },<br>    &#39;num_workers&#39;: 8<br>}</pre><p>The schema of this specification matches the new cluster field of the <a href="https://docs.databricks.com/api/latest/jobs.html#runs-submit">Runs Submit</a> endpoint. For your example DAG, you may want to decrease the number of workers or change the instance size to something smaller.</p><p>Finally, we’ll instantiate the <em>DatabricksSubmitRunOperator</em> and register it with our DAG.</p><pre>notebook_task_params = {<br>    &#39;new_cluster&#39;: new_cluster,<br>    &#39;notebook_task&#39;: {<br>        &#39;notebook_path&#39;: &#39;/Users/airflow@example.com/PrepareData&#39;,<br>    },<br>}</pre><pre># Example of using the JSON parameter to initialize the operator.<br>notebook_task = DatabricksSubmitRunOperator(<br>    task_id=&#39;notebook_task&#39;,<br>    dag=dag,<br>    json=notebook_task_params)</pre><p>In this piece of code, the JSON parameter takes a python dictionary that matches the <a href="https://docs.databricks.com/api/latest/jobs.html#runs-submit">Runs Submit</a> endpoint.</p><p>To add another task downstream of this one, we do instantiate the <em>DatabricksSubmitRunOperator</em> again and use the special <a href="https://airflow.incubator.apache.org/code.html?highlight=dag#airflow.models.DAG.set_dependency">set_downstream</a> method on the notebook_task operator instance to register the dependency.</p><pre># Example of using the named parameters of DatabricksSubmitRunOperator<br># to initialize the operator.<br>spark_jar_task = DatabricksSubmitRunOperator(<br>    task_id=&#39;spark_jar_task&#39;,<br>    dag=dag,<br>    new_cluster=new_cluster,<br>    spark_jar_task={<br>        &#39;main_class_name&#39;: &#39;com.example.ProcessData&#39;<br>    },<br>    libraries=[<br>        {<br>            &#39;jar&#39;: &#39;dbfs:/lib/etl-0.1.jar&#39;<br>        }<br>    ]<br>)<br><br>notebook_task.set_downstream(spark_jar_task)</pre><p>This task runs a jar located at dbfs:/lib/etl-0.1.jar.</p><p>Notice that in the notebook_task, we used the JSON parameter to specify the full specification for the submit run endpoint and that in the spark_jar_task, we flattened the top level keys of the submit run endpoint into parameters for the <em>DatabricksSubmitRunOperator</em>. Although both ways of instantiating the operator are equivalent, the latter method does not allow you to use any new top level fields like <a href="https://docs.databricks.com/api/latest/jobs.html#request-structure">spark_python_task</a> or <a href="https://docs.databricks.com/api/latest/jobs.html#request-structure">spark_submit_task</a>. For more detailed information about the full API of <em>DatabricksSubmitRunOperator</em>, please look at the <a href="https://airflow.readthedocs.io/en/latest/integration.html#databrickssubmitrunoperator">documentation here</a>.</p><p>Now that we have our DAG, to install it in Airflow create a directory in ~/airflow called ~/airflow/dags and copy the DAG into that directory.</p><p>At this point, Airflow should be able to pick up the DAG.</p><pre>$ airflow list_dags                                                           [10:27:13]<br>[2017-07-06 10:27:23,868] {__init__.py:57} INFO - Using executor SequentialExecutor<br>[2017-07-06 10:27:24,238] {models.py:168} INFO - Filling up the DagBag from /Users/andrew/airflow/dags<br><br><br>-------------------------------------------------------------------<br>DAGS<br>-------------------------------------------------------------------<br>example_bash_operator<br>example_branch_dop_operator_v3<br>example_branch_operator<br>example_databricks_operator</pre><p>We can also visualize the DAG in the web UI. To start it up, run airflow webserver and connect to localhost:8080. Clicking into the “example_databricks_operator,” you’ll see many visualizations of your DAG. Here is the example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Cwas0RUf9hG3zuBT.png" /></figure><p>At this point, a careful observer might also notice that we don’t specify information such as the hostname, username, and password to a Databricks shard anywhere in our DAG. To configure this we use the <a href="https://airflow.incubator.apache.org/concepts.html?highlight=connections#connections">connection</a> primitive of Airflow that allows us to reference credentials stored in a database from our DAG. By default, all <em>DatabricksSubmitRunOperator</em> set the <a href="http://airflow.readthedocs.io/en/latest/integration.html?highlight=databricks_conn_id#databrickssubmitrunoperator">databricks_conn_id</a> parameter to “databricks_default,” so for our DAG, we’ll have to add a connection with the ID “databricks_default.”</p><p>The easiest way to do this is through the web UI. Clicking into the “Admin” on the top and then “Connections” in the dropdown will show you all your current connections. For our use case, we’ll add a connection for “databricks_default.” The final connection should look something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*K1U-kL3qx0vVKqtY.png" /></figure><p>Now that we have everything set up for our DAG, it’s time to test each task. To do this for the notebook_task we would run, airflow test example_databricks_operator notebook_task 2017-07-01 and for the spark_jar_task we would run airflow test example_databricks_operator spark_jar_task 2017-07-01. To run the DAG on a schedule, you would invoke the scheduler daemon process with the command airflow scheduler.</p><p>If everything goes well, after starting the scheduler, you should be able to see backfilled runs of your DAG start to run in the web UI.</p><h3>Next Steps</h3><p>In conclusion, this blog post provides an easy example of setting up Airflow integration with Databricks. It demonstrates how Databricks extension to and integration with Airflow allows access via Databricks <a href="https://docs.databricks.com/api/latest/jobs.html#runs-submit">Runs Submit</a> API to invoke computation on the Databricks platform. For more detailed instructions on how to set up a production Airflow deployment, please look at the official <a href="https://airflow.incubator.apache.org/configuration.html">Airflow documentation</a>.</p><p>Also, if you want to try this tutorial on Databricks, sign up for a <a href="https://databricks.com/try-databricks">free trial today</a>.</p><p><em>This article was originally published on the </em><a href="https://databricks.com/blog/2017/07/19/integrating-apache-airflow-with-databricks.html"><em>Databricks Blog</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc4de9ca8e2e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/integrating-apache-airflow-with-databricks-dc4de9ca8e2e">Integrating Apache Airflow with Databricks</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Entropy-based Log Redaction for Apache Spark on Databricks]]></title>
            <link>https://medium.com/databricks-engineering/entropy-based-log-redaction-for-apache-spark-on-databricks-3fb7192fe6a9?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/3fb7192fe6a9</guid>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[data-security]]></category>
            <category><![CDATA[log-redaction]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Jake Bellacera]]></dc:creator>
            <pubDate>Tue, 15 Aug 2017 13:01:04 GMT</pubDate>
            <atom:updated>2017-08-15T13:01:04.036Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SAc84eMPcyrloVTsf-fEzA.jpeg" /></figure><p><strong>Original authors:</strong> <a href="https://databricks.com/blog/author/weiluo-ren">Weiluo Ren</a>, <a href="https://databricks.com/blog/author/yu-peng">Yu Peng</a> and <a href="https://databricks.com/blog/author/xiangrui">Xiangrui Meng</a></p><p><em>This blog post is part of our series of internal engineering blogs on Databricks platform, infrastructure management, tooling, monitoring, and provisioning.</em></p><p>We love logs at Databricks. And we want to provide users the best experience to access Apache Spark logs. On Databricks, Spark logs are available via built-in Spark UI for clusters. Databricks also supports <a href="https://docs.databricks.com/user-guide/clusters/log-delivery.html">delivering cluster logs to customers’ chosen destination</a>.</p><p>While we care about the ease of accessing Spark logs, we pay more attention to data security issues that appear in logging. In this blog post, we discuss a specific data security topic on Spark logs: redacting credentials in logs.</p><h3>Credentials in Apache Spark Logs</h3><p>Spark can load data from various data sources, where users may need to provide credentials for accessing data. For instance, a Spark job may read a dataset from S3 using AWS access and secret access keys:</p><pre>spark.read.parquet(&quot;s3a://AKIAABCDEFGHIJKLMNOP:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn@test-bucket/test/path&quot;)</pre><p>Anyone who obtained a copy of the keys would have the same access to the corresponding AWS resources. Unfortunately, the S3 URI that contains the credentials may appear in Spark logs in plain text form:</p><pre>17/01/01 00:00:00 INFO FileScanRDD: Reading File path: s3a://AKIAABCDEFGHIJKLMNOP:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn@test-bucket/test/path, range: ...</pre><p>This could lead to serious data security issues if logs do not receive the same level of protection as credentials. And it is hard for users to control or watch what content are logged by Spark and third-party packages.</p><p>To enhance data security, Databricks redacts credentials from Spark logs at logging time, before log messages are written to the disk. For example, the above log message would appear in redacted form on Databricks:</p><pre>17/01/01 00:00:00 INFO FileScanRDD: Reading File path: s3a://REDACTED_AWS_ACCESS_KEY:REDACTED_AWS_SECRET_ACCESS_KEY@test-bucket/test/path, range: ...</pre><p>In the following sections, we’ll discuss how we attempt to properly identify credentials and redact them at logging time. Although we’ll use AWS keys as examples for this blog post, Databricks also redacts other types of credentials.</p><h3>Identifying AWS Credentials</h3><p>Before each message gets logged, Databricks scans the whole string to identify any possible credential and redacts them. Because redaction is done at logging time, we cannot afford an overly sophisticated redaction procedure that may hurt the performance. With complexity constraints in mind, log redaction at Databricks is developed based on regular expressions (regexes).</p><p>AWS access keys consist of 20-character upper case alphanumeric strings starting with “AKIA,” which can be captured by the following regex:</p><pre>(?&lt;![A-Z0-9])AKIA([A-Z0-9]){16}(?![A-Z0-9])</pre><p>For secret keys, there is no explicit pattern except that they are 40-character base64 strings. AWS provided a regex for access keys in the blog, <a href="https://aws.amazon.com/cn/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/">A Safer Way to Distribute AWS Credentials to EC2</a>, which is a good start:</p><pre>(?&lt;![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])</pre><p>However, we found this regex insufficient to capture all secret keys. Some secret keys contains special characters such as the forward slash “/”, which needs to be URL-encoded (e.g., “/” encoded as “%2F”) to appear in a file path. Therefore, to identify secret keys in Spark logs, we also need to take into account the URL encoding on special characters. Moreover, both users and Spark may escape special characters in the URI, so in some cases special characters may be encoded twice. For instance, instead of “%2F”, “/” may be encoded and logged as “%252F”. We improved the regular expression accordingly as follows, which matches the secret keys not only in code but also in logs:</p><pre>(?&lt;![A-Za-z0-9/+])([A-Za-z0-9/+=]|%2F|%2B|%3D|%252F|%252B|%253D){40}(?![A-Za-z0-9/+=])</pre><h3>False Positives and Entropy Thresholding</h3><p>One issue of using the regular expression above is that it is not specific enough for secret keys, which could cause a number of false positives when applying to all Spark logs. For instance, it could also match hashcodes, paths or even class names that happen to be 40 characters long. To reduce the false positive rate without missing real secret keys, more checks are needed after regular expression matching.</p><p>One difference between secret keys and informative false positives is that the secret keys are randomly generated. One way to differentiate secret keys from those false positives is to measure how random they are. Following this idea, we use entropy to determine how likely a matched string is indeed a secret key that was randomly generated.</p><blockquote><em>In information theory, entropy, or Shannon entropy, is the expected value of the information contained in each message (</em><a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)"><em>Wiki page</em></a><em>). This concept can be applied to strings to measure how random they are. We expect randomly generated strings to have higher entropy than meaningful words with the same length.</em></blockquote><p>After calculating entropy, we set a threshold to decide whether to redact a given string. The threshold is based on the empirical distribution of AWS secret keys and those of known false positive types. One type of false positives is the SHA-1 hashcode, which is 40 characters long and randomly generated. However, the SHA-1 hashcode only consists of hexadecimal digits. Roughly speaking, with the same length, the more distinct characters a string have, the higher its entropy would be. Therefore, the entropy of SHA-1 hashcodes would usually be smaller than that of AWS secret keys, which are generated from base64 character set. By comparing the empirical distribution of entropy of two datasets consisting of AWS secret keys and SHA-1 hashcodes respectively (see figure below), it turns out that there’s a clear gap between those two distributions and we can choose a threshold accordingly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/0*-_tacnApBZvcZNja.png" /></figure><h3>Logging-time Redaction with log4j</h3><p>Logs generated by Apache Spark jobs at Databricks are mostly handled by the logging service log4j. Databricks customizes <a href="https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Appender.html">log4j appenders</a>, for example, console appender and rolling file appender to redact log strings before they arrive at the disk. Note that the redaction is applied to not only normal messages, but also stack traces when exceptions are thrown.</p><p>One interesting lesson learned is that since logging is so pervasive at Databricks, we need to minimize the dependency of customized appenders. Otherwise there could be a circle dependency issue.</p><h3>Using IAM Roles Instead of Keys</h3><p>In this blog post, we shared our experience on redacting credentials at logging time. On Databricks this security feature is turned on automatically[<a href="https://databricks.com/blog/2017/05/30/entropy-based-log-redaction-apache-spark-databricks.html#fn-11005-1">1</a>]. Outside of Databricks, users can implement the methods mentioned to improve data security in Spark logs.</p><p>However, rather than doing redaction at logging time, the best security practice is to simply not use credentials in the first place. Databricks encourages users to <a href="https://docs.databricks.com/user-guide/cloud-configurations/aws/iam-roles.html">use IAM roles to access AWS resources</a>. Users can launch Databricks clusters with IAM roles that grant the workers the corresponding permissions. With IAM-enabled clusters, users no longer need to embed their AWS keys in notebooks to access data, and hence keys won’t appear in logs.</p><p>Similarly, in Databricks File System (DBFS), users can mount their S3 bucket with IAM roles. Users can create clusters with corresponding IAM roles to access data directly without keys.</p><p><em>This article was originally published on the </em><a href="https://databricks.com/blog/2017/05/30/entropy-based-log-redaction-apache-spark-databricks.html"><em>Databricks Blog</em></a><em>.</em></p><ol><li>see <a href="https://docs.databricks.com/user-guide/security.html">https://docs.databricks.com/user-guide/security.html</a> for details</li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3fb7192fe6a9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/entropy-based-log-redaction-for-apache-spark-on-databricks-3fb7192fe6a9">Entropy-based Log Redaction for Apache Spark on Databricks</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Declarative Infrastructure with the Jsonnet Templating Language]]></title>
            <link>https://medium.com/databricks-engineering/declarative-infrastructure-with-the-jsonnet-templating-language-e33d97e862fd?source=rss----a94c8b85dab4---4</link>
            <guid isPermaLink="false">https://medium.com/p/e33d97e862fd</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[cloud-provisioning]]></category>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[data-engineering]]></category>
            <dc:creator><![CDATA[Jake Bellacera]]></dc:creator>
            <pubDate>Mon, 14 Aug 2017 23:35:17 GMT</pubDate>
            <atom:updated>2017-08-14T23:51:12.298Z</atom:updated>
            <content:encoded><![CDATA[<h4>How Databricks Provisions Infrastructure Services at Scale with Kubernetes</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d_hx4BxGZ9qYODhnTn-Bjg.jpeg" /></figure><p><strong>Original authors:</strong> Eric Liang and Aaron Davidson.</p><p><em>This blog post is part of our series of internal engineering blogs on Databricks platform, infrastructure management, integration, tooling, monitoring, and provisioning.</em></p><p>At Databricks engineering, we are avid fans of <a href="https://kubernetes.io/">Kubernetes</a>. Much of our platform infrastructure runs within Kubernetes, whether in AWS cloud or <a href="https://databricks.com/solutions/public-sector">more regulated environments</a>.</p><p>However, we have found that Kubernetes alone is not enough for managing a complex service infrastructure, which may encompass both resources created in Kubernetes (e.g., <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod/">pods</a>, <a href="https://kubernetes.io/docs/concepts/services-networking/service/">services</a>) and external resources such as <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html">IAM roles</a>. Management complexity comes from (1) need for visibility into the current state of the infrastructure (2) reasoning about how to make changes to the infrastructure.</p><p>To help reduce management complexity, it is desirable for infrastructure to be <a href="https://research.google.com/pubs/pub43438.html"><em>declarative</em></a> (i.e., described by a set of configuration files or templates). The first advantage is that one can easily inspect the target state of infrastructure by reading the configuration files. Second, since the infrastructure is entirely described by the files, changes can be proposed, reviewed, and applied as part of a standard software development workflow.</p><h3>What’s Missing?</h3><p>Kubernetes <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/">YAML configuration files</a> already implement declarative updates to objects in Kubernetes. The user only need edit an object’s YAML file and then run $ kubectl apply -f theobject.yaml to sync the changes to Kubernetes. These YAML files may be checked into source control, and if needed the user can query Kubernetes to inspect the difference between the live version and the local file.</p><p>However, one runs into pain points when attempting to apply this methodology to a larger production environment:</p><ol><li>Common structures cannot be shared across YAML files. You typically set up a service not once but several times, perhaps in {dev, staging, prod} environments, across different geographic regions such as {us-west, us-east, asia-pacific}, and Cloud providers {AWS, Azure, GCE}.</li><li>YAML files must often reference metadata about externally defined entities such as relational databases, networking configurations, or SSL certificates.</li><li>Complex multi-tier service deployments may require the composition of many distinct resources or YAML files. Updating a deployment composed of many such files becomes burdensome.</li></ol><p>In this blog post, we describe how we use Google’s <a href="http://jsonnet.org/">Jsonnet configuration language</a> to solve these problems, walk through an example of templatizing a service deployment using Jsonnet, and propose a <a href="https://github.com/databricks/jsonnet-style-guide">Jsonnet style guide</a> for the infrastructure templating use case. We’ve found that Jsonnet is easy to get started with and scales well to complex use cases.</p><h3>Jsonnet Usage at Databricks</h3><p>Late 2015 we started experimenting with Jsonnet as part of the effort for <a href="https://databricks.com/blog/2016/06/07/dce-ga.html">Databricks Community Edition</a>, our free tier Apache Spark service for education. Since then, Jsonnet has exploded in popularity within Databricks engineering, with over 40,000 lines of Jsonnet in 1000+ distinct files checked into our main development repository. These templates expand to hundreds of thousands of lines of raw YAML once materialized.</p><p>Teams at Databricks currently use Jsonnet to manage configurations for Kubernetes resources (including internal configurations of services running on Kubernetes), <a href="https://aws.amazon.com/cloudformation/">AWS CloudFormation</a>, <a href="https://www.terraform.io/">Terraform</a>, <a href="https://docs.databricks.com/user-guide/jobs.html">Databricks Jobs</a>, and also for tasks such as <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">TLS cert management</a> and defining <a href="https://prometheus.io/docs/practices/alerting/">Prometheus alerts</a>. Over the past two years, Jsonnet has grown to become the de-facto standard configuration language within engineering.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/929/0*Td-OuAy4NTsmga5F.png" /></figure><h3>Jsonnet Basics</h3><p><a href="http://jsonnet.org/">Jsonnet</a> is a configuration language that helps you define <a href="http://www.json.org/">JSON</a> data. The basic idea is that some JSON fields may be left as variables or expressions that are evaluated at compilation time. For example, the JSON object {&quot;count&quot;: 4} may be expressed as {count: 2 + 2} in Jsonnet. You can also declare hidden fields with “::” that can be referenced during compilation, e.g. {x:: 2, y:: 2, count: $.x + $.y} also evaluates to {&quot;count&quot;: 4}.</p><p>Jsonnet objects may be <em>subclassed</em> by concatenating (“+”) objects together to override field values, e.g. suppose we define the following.</p><pre>local base = {<br>   x:: error &quot;you must define the value of &#39;x&#39;&quot;,<br>   y:: 2,  // this field has a default value<br>   count: $.x + $.y<br>};</pre><p>Then, the Jsonnet expression (base + {x:: 10}) will compile to {&quot;count&quot;: 12}. In fact, the Jsonnet compiler requires you to override x since the default value raises an error. You can, therefore, think of base as defining an abstract base class in Jsonnet.</p><p>Jsonnet compilation is completely deterministic and cannot perform external I/O, making it ideal for defining configuration. We’ve found that Jsonnet strikes the right balance between restrictiveness and flexibility — previously we generated configurations using Scala code, which erred too much on the side of flexibility, leading to many headaches.</p><p>At Databricks, we extended our <a href="https://kubernetes.io/docs/user-guide/kubectl-overview/">kubectl</a> tool (dubbed kubecfg) so that it can take Jsonnet files directly as arguments. Internally it compiles the Jsonnet to plain JSON / YAML before sending the data to Kubernetes. Kubernetes then creates or updates objects as needed based on the uploaded configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/541/0*YM3A1sPd-JWMdt48.png" /></figure><h3>Composing Kubernetes Objects with Jsonnet</h3><p>To better understand how Jsonnet can be used with Kubernetes, let’s consider the task of deploying an idealized [1] single-tenant “Databricks platform” for an enterprise customer. Here we want to create two distinct but related service deployments: a Webapp (for the interactive workspace) and Cluster manager (for managing Spark clusters). In addition, the services need access to an <a href="https://aws.amazon.com/rds/">AWS RDS database</a> to store persistent data.</p><p>[1]: Note that we don’t actually create individual Jsonnet files for each customer in reality — that would create a frightening number of Jsonnet files and would in itself be a problem.</p><h3>Template for Defining Kubernetes Deployments</h3><p>For these examples, we’ll be using <a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/service-deployment.jsonnet.TEMPLATE">service-deployment.jsonnet.TEMPLATE</a>, a simplified version of one of our internal base templates that define a Kubernetes <a href="https://kubernetes.io/docs/concepts/services-networking/service/">service</a> and <a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">deployment</a> together as a pair. The instantiated service and deployment together constitute a standalone “production service” in Kubernetes that can receive network traffic. Note that the template has two required arguments in addition to several optional arguments, including optional config passed to the service binary itself:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/service-deployment.jsonnet.TEMPLATE">service-deployment.jsonnet.TEMPLATE</a>:</p><pre>// Template for a Kubernetes (service, deployment) pair.<br>{<br>  // Required arguments for this template<br>  serviceName:: error &quot;serviceName must be specified&quot;,<br>  dockerImage:: error &quot;dockerImage must be specified&quot;,</pre><pre>  // Optional arguments for this template.<br>  serviceConf:: {}<br>  ...<br>}</pre><h3>Example 1: One File for Each Service Deployment</h3><p>Given service-deployment.jsonnet.TEMPLATE, the simplest option we have is to create separate Jsonnet files for the Webapp and Cluster manager. In each file we must import the base template, subclass it, and fill in the required parameters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/431/0*fZJqZ-9vdJxdX1JZ.png" /></figure><p>We specify the service name, the Docker image containing the service binary, and some service specific configurations including the RDS address. In this example, the RDS address is hard-coded, but later on we’ll show how to import that metadata from e.g. the output file of running a CloudFormation script.</p><p>The following file describes the manager service (here service takes on its standard meaning, we’ll refer to <em>Kubernetes services</em> explicitly as such going on). We also construct service-specific configurations within the Jsonnet template via the serviceConf:: field, which the template passes to the pod as an environment variable. We’ve found it useful to unify service and Kubernetes configs in this way:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/simple/foocorp-manager.jsonnet">simple/foocorp-manager.jsonnet</a>:</p><pre>local serviceDeployment = import &quot;../service-deployment.jsonnet.TEMPLATE&quot;;</pre><pre>// The cluster manager deployment for foocorp.<br>serviceDeployment + {<br>  serviceName:: &quot;foocorp-manager&quot;,<br>  dockerImage:: &quot;manager:2.42-rc1&quot;,<br>  serviceConf:: {<br>    customerName: &quot;foocorp&quot;,<br>    database: &quot;user-db.databricks.us-west-2.rds.amazonaws.com&quot;,<br>  },<br>}</pre><p>For the webapp service to create clusters, it must specify the cluster manager’s <a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/">Kubernetes DNS</a> address, which can be determined ahead of time from the Kubernetes service name:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/simple/foocorp-webapp.jsonnet">simple/foocorp-webapp.jsonnet</a>:</p><pre>local serviceDeployment = import &quot;../service-deployment.jsonnet.TEMPLATE&quot;;</pre><pre>// The webapp deployment for foocorp.<br>serviceDeployment + {<br>  serviceName:: &quot;foocorp-webapp&quot;,<br>  dockerImage:: &quot;webapp:2.42-rc1&quot;,<br>  serviceConf:: {<br>    customerName: &quot;foocorp&quot;,<br>    database: &quot;user-db.databricks.us-west-2.rds.amazonaws.com&quot;,<br>    managerAddress: &quot;foocorp-manager.prod.svc.cluster.local&quot;,<br>  },<br>}</pre><p>If you have the <a href="https://github.com/databricks/jsonnet-style-guide">example code repo</a> cloned, you can view the materialized outputs of these templates by running the jsonnet compiler on the files, e.g.:</p><p>$ jsonnet examples/databricks/simple/foocorp-webapp.jsonnet</p><p>So what has this gotten us? We’ve managed to at least remove some of the standard Kubernetes boilerplate around defining services, reducing each deployment definition from over 100 lines to about 10. However, we can do better — there are still duplicated parameters which would make this pattern hard to maintain if there were many distinct customers or more services required per customer.</p><h3>Example 2: Composing Together Deployments in a Single File</h3><p>Since both the Webapp and Cluster manager services are deployed together as a unit or <em>shard</em> per customer, it makes sense that there should just be one file per customer describing their unique requirements. It is indeed possible to define a single template, <a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v1/shard.jsonnet.TEMPLATE">shard.jsonnet.TEMPLATE</a>, that composes together both the Webapp and Cluster manager deployments without duplication of params.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/376/0*2sIJwhVOEM3Ccwh0.png" /></figure><p>The trick here is that the template defines a Kubernetes “List” object, which can include multiple Kubernetes resources within one JSON object. We merge the sub-lists produced by the service-deployment templates using the Jsonnet <a href="http://jsonnet.org/docs/stdlib.html">standard library function</a> std.flattenArrays:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v1/shard.jsonnet.TEMPLATE">shard-v1/shard.jsonnet.TEMPLATE</a>:</p><pre>local serviceDeployment = import &quot;../service-deployment.jsonnet.TEMPLATE&quot;;</pre><pre>{<br>  // Required arguments<br>  customerName:: error &quot;customerName must be defined&quot;,<br>  release:: error &quot;release must be defined&quot;,</pre><pre>  // Optional arguments<br>  commonConf:: {<br>    customerName: $.customerName,<br>    database: &quot;user-db.databricks.us-west-2.rds.amazonaws.com&quot;,<br>  },</pre><pre>  local webapp = serviceDeployment + ...<br>  local manager = serviceDeployment + ...</pre><pre>  kind: &quot;List&quot;,<br>  items: std.flattenArrays([webapp, manager]),<br>}</pre><p>Now we can conveniently define FooCorp’s entire deployment without any duplicated data, and using only one file:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v1/foocorp-shard.jsonnet">shard-v1/foocorp-shard.jsonnet</a>:</p><pre>local shardTemplate = import &quot;shard.jsonnet.TEMPLATE&quot;;</pre><pre>shardTemplate + {<br>  customerName:: &quot;foocorp&quot;,<br>  release:: &quot;2.42-rc1&quot;,<br>}</pre><h3>Example 3: Subclassing Templates for Multiple Environments</h3><p>To see the flexibility of Jsonnet, consider how you might subclass the templates defined in the above examples to create shards that run in the development environment. In dev, deployments should use the dev database instead of the production one, and we also want to run with the latest bleeding-edge Docker images. To do this, we can further subclass the shard.jsonnet.TEMPLATE to different environments. This makes it possible to create specific production or development shards from these specialized templates:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/618/0*hiWOkAjPXWSMnVgK.png" /></figure><p>To make the interface exposed by shard.jsonnet.TEMPLATE more explicit, instead of exporting an object we will export a function newShard(name, release, env), which can be called to construct the shard object. The env object encapsulates the differences between environments (e.g. database URL).</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v2/shard.jsonnet.TEMPLATE">shard-v2/shard.jsonnet.TEMPLATE</a>:</p><pre>local serviceDeployment = import &quot;../service-deployment.jsonnet.TEMPLATE&quot;;</pre><pre>local newShard(customerName, release, env) = {<br>  local commonConf = {<br>    customerName: customerName,<br>    database: env.database,<br>  },</pre><pre>  local webapp = serviceDeployment + {<br>    serviceName:: customerName + &quot;-webapp&quot;,<br>    dockerImage:: &quot;webapp:&quot; + release,<br>    serviceConf:: commonConf + {<br>      managerAddress: customerName + &quot;-manager.prod.svc.cluster.local&quot;,<br>    },<br>  },</pre><pre>  local manager = serviceDeployment + {<br>    serviceName:: customerName + &quot;-manager&quot;,<br>    dockerImage:: &quot;manager:&quot; + release,<br>    serviceConf:: commonConf,<br>  },</pre><pre>  apiVersion: &quot;v1&quot;,<br>  kind: &quot;List&quot;,<br>  items: std.flattenArrays([webapp.items, manager.items]),<br>};</pre><pre>// Export the function as a constructor for shards<br>{<br>  newShard:: newShard,<br>}</pre><p>The dev shard template now just needs to fill in the required env for dev and specify the bleeding-edge release by default. It obtains the env by importing a plain JSON file containing the needed database metadata:</p><p><a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v2/dev-shard.jsonnet.TEMPLATE">shard-v2/dev-shard.jsonnet.TEMPLATE</a>:</p><pre>local shardTemplate = import &quot;shard.jsonnet.TEMPLATE&quot;;<br>local devEnv = import &quot;dev-env.json&quot;;</pre><pre>local newDevShard(shardName, release=&quot;bleeding-edge&quot;) = (<br>  shardTemplate.newShard(shardName, release, env=devEnv)<br>);</pre><pre>{<br>  newDevShard:: newDevShard,<br>}</pre><p>The full example code for the prod and dev shard templates can be found here: <a href="https://github.com/databricks/jsonnet-style-guide/tree/master/examples/databricks/shard-v2">https://github.com/databricks/jsonnet-style-guide/tree/master/examples/databricks/shard-v2</a></p><p>Note that the use of functions for subclassing is optional — we could have instead derived <a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v2/dev-shard.jsonnet.TEMPLATE">dev-shard.jsonnet.TEMPLATE</a> and <a href="https://github.com/databricks/jsonnet-style-guide/blob/master/examples/databricks/shard-v2/prod-shard.jsonnet.TEMPLATE">prod-shard.jsonnet.TEMPLATE</a> using only field overrides. However, in our experience, the use of ad-hoc field overrides, though powerful, tends to lead to more brittle templates. We’ve found it useful to establish best practices to limit the use of such constructs in larger templates.</p><h3>Jsonnet Style Guide</h3><p>In the above examples, we saw how Jsonnet templates can be used to remove duplication of configuration data between Kubernetes objects, compose multiple deployments together, and reference external entities. This greatly simplifies infrastructure management. In large projects, however, templates themselves may become a source of complexity. We’ve found this to be manageable with some best practices and investment in tooling around Jsonnet:</p><p><strong>Here are a few Jsonnet best practices we’ve found:</strong></p><ol><li>For large templates (&gt;10 parameters), avoid directly overriding internal fields when subclassing. Rather, define explicit constructors for templates using Jsonnet functions, which helps with readability and encourages modularity.</li><li>Check your Jsonnet configurations into source control.<br>- Add pre-commit tests to make sure all checked in templates compile. This avoids inadvertent breakages when common templates are updated. You can also create “unit tests” using <a href="http://jsonnet.org/docs/stdlib.html#assertions_debugging">Jsonnet assert expressions</a> that assert invariants over template variables.<br>- Consider also checking in the Jsonnet materialized JSON / YAML to make changes more visible during code review. Because common Jsonnet templates may be imported by many files, changing one file can affect the output of many. Fortunately, unintended changes can be detected at compile time if the materialized YAML is also included with the Jsonnet changes.<br>- Refactor often — there’s no risk of breakage. Since Jsonnet templates compile to concrete JSON / YAML files, it’s possible to check the before and after outputs to ensure the refactoring was correct (i.e. zero net materialized diff).</li><li>Push as much configuration to Jsonnet as possible. It may be tempting to introduce logic into services based on some environment flag (e.g. dev vs prod). We’ve found this to be an anti-pattern — such configuration should be determined at template compile time rather than at runtime. This principle of <em>hermetic configuration</em> helps prevent surprises when services are deployed to new environments.</li></ol><p><strong>For more detailed recommendations, check out our newly posted </strong><a href="https://github.com/databricks/jsonnet-style-guide"><strong>Jsonnet style guide</strong></a>. We also want to welcome any comments or contributions there from the community.</p><h3>Conclusion</h3><p>In this blog post, we’ve shared our experience at Databricks using Jsonnet to simplify infrastructure management. As a unifying templating language that enables composition across a broad range of infrastructure and services, Jsonnet brings us one step closer to a totally declarative infrastructure. We’ve found Jsonnet easy to use and flexible enough to serve as our primary configuration language, and suggest you try it too!</p><p>If you found this topic interesting, watch out for future blog posts on the tooling we’ve built around Jsonnet and Kubernetes. We’re also <a href="https://databricks.com/company/careers">hiring</a> for Cloud platform, infrastructure, and <a href="https://databricks.com/blog/2017/06/07/databricks-serverless-next-generation-resource-management-for-apache-spark.html">Databricks Serverless</a>.</p><p><em>This article was originally published on the </em><a href="https://databricks.com/blog/2017/06/26/declarative-infrastructure-jsonnet-templating-language.html"><em>Databricks Blog</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e33d97e862fd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/databricks-engineering/declarative-infrastructure-with-the-jsonnet-templating-language-e33d97e862fd">Declarative Infrastructure with the Jsonnet Templating Language</a> was originally published in <a href="https://medium.com/databricks-engineering">Databricks Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>