<?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[Stories by zeotap on Medium]]></title>
        <description><![CDATA[Stories by zeotap on Medium]]></description>
        <link>https://medium.com/@zeotapstories?source=rss-bb1de2321a69------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*6OQP3t8kgSvps_dJyzuhQQ.png</url>
            <title>Stories by zeotap on Medium</title>
            <link>https://medium.com/@zeotapstories?source=rss-bb1de2321a69------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 06:55:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@zeotapstories/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Asynchronous Spark jobs using Apache Livy — A Primer — Zeotap]]></title>
            <link>https://medium.com/@zeotapstories/asynchronous-spark-jobs-using-apache-livy-a-primer-zeotap-2052d06e3a28?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/2052d06e3a28</guid>
            <category><![CDATA[hadoop]]></category>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[apache]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 28 Nov 2019 16:11:13 GMT</pubDate>
            <atom:updated>2019-12-05T14:40:03.324Z</atom:updated>
            <content:encoded><![CDATA[<h3>Asynchronous Spark jobs using Apache Livy — A Primer — Zeotap</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rgTyc1yPVRUu0wrbijecWA.jpeg" /></figure><p>At zeotap, a large amount of structured and unstructured data from data partners is transformed and converted to easily queryable, standardized data sets, which are fanned out to a variety of destinations. For this processing, the transformation jobs use Apache Spark as the distributed computing framework, with a fair share of them being batch processing jobs. Batch processing is defined as a non-continuous processing of data. A lot of the batch processing done at zeotap regularly processes files at a given frequency, with the input data being gathered in between the processing jobs.</p><p>We currently use two different types of systems to launch and manage these Spark jobs.</p><p>An introduction to Oozie, and how we use it has been given <a href="https://medium.com/@zeotapstories/oozie-its-pouring-data-1dfb7bfb635b">here </a>. In this article, we will be focussing on Livy, and how we use it at zeotap.</p><h3>Oozie vs Livy</h3><p>Oozie is a workflow management system, which allows for launching and scheduling various MapReduce jobs. A workflow is defined as a Directed Acyclic Graph (DAG) of actions (which can include Spark Batch Jobs), which is defined in an XML File.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*oUnAvsb-o3grxwFn.jpg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*XfwMvpWNjxEZPevK.jpg" /></figure><p>Livy, on the other hand, is a REST interface with a Spark Cluster, which allows for launching, and tracking of individual Spark Jobs, by directly using snippets of Spark code or precompiled jars.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*Axuz_5F4qcHzz-ui.jpg" /></figure><p>Both these systems can be used to launch and manage Spark Jobs, but go about them in very different manners. Where can we use Oozie, and where can we use Livy?</p><h3>Oozie</h3><p>Oozie can be used when the processing flow involves multiple steps, each of which is dependent on the previous one. This is why a DAG is required to launch Oozie Workflows. A simple example of such a dependency would be of running a script to check whether the expected input files are present or not before the actual processing takes place on those input files.</p><p>When oozie launches a spark job, it first launches an ‘oozie-launcher’ container on a core node of the cluster, which in turn launches the actual Spark Job. Oozie uses this oozie-launcher container to track and wait for Spark job processing.</p><h3>Pros:</h3><ul><li><strong>Workflow Management</strong> — Oozie supports coordinator and workflow management. It is a more generic framework, as compared to Livy (which is just used for launching and managing Spark Jobs specifically), supporting running and scheduling of workflows.</li><li><strong>Versatile</strong> — Oozie supports much more than just Spark jobs, and is used for managing a wide variety of Hadoop jobs, such as Pig, Hive, Sqoop, Distcp to name a few more. This gives Oozie a large variety of use cases.</li></ul><h3>Cons:</h3><ul><li><strong>Cluster Choke</strong> — Oozie launches an oozie-launcher container (Fig. 2), for each job it launches. When multiple jobs are launched concurrently, there is a possibility that all the oozie-launcher are running on the cluster, but none of the Spark Jobs. Now those oozie-launcher containers are waiting for their respective Spark jobs to complete, but none of the actual Spark jobs have been launched. This means the cluster is choked with oozie-launchers.</li><li><strong>Jar Hell</strong> — A pro for Oozie, being its versatility in launching multiple types of Hadoop jobs, also turns out to be a reason for a con. Each type of job requires its own jars and version of libraries. This can cause library jar and classpath issues, where different incompatible versions of a library can cause failures in jobs, if not properly set up.</li><li>The above point can also cause migration between/upgrading to newer Spark versions to become a more involved process, as jar version conflicts are bound to crop up when upgrading versions, which are often difficult to resolve.</li><li><strong>Workflow Maintenance</strong> — Need to write workflows to launch even the simplest of jobs. (Fig. 1)</li></ul><h3>Livy</h3><p>Livy can be used to launch ‘asynchronous’ Spark Jobs which are not dependent on, and also do not have any jobs dependent on them. By ‘asynchronous’, we mean that these jobs can be launched at any time, and we do not need to wait for the response of the job.</p><p>Since jobs launched through Livy are asynchronous, they can just be launched by a parent processing workflow, and then can continue on its own without waiting for the response about this job. Such jobs would often produce results that are further also asynchronously consumed. Some such examples are:</p><ol><li>Data quality reporting jobs, involving stats such as row counts, column cardinality, etc. would write to a database, which is then further consumed for dashboard generation.</li><li>Terminal jobs of a pipeline, such as jobs to prepare and export data to an external system.</li></ol><p>But, this does not mean that Livy’s features cannot be used to Orchestrate workflows. We could possibly wait for a job’s response, by simply polling the API. Indeed using this, at zeotap, we have leveraged Livy to launch and monitor spark jobs, and built one part of a decision making, and orchestrating framework, which supports workflow management, similar to Oozie.</p><h3>Pros:</h3><ul><li>Simple HTTP interface to launch, interact and track Spark Jobs.</li><li>Unlikely to cause any jar issues, as it is pre-bundled with the requisite jars, and just having Spark set up correctly on the cluster is enough.</li><li>Easy migration between Spark Versions. If the job runs on the newer Spark Version, Livy will launch it.</li><li>Do not need to write additional workflow files, and properties to launch jobs.</li><li>Will not choke clusters with additional tracking jobs (like ‘oozie-launcher’ — Fig. 2, 3).</li></ul><h3>Cons:</h3><ul><li>Less versatile than Oozie for launching jobs supports only Spark jobs.</li><li>Not a workflow management system. Purely used to launch and track single Spark Jobs. But, a workflow management system can be built around this.</li></ul><h3>Livy Features</h3><p>Spark allows launching two types of data analysis options:</p><ol><li>Interactive: Using spark-shell and pyspark. This launch mode allows for commands to be submitted at runtime.</li><li>Batch: Using spark-submit to launch an application on a cluster, with pre-compiled code, without any interaction at run time.</li></ol><p>Livy supports these two modes using the REST interface as well.</p><h3>Interactive / Session</h3><p>In Interactive Mode (or Session mode as Livy calls it), first, a Session needs to be started, using a POST call to the Livy Server. This will start an Interactive Shell on the cluster for you, similar to if you logged into the cluster yourself and started a spark-shell.</p><h3>Batch</h3><p>In batch mode, the code to be executed needs to be already pre-written in a file; This could be a python file or compiled jar, which can then be stored on the cluster and used to launch the job.</p><h4>Create Session:</h4><pre>curl -X POST \http://&lt;Livy Endpoint&gt;/sessions \<br>-H‘Cache-Control:no-cache’\<br>-H ‘Content-Type: application/json’ \<br>-d ‘{<br>“driverMemory” : “1g”,<br>“executorMemory” : “1g”,<br>“numExecutors” : 2,<br>“name” : “livy”,<br>“conf” : {<br>“spark.test.conf” : “conf1”<br>}<br>}’</pre><p>Response:</p><pre>{<br>“id”: 0,<br>“appId”: null,<br>“owner”: null,<br>“proxyUser”: null,<br>“state”: “starting”,<br>“kind”: “shared”,<br>“appInfo”: {<br>“driverLogUrl”: null,<br>“sparkUiUrl”: null<br>},<br>“log”: [<br>“stdout: “,<br>“\nstderr: “,<br>“\nYARN Diagnostics: “<br>]<br>}</pre><p>In both Session and Batch mode, on launching the Spark job, Livy will respond with an Integer ID which can be used for tracking the state of the Session/Batch.</p><p>Check Session State:</p><pre>curl -X GET \<br>http://&lt;Livy Endpoint&gt;/sessions/1/state</pre><p>Response:</p><pre>{<br>“id”: 1,<br>“state”: “idle”<br>}</pre><h3>Setting Up Livy</h3><p>Setting up Livy on a Hadoop Cluster is fairly straightforward. The prerequisites to run Livy are that the SPARK_HOME and HADOOP_CONF_DIR environment variables need to be set up on the Master Node.</p><p>If these are set, then out of the box, after extracting the Livy package to the installation folder, we can run it directly, using :</p><pre>./bin/livy-server start</pre><h3>Configuration</h3><p>By default, Livy uses configuration files present in the conf directory in the install directory. This can be changed by setting LIVY_CONF_DIR environment variables before running.</p><p>The configurations mentioned below need to be added to a file livy.conf in the configuration directory.</p><p><strong>livy.spark.master:</strong> What spark master Livy sessions should use.</p><ul><li>By default, it is set to local. Set it to yarn to launch using Hadoop YARN Resource Manager</li></ul><p><strong>livy.spark.deploy-mode </strong>: What spark deploy mode Livy sessions should use.</p><ul><li>It is strongly recommended to use cluster deploy mode when using Livy with YARN. This ensures that the host running the Livy server doesn’t become overloaded when multiple sessions/jobs are running.</li></ul><p><strong>livy.server.yarn.app-lookup-timeout</strong> : How long Livy will wait and try to launch a session/job. If the cluster is busy, Livy might not be able to launch the job within this time limit. Increase this for a larger tolerance of a busy cluster.</p><p><strong>livy.server.recovery.mode:</strong> Recovery mode of Livy. Possible values:</p><ul><li>off: Default. Turn off recovery. Every time Livy shuts down, it stops and forgets all sessions.</li><li>recovery: Livy persists session info to the state store. When Livy restarts, it recovers previous sessions from the state store.</li><li>It is strongly recommended to set this to recovery. By default, it is in off mode. If set to off, if and whenever Livy shuts down / crashes, all sessions and jobs launched by Livy are forgotten. <em>Furthermore, if any session is currently running, Livy will shut it down as well.</em></li></ul><p>If set to recovery, ensure that the following two configs are also setup:</p><p><strong>livy.server.recovery.state-store</strong> : Where Livy should store the state for recovery. Possible values:</p><ul><li>&lt;empty&gt;: Default. State store disabled.</li><li>filesystem: Store state on a file system.</li><li>zookeeper: Store state in a Zookeeper instance.</li></ul><p><strong>livy.server.recovery.state-store.url</strong>: For filesystem state store, the path of the state store directory. e.g. file:///tmp/livy or hdfs:///.</p><ul><li>For zookeeper, the address to the Zookeeper servers. e.g. host1:port1,host2:port2</li></ul><p>At zeotap, we use filesystem state store on the local system of the master. Livy does not go down very often, and even if it does, storing the state on the local filesystem proves to be reliable enough for the use cases.</p><p>The above configurations should be enough, to allow for a stable Livy usage environment. The following configurations are required only if querying Livy and updating of the state of Spark Jobs using the API is required.</p><p><strong>livy.server.session.state-retain.sec:</strong> How long a finished state of a session should be retained by Livy to be available for querying. By default, it is 10 minutes. Applies to both batch and sessions.</p><p><strong>livy.server.session.timeout:</strong> This is only for sessions (and not batches launched) on Livy. This is the time on how long Livy will wait before timing out an idle session. By default, 1 hour.</p><p>Note: If you are planning to use Apache Livy on newer versions of Amazon EMR, it is already bundled, and only configurations need to be set up.</p><h3>Job Tracking using Livy — Caveats</h3><p>A few things to note about querying Apache Livy to track job states :</p><ul><li>The UI currently will show only 100 oldest jobs/sessions within the state-retain time out. The other jobs will not be visible on the UI but can be queried through the REST API.</li><li>Livy does job tracking through integer job IDs. This could lead to mix-ups if Livy shuts down, and restarts without recovery being set, as the job IDs reset to 0 again.</li><li>Livy logs which it returns from its APIs are usually not useful for debugging. Very limited information is available, usually just the tail end of the logs from the job. Debugging will often involve looking into the yarn application logs instead.</li></ul><p>Apache Livy and Oozie are just two of the constituent technologies used to build the data platform at zeotap. Leveraging these two, and others, we have built a code-driven workflow management system used across multiple products here. A blog about this control plane framework will follow soon!</p><h4><strong>AUTHOR: </strong>Akshay Sasikumar — Data Engineer at zeotap</h4><p><em>Originally published at </em><a href="https://zeotap.com/apachelivy"><em>https://zeotap.com</em></a><em> on November 28, 2019.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2052d06e3a28" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Guide for every UI developer with an aversion to Unit Tests.]]></title>
            <link>https://medium.com/@zeotapstories/a-guide-for-every-ui-developer-with-an-aversion-to-unit-tests-182039c82bcd?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/182039c82bcd</guid>
            <category><![CDATA[jest]]></category>
            <category><![CDATA[rxjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[unit-testing]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Wed, 30 Oct 2019 13:09:38 GMT</pubDate>
            <atom:updated>2019-10-31T09:28:17.539Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*8kvbWbtCm2M8kvaNQZxAJw.png" /></figure><p>Writing tests are analogous to tasting your meal before you serve it. The importance of unit testing is known at all levels in programming but more often is ignored when it comes to UI developers. This post is a brief about how you can start your journey in being a better Frontend Engineer by incorporating these key concepts about unit testing in your code.</p><p>Overview</p><p>1.Importance of Unit testing</p><p>2.Sample App</p><ul><li>Setup</li><li>Writing Unit Tests for Presentational components</li><li>Writing Unit Tests for Services</li><li>Writing Unit Tests for Container Components</li></ul><p>3.Conclusion</p><h3>Importance of Unit testing</h3><p>Writing Unit Tests does seem like an overhead when a you can just test the functionality by using it. For the times you are in such a dilemma you can keep these few points in mind:</p><ol><li><strong>Unit tests not only improve quality but decrease time to debug </strong>: Unit tests help you understand what parts of the app are working as intended and what parts aren’t and hence allow you to narrow down on the cause of bugs much quicker than using console.logs or debuggers.</li><li><strong>We are JS developers!!</strong> : We all as developers have either built test UI components and rough Html to test an underlying logic/service or delayed testing till our Presentational components are done. Writing a unit test allows you to iteratively build a functional component without unnecessary test UI elements.</li><li><strong>Freedom to collaborate</strong> : Working in a team I have often noticed members working on isolated silos of functionality and with a large code base there is a never ending fear of breaking some working code during refactoring and bug fixing. This should and can be avoided if you write proper unit tests along with the code which detects any breakage in case of changes for developers who may work on the code later.</li><li><strong>No Low level Documentation </strong>a unit test declares the purpose of a given unit of code. This reduces the requirement for a developer to explicitly document code ( would also recommend declarative style of programming for all JS developers) and product teams can focus more on the look and feel of the application than on the functionality.<br>Using Test frameworks like Jest also allows you to test Frontend code on your CI/CD environments which is a plus to the point no. 3 as it helps generate regular reports on your code health and test coverage.</li></ol><p>Here are some key guidelines that you should keep in mind while writing unit tests :</p><ol><li>Understanding the type of unit tests that should be written depends on the type of app component( Presentational, Logic Containers,Services,etc). Understanding what should be tested really helps in reasoning the extra effort you are taking in writing unit tests at each level.</li><li>Write Functional JS and try to break down your app into Presentational and Logic components as much as possible. This really helps in improving the focus of your unit tests and also decreases the time taken to write them.</li><li>Write Tests along with the code. <strong>This is By far the most Important one !!</strong> I can’t stress enough on how painful it has been for me to revisit old code and add unit tests for already developed components. It requires both time and effort to figure out what you have written and what to test. When Tests are written our aim should be to write code that pass tests rather than the other way around.</li><li>Practice writing tests before you dive into writing your app. Most developers avoid writing tests because they either don’t know or are not completely sure about some basics like Mocking a Class , testing an async call, mocking http calls etc. Get rid of these confusions and myths with practice. So practice unit testing as much as you practice writing application code.</li></ol><p>Having Understood the importance of writing tests we are going to go through an example Angular App and write some unit tests around it with Jest.</p><h3>Why Jest ?</h3><p>Jest is beautiful testing framework that provides a uniform and non-browser based unit testing options for multiple javascript frameworks.</p><p><a href="https://jestjs.io/">Find more about them here</a>.</p><p>Also a shoutout to <a href="https://github.com/thymikee/jest-preset-angular">jest-angular-preset</a> library that makes it easy to use jest with angular. With jest I get three great features which aren’t present with default angular testing setup : Snapshot testing, Unit tests that can run without browser and AutoMocking. I suggest everyone to understand these to use this wonderful framework to its fullest.</p><h3>Setup :</h3><p>If you have never used angular before please follow the official angular setup guide <a href="https://angular.io/guide/setup-local">here</a></p><p>Our App will have three major Component : AppComponent, ListingService, ListRowComponent. But before we start writing our components and test cases, We have to setup jest.</p><h4>Steps to setup jest :</h4><p><a href="https://blog.angularindepth.com/angular-cli-ng-test-with-jest-in-3-minutes-v2-1060ddd7908d">Use this quick guide to do the initial setup, remove karma based code, and run jest.</a></p><p>Jest allows you to store your config in either a jest field in your package.json or in a separate file jest.config.js</p><p>I would suggest everyone should go through the <a href="https://jestjs.io/docs/en/configuration">official config guide</a> once to know what kind of configurations your project can have and might need. To help you guys I would recommend at-least focusing on the following fields: setupFilesAfterEnv, coverageDirectory, coverageReporters, transformIgnorePatterns, modulePathIgnorePatterns, moduleNameMapper, testPathIgnorePatterns</p><p>Here is jest.config.js from our sample app</p><pre>module.exports = {<br>    &quot;preset&quot;: &quot;jest-preset-angular&quot;,<br>    &quot;setupFilesAfterEnv&quot;: [&quot;&lt;rootDir&gt;/setupJest.ts&quot;],<br>    globals: {<br>      &quot;ts-jest&quot;: {<br>        tsConfig: &#39;&lt;rootDir&gt;/tsconfig.spec.json&#39;,<br>        &quot;diagnostics&quot;:false,<br>        &quot;allowSyntheticDefaultImports&quot;: true,<br>        &quot;stringifyContentPathRegex&quot;: &quot;\\.html$&quot;,<br>        astTransformers: [require.resolve(&#39;jest-preset-angular/InlineHtmlStripStylesTransformer&#39;)],<br>      }<br>    },<br>    coverageDirectory:&#39;&lt;rootDir&gt;/output/coverage/jest&#39;,<br>    transformIgnorePatterns: [&quot;node_modules/&quot;],<br>    &quot;coverageReporters&quot;: [<br>      &quot;text&quot;,<br>      &quot;json&quot;,<br>    ],<br>    &quot;reporters&quot;: [<br>      &quot;default&quot;,<br>    ],<br>    snapshotSerializers: [<br>      &#39;jest-preset-angular/AngularSnapshotSerializer.js&#39;,<br>      &quot;jest-preset-angular/AngularSnapshotSerializer.js&quot;,<br>      &quot;jest-preset-angular/HTMLCommentSerializer.js&quot;<br>    ],<br>    &quot;transform&quot;: {<br>      &#39;^.+\\.(ts|html)$&#39;: &#39;ts-jest&#39;,<br>      &quot;^.+\\.js$&quot;: &quot;babel-jest&quot;,<br>    },<br>    modulePathIgnorePatterns: [],<br>    moduleNameMapper: {},<br>    testPathIgnorePatterns:[&#39;sampleCodes/&#39;],<br>  };</pre><p>Here is my tsconfig.spec.ts</p><pre>{<br>  &quot;extends&quot;: &quot;./tsconfig.json&quot;,<br>  &quot;compilerOptions&quot;: {<br>    &quot;outDir&quot;: &quot;./out-tsc/spec&quot;,<br>    &quot;types&quot;: [&quot;jest&quot;, &quot;node&quot;],<br>    &quot;emitDecoratorMetadata&quot;: true,<br>    &quot;allowJs&quot;: true<br>  },<br>  &quot;files&quot;: [<br>    &quot;src/polyfills.ts&quot;<br>  ],<br>  &quot;include&quot;: [<br>    &quot;src/**/*.spec.ts&quot;,<br>    &quot;src/**/*.d.ts&quot;<br>  ]<br>}</pre><p><strong>Note:</strong> Don’t simply copy and paste the code but understanding the config really helps you in setting up your entire config for your project on your own.</p><p>I would also suggest install jest globally</p><p>npm install -g jest</p><p>This really helps when running jest cli commands required for snapshot testing ( like updating snapshots using jest -u)</p><p>Finally run jest and check if the basic tests that are auto created with ng generate are running using</p><p>jest --coverage</p><p><a href="https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change">Here is a great guide on how to test components and improve our test cases and how the DOM Testing library helps in this</a></p><h3>Writing Unit Tests for Presentational components</h3><p>If you are in practice of writing Pure Presentational components then you are awesome!!. If you aren’t I suggest you start practicing on how to divide your app code into Logic Containers and Presentational Components.</p><p>Jest has the ability to use Snapshot testing for testing the UI components. <a href="https://jestjs.io/docs/en/snapshot-testing">Read more about Snapshot testing here</a></p><p>This saves time spent writing DOM queries. As per the documentation one should commit these snapshots with your code so you can verify how your UI components should be rendered in DOM.</p><h4>When Not to use snapshots ?</h4><p>If the component is basic and simple enough ,snapshot testing should cover most of your UI tests, though avoid using it with Presentational Components like Lists where you would want to check the total number of rendered rows or in components where verification of business logic representation is required.</p><p>Below Find Sample ListRowComponent</p><pre>@Component({<br>  selector: &#39;app-list-row-component&#39;,<br>  templateUrl: &#39;./list-row-component.component.html&#39;,<br>  styleUrls: [&#39;./list-row-component.component.scss&#39;],</pre><pre>})<br>export class ListRowComponentComponent implements OnInit {</pre><pre>  @Input() firstName:string;<br>  @Input() lastName:string;<br>  @Input() gender:string;<br>  @Output() rowClick = new EventEmitter();</pre><pre>  getClass(){<br>    return {<br>      &#39;blue&#39;:this.gender===&#39;male&#39;,<br>      &#39;green&#39;:this.gender===&#39;female&#39;<br>    }<br>  }<br>  constructor() { <br>  }<br>  ngOnInit() {<br>  }<br>}</pre><p>Below Find Sample ListRowComponent.spec file</p><pre>describe(&#39;ListRowComponentComponent&#39;, () =&gt; {<br>  let component: ListRowComponentComponent;<br>  let fixture: ComponentFixture&lt;ListRowComponentComponent&gt;;<br></pre><pre>  beforeEach(async(() =&gt; {<br>    TestBed.configureTestingModule({<br>      declarations: [ ListRowComponentComponent ]<br>    })<br>    .compileComponents();<br>  }));</pre><pre>  beforeEach(() =&gt; {<br>    fixture = TestBed.createComponent(ListRowComponentComponent);<br>    component = fixture.componentInstance;<br>    fixture.detectChanges();<br>  });</pre><pre>  it(&#39;should create&#39;, () =&gt; {<br>    expect(component).toBeTruthy();<br>  });</pre><pre>  it(&#39;should render the component with blue color class&#39;,()=&gt;{<br>    component.firstName = &#39;James&#39;<br>    component.lastName = &#39;Bond&#39;<br>    component.gender = &#39;male&#39;<br>    fixture.detectChanges()</pre><pre>    expect(fixture).toMatchSnapshot();<br>  })<br>  it(&#39;should render the component with green color class&#39;,()=&gt;{<br>    component.firstName = &#39;James&#39;<br>    component.lastName = &#39;Bond&#39;<br>    component.gender = &#39;female&#39;<br>    fixture.detectChanges()</pre><pre>    expect(fixture).toMatchSnapshot();<br>  })</pre><pre>  it(&#39;should emit events onClick&#39;,done=&gt;{<br>    let buttonClicked = false<br>    component.rowClick.subscribe(()=&gt;{<br>      buttonClicked =true;<br>      expect(buttonClicked).toBeTruthy()<br>      done();<br>    })<br>    var btn = getByTestId(fixture.nativeElement,&#39;row-click&#39;);<br>    simulateClick(btn);<br>  })<br>});</pre><p><strong>Note:</strong> If you notice I’m using data-testid to query the button in the unit test above. I would suggest all developers to bring this into practice , It makes our tests very resilient to change and robust in nature.</p><h3>Writing Unit Tests for Services</h3><p>First here are some concepts that were confusing to me before I stared writing unit tests for services or containers :</p><p><strong>Mocking Dependencies</strong>. There are a lot of great tutorials available with a simple Google search on this but most use component constructors or promote using auto-mocking features of Jest for Mocking dependencies. It depends on your preference which method you use ,For me mocking dependencies while using Angular’s Dependency Injection to instantiate a component were key and I found a really good way to do that .</p><p><a href="https://www.ng-conf.org/2019/mocking-dependencies-angular/">You can go through this great article about the same</a></p><p><strong>Mocking Store</strong> : It is suggested that we should write getters and selectors for ngrx store ( <a href="https://ngrx.io/">https://ngrx.io/</a> ) in services , so that your components are reusable along with the store. This means mocking a Store in service becomes very important.</p><pre>describe(&#39;Service:StoreService&#39;, () =&gt; {<br>  let backend: HttpTestingController;</pre><pre>  beforeEach(() =&gt; {<br>    TestBed.configureTestingModule({<br>      imports: [HttpClientModule, HttpClientTestingModule, RouterTestingModule],<br>      providers: [<br>        provideMockStore({ initialState }),<br>      ],<br>      schemas:[NO_ERRORS_SCHEMA]<br>    });<br>    backend = TestBed.get(HttpTestingController);<br>  });</pre><pre>  afterEach(inject(<br>    [HttpTestingController],<br>    (_backend: HttpTestingController) =&gt; {<br>      _backend.verify();<br>    }<br>  ));</pre><p><a href="https://ngrx.io/guide/store/testing">know more</a></p><p><strong>Using Marble testing</strong> : Lastly Most Services you will create in your angular projects will use RxJs. To test your services and logic container components properly, understanding how to test these Observables ( best done using <a href="https://www.npmjs.com/package/jasmine-marbles">jasmine-marbles</a> ) is essential.</p><p><a href="https://dev.to/mokkapps/how-i-write-marble-tests-for-rxjs-observables-in-angular-4l0k">Here is a great article by Micheal Hoffman that will help you get a good understanding about the same</a></p><p>Sample Service</p><pre>@Injectable({<br>  providedIn: &#39;root&#39;<br>})<br>export class ListingService {</pre><pre>  constructor(<br>    public http: HttpClient<br>  ) { }</pre><pre>  public getHeaderWithoutToken() {<br>    return new HttpHeaders()<br>      .append(&#39;Content-Type&#39;, &#39;application/json&#39;)<br>      .append(&#39;Accept&#39;, &#39;application/json&#39;);<br>  }</pre><pre>  public getHeader(tokenPrefix = &#39;&#39;) {<br>    let headers = this.getHeaderWithoutToken();<br>    return { headers };<br>  }</pre><pre>  public doGet(url,header=this.getHeader()){<br>    return this.http.get(url,header);<br>  }<br>  public getList() : Observable&lt;UserModel[]&gt;{<br>    return this.doGet(&#39;http://example.com/users&#39;)<br>    .pipe(<br>      map((res:any[])=&gt;{<br>        return res.map(toUserModel)<br>    }))<br>  }<br>}</pre><p>Testing a service using jest</p><pre>describe(&#39;ListingServiceService&#39;, () =&gt; {<br>  let service: ListingService;<br>  let backend: HttpTestingController;</pre><pre>  beforeEach(() =&gt; {<br>    TestBed.configureTestingModule({<br>      imports: [HttpClientModule, HttpClientTestingModule],<br>      providers: [<br>        ListingService<br>      ],<br>      schemas:[NO_ERRORS_SCHEMA,CUSTOM_ELEMENTS_SCHEMA]<br>    });<br>    backend = TestBed.get(HttpTestingController);<br>    service = TestBed.get(ListingService);<br>  });</pre><pre>  afterEach(inject(<br>    [HttpTestingController],<br>    (_backend: HttpTestingController) =&gt; {<br>      _backend.verify();<br>    }<br>  ));</pre><pre>  it(&#39;should be created&#39;, () =&gt; {<br>    expect(service).toBeTruthy();<br>  });</pre><pre>  const url = &#39;http://example.com/users&#39;;<br>  test(&#39;should fetch a list of users&#39;,done=&gt;{<br>    service.getList()<br>    .subscribe(data=&gt;{<br>      expect(data).toEqual(outputArray)<br>      done()<br>    })<br>    backend.expectOne((req: HttpRequest&lt;any&gt;) =&gt; {<br>        return req.url === url &amp;&amp; req.method === &#39;GET&#39;;<br>      }, `GET all list data from ${url}`)<br>      .flush(outputArray);<br>  })<br>});</pre><h3>Writing Unit Tests for Container Components</h3><p>Container Components are complex components and often this complexity can lead to confusion towards how to write unit tests for a container component. To avoid this you can take the shallow and deep testing approach of writing unit tests.</p><p><a href="https://medium.com/simars/testing-angular-container-components-shallow-and-deep-d3bf6d70a8c7">You can learn more about this approach here</a></p><p>Sample App container component</p><pre>@Component({<br>  selector: &#39;app-root&#39;,<br>  templateUrl: &#39;./app.component.html&#39;,<br>  styleUrls: [&#39;./app.component.scss&#39;]<br>})</pre><pre>export class AppComponent implements OnInit{<br>  title = &#39;my-test-app&#39;;<br>  list$ : Observable&lt;UserModel[]&gt;;<br>  constructor(<br>    private listService :ListingService,<br>  ){<br>  }<br>  ngOnInit(){<br>    this.initListService()<br>  }<br>  initListService(){<br>    this.list$ =  this.listService.getList();<br>  }<br>  onClicked(user){</pre><pre>  }<br>}</pre><p>Setting up the Container for unit tests</p><pre>let fixture : ComponentFixture&lt;AppComponent&gt;;<br>  let service : ListingService;<br>  let component : AppComponent;<br>  beforeEach(async(() =&gt; {<br>    TestBed.configureTestingModule({<br>      declarations: [<br>        AppComponent<br>      ],<br>      providers:[<br>        {provide:ListingService,useClass:MockListService}<br>      ],<br>      schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA]<br>    }).compileComponents();<br>  }));<br>  beforeEach(()=&gt;{<br>    fixture = TestBed.createComponent(AppComponent)<br>    component = fixture.debugElement.componentInstance;<br>    service = fixture.componentRef.injector.get(ListingService);<br>    fixture.detectChanges()<br>  })</pre><h4>Writing Shallow Tests</h4><p>Unit tests for testing only parts which are isolated from other components in the current container like whether all DOM components written as part of this component’s template are being rendered as desired, component is being set up by fetching data from services and the component outputs are working as intended.</p><pre>it(&#39;should create the app&#39;, () =&gt; {</pre><pre>    expect(component).toBeTruthy();<br>  });<br></pre><pre>  it(&#39;should render title in a h1 tag&#39;,() =&gt; {<br>    const compiled = fixture.debugElement.nativeElement;<br>    expect(queryByTestId(compiled,&#39;app-title&#39;)).not.toBeNull();<br>    expect(queryByTestId(compiled,&#39;app-title&#39;).textContent).toEqual(component.title)<br>  });</pre><pre>  test(&#39;should fetch the user list from the listing service&#39;,async(()=&gt;{<br>    const spy = jest.spyOn(service,&#39;getList&#39;);<br>    var expectedObservable = cold(&#39;-a&#39;,{a:outputArray})<br>    spy.mockReturnValue(expectedObservable)<br>    component.ngOnInit()<br>    fixture.detectChanges()<br>    expect(spy).toHaveBeenCalled();<br>    expect(component.list$).toBeObservable(expectedObservable)<br>    getTestScheduler().flush()<br>    fixture.detectChanges()<br>    component.list$.subscribe((o)=&gt;{<br>      fixture.detectChanges()<br>      var list = fixture.nativeElement.querySelectorAll(&#39;app-list-row-component&#39;)<br>      expect(list.length).toEqual(outputArray.length)<br>    })<br>    spy.mockRestore()<br>  }))</pre><h4>Writing Deep Tests</h4><p>Set of unit tests where the aim is to check the interaction in the component between the child / internal components and the providers and dispatchers attached to the component.</p><pre>test(&#39;should call onClicked when app-list-row-component is clicked&#39;,()=&gt;{<br>    const spy = jest.spyOn(service,&#39;getList&#39;);<br>    var expectedObservable = cold(&#39;a&#39;,{a:outputArray})<br>    spy.mockReturnValue(expectedObservable)<br>    component.initListService()<br>    getTestScheduler().flush()<br>    var onClicked = spyOn(component,&#39;onClicked&#39;).and.callThrough();<br>    component.list$.subscribe((o)=&gt;{<br>      fixture.detectChanges()<br>      var row0 = fixture.debugElement.query((el)=&gt;{<br>        return el.properties[&#39;data-testid&#39;] === &#39;row0&#39;<br>      }).componentInstance as ListRowComponentComponent<br>      row0.rowClick.emit();<br>      expect(onClicked).toHaveBeenCalledWith(outputArray[0])<br>    })<br>  })</pre><h3>Conclusion</h3><p>Through this article I hope to have given the reader a brief knowledge of the key concepts required to integrate Unit testing into your Frontend code and also some tips on how to write unit tests for complex components and the way you should design your application so it becomes easy to maintain a healthy codebase.</p><p><a href="https://github.com/divye1995/JestAngularTestApp">You can find the entire code for the sample app used in this post here</a></p><p><strong>Please feel free to fork and practice unit testing using this setup.</strong></p><p><strong>AUTHOR:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/269/1*jj7LSptXfWUU9DeU6AWutQ.png" /></figure><p><strong>Divye Marwah — </strong><em>Full stack developer | Software Engineer at Zeotap</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=182039c82bcd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: The Importance of Infrastructure Monitoring with Prometheus and Telegraf]]></title>
            <link>https://medium.com/@zeotapstories/the-zeotech-series-the-importance-of-infrastructure-monitoring-with-prometheus-and-telegraf-7e0093517c7d?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/7e0093517c7d</guid>
            <category><![CDATA[telegraph]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[prometheus]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[infrastructure]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 30 May 2019 14:27:47 GMT</pubDate>
            <atom:updated>2019-05-30T14:27:47.461Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/679/1*xNYPwghmlS-xgU5iDIp-hg.png" /></figure><p>Monitoring is one of the critical yet under-appreciated aspects of infrastructure. Nobody cares when your servers/applications are running fine, but as soon as your website is down or your application is not responding, the first question that comes to our mind is “‘Why is there no monitoring enabled for this piece?”</p><p>For starters, the purpose of monitoring majorly falls into the below categories:</p><ol><li><strong>Alerting:</strong> the most important thing is knowing when something goes wrong. The first step is alerting humans when a defined event occurs aka when some threshold is breached.</li><li><strong>Debugging:</strong> we refer to this when humans look into the alert, investigate and troubleshoot.</li><li><strong>Trending:</strong> when humans identify the trend of the event, whether it’s an anomaly or expected, ex: how the systems are used and usage changes over time. This helps in RCA and capacity planning.</li></ol><p>At zeotap we use AWS for our cloud infrastructure and our resources are majorly deployed in three regions: Asia Pacific (Mumbai), EU (Ireland) and US (North Virginia). We have on-demand EC2 instances running across all the three regions and we primarily use spot instances for our EMR jobs.</p><p>Earlier we used datadog (enterprise version) for our infra monitoring but we found it expensive as our tech stack grew. We found cloudwatch not flexible enough to monitor our applications, so we decided to go with an open source monitoring tool which would fit our needs much better.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9eXPRAUhcwOis4UorwHS_Q.jpeg" /><figcaption>zeotap infrastructure monitoring setup</figcaption></figure><h3>Prometheus</h3><p>Prometheus is a popular open source, metrics-based monitoring system. It is written in GO, so you have a single binary executable, download the binary and run prometheus, as simple as that.</p><p>It stores the metrics scraped in a time series database(TSDB) and provides PROM Query Language (<a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">PromQL</a>) that lets you query over the metrics to see how your applications, services and servers are performing.</p><h3>How Prometheus works</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/0*pryyQ24i-yGrEviU.png" /></figure><p>The Prometheus service is called Prometheus server and the things it monitor/scrapes are called <strong>targets</strong>. The targets could be a server, service, database, http endpoint, script etc. Each unit of a target such as CPU, memory, disk, network usage is called a <strong>metric</strong>. These metrics should be exposed in a prometheus format.</p><p>Prometheus works on pull approach. It <strong>pulls/scrapes metrics from the targets over the HTTP </strong>on regular intervals and stores the metric in TSDB. The metric stored can be queried in the Prometheus UI or we can integrate it to Grafana for rich visualization.</p><p>Prometheus has a configuration file called prometheus.yml, where you can specify the scrape interval, alert rules, and targets under scrape configs.</p><p>You can create multiple jobs under scrape_configs such as</p><ul><li>job_name: EC2 metrics,</li><li>job_name: DB metrics</li></ul><p>and specify the respective targets under them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e2nQaPj-DOtZecI0KfBnpg.png" /><figcaption>In this example, Prometheus scrapes itself.</figcaption></figure><p>You can simply query the metrics stored in prometheus TSDB by typing PromQL expression in the prometheus UI.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YdL5JH02QtNGpBovX6loGA.png" /><figcaption>prometheus UI where we can query the metrics</figcaption></figure><p><strong>How the metrics can be generated in prometheus format</strong></p><p>Prometheus provides client libraries in all popular languages which you can instrument in your own code. Some of the popular softwares like Kubernetes and docker are already instrumented with Prometheus client libraries. For other applications, we can use third party integrations to expose their metrics to prometheus.</p><p>These third-party integration tools are called exporters. They collect the system, application metrics and report them in a Prometheus format on a specified port and path. It has to be installed in respective machines where you want to expose metrics to be monitored by prometheus.</p><p>For example, servers don’t have any mechanism to expose metrics (system stats such as cpu, memory, disk) in prometheus format, so we can use an exporter to get those metrics.</p><p>At zeotap, we use an exporter tool called <a href="https://www.influxdata.com/time-series-platform/telegraf/">telegraf</a> from influxdata. The reason we chose telegraf is, it has integration with all the major softwares Aerospike, Elasticsearch, Mesos, Kafka, Zookeeper which are part of zeotap’s tech stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/750/0*5uGXZN88jocbTYGC" /></figure><p>Below block is a configuration file of telegraf agent.</p><p>The telegraf agent runs at the interval of 60s, it collects host metrics such as CPU, disk, memory, network stats, etc., and outputs it in a Prometheus format on the port “:9999” and in the path “/metrics”.</p><pre>[global_tags]<br>  type = &quot;EC2&quot;</pre><pre>[agent]<br>  interval = &quot;60s&quot;<br>  round_interval = true<br>  metric_batch_size = 1000<br>  flush_interval = &quot;60s&quot;<br>  debug = false<br>  quiet = true<br>  logfile = &quot;/var/log/telegraf/telegraf.log&quot;</pre><pre>[[outputs.prometheus_client]]<br>  listen = &quot;:9999&quot;<br>  path = &quot;/metrics&quot;<br>  expiration_interval = &quot;60s&quot;</pre><pre>[[inputs.cpu]]<br>  percpu = true</pre><pre>[[inputs.disk]]<br>  ignore_fs = [&quot;tmpfs&quot;, &quot;devtmpfs&quot;, &quot;devfs&quot;]</pre><pre>[[inputs.mem]]</pre><pre>[[inputs.net]]</pre><p>If we hit the “ipaddress or hostname:9999/metrics” we can see the metrics exposed by telegraf agent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1002/1*dES5HJR7uuary49HTkH-Nw.png" /><figcaption>Host metrics exposed by telegraf agent</figcaption></figure><p>Telegraf agent updates the metrics as per the interval specified in the telegraf.conf.</p><p>Prometheus scrapes these metrics in an interval defined in the prometheus.yml and stores the data locally in a custom TSDB.</p><h3>Service Discovery</h3><p>So far, we have seen how Prometheus works and how a target can expose their metrics to Prometheus.</p><p>In the Prometheus section, we learned that we can specify the targets under scrape_configs. This is easy to do when you have a handful of servers. What if you have 100s of servers though? Manually adding all the server names in prometheus.yml is not scalable.</p><p>Prometheus has a service discovery feature which allows prometheus to automatically discovers the new instances and scrapes them without manual intervention. We tried using Consul (service discovery tool), but it requires separate agent to be installed in all the target machines. We wanted to go for an agent-less mechanism for service discovery.</p><p>As our infrastructure runs on AWS, we use EC2 service discovery in our monitoring setup. We have created an IAM role with EC2ReadOnlyAccess and attached in our Prometheus server.</p><p>Below code block shows the config to discover the EC2 instances in eu-west-1 region.</p><p>We have added tags “Environment=production” ‘Monitoring = yes” in all of our production instances.</p><p>So when Prometheus queries our EC2 instances in the EU region, AWS provides all list of instances in EU. Prometheus then checks whether the instances are having the tags “Environment=production, Monitoring=yes”. It will drop the instances which don’t have the tags, from its targets list and start scraping the matching instances.</p><p>We are passing tag keys ‘Team, Application, Owner, Role, Project’ as metadata which will be added as a label to the metrics of the targets. This helps us in querying the list of instances belong to particular team, projects.</p><pre>scrape_configs:</pre><pre>  - job_name: &#39;EC2-EU-region&#39;<br>    ec2_sd_configs:<br>      - region: eu-west-1<br>        port: 9999<br>        refresh_interval: 10m<br>    relabel_configs:<br>      - source_labels: [&#39;__meta_ec2_tag_Environment&#39;]<br>        regex: &#39;production&#39;<br>        action: keep<br>      - source_labels: [&#39;__meta_ec2_tag_Monitoring&#39;]<br>        regex: &#39;yes&#39;<br>        action: keep<br>      - source_labels: [&#39;__meta_ec2_availability_zone&#39;]<br>        target_label: &#39;Zone&#39;<br>      - source_labels: [&#39;__meta_ec2_private_ip&#39;]<br>        target_label: &#39;instance&#39;<br>      - source_labels: [&#39;__meta_ec2_tag_Team&#39;]<br>        target_label: &#39;Team&#39;<br>      - source_labels: [&#39;__meta_ec2_tag_Application&#39;]<br>        target_label: &#39;Application&#39;<br>      - source_labels: [&#39;__meta_ec2_tag_Name&#39;]<br>        target_label: &#39;Name&#39;<br>      - source_labels: [&#39;__meta_ec2_tag_Role&#39;]<br>        target_label: &#39;Role&#39;<br>      - source_labels: [&#39;__meta_ec2_tag_Owner&#39;]<br>        target_label: &#39;Owner&#39;<br>      - source_labels: [&#39;__meta_ec2_instance_state&#39;]<br>        target_label: &#39;Instance_Status&#39;<br>      - source_labels: [&#39;__meta_ec2_instance_type&#39;]<br>        target_label: &#39;Instance_Type&#39;</pre><p>This is customizable and Prometheus allows you to configure how metadata from service discovery is mapped to monitoring targets and their labels using <em>relabelling. </em>You can add a tag with key ‘monitoring_worthy’, value as ‘yeah’ or ‘nah’ and tell Prometheus to keep only ‘yeah’ and drop ‘nah’.</p><p>Refresh interval indicates that this service discovery happens every 10 minutes, so any new instance provisioned with above tags will be discovered in 10 minutes. Likewise if the instance is terminated, it will be removed from AWS, so when the refresh happens, the instance will not be identified and Prometheus will not scrape it.</p><p>We are using terraform to provision instances with necessary tags and run Ansible playbook to install/configure telegraf in the instances. So this way the monitoring configuration for instance is done in less than 5 minutes and the instance comes under monitoring in 10 minutes.</p><h3>Alerting</h3><p>We have got instances (targets) which are exposing metrics using telegraf agent (exporters), EC2 service discovery to provide the list of targets to prometheus, prometheus is configured to scrape the metrics from the targets, so the only missing piece is alerting.</p><p>Alerting with Prometheus is separated into two parts.</p><ul><li>Alerting rules in Prometheus send alerts to an Alertmanager.</li><li>The <a href="https://prometheus.io/docs/alerting/alertmanager">Alertmanager</a> then manages those alerts, including silencing, aggregation and sending out notifications via methods such as email, PagerDuty and Slack.</li></ul><p>The main steps to setting up alerting and notifications are:</p><ul><li>Create <a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/">alerting rules</a> in Prometheus</li><li><a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Calertmanager_config">Configure Prometheus</a> to talk to the Alertmanager</li><li>Setup and <a href="https://prometheus.io/docs/alerting/configuration">configure</a> the Alertmanager</li></ul><p><strong>Alerting rules:</strong></p><p>Alerting rules allow you to define alert conditions based on PromQL and attach a severity label.</p><p>Below is an example rule, to alert if disk usage percent is above 95 for 15 minutes.</p><pre>- alert: Critical Disk usage<br>    expr: disk_used_percent &gt; 95<br>    for: 15m<br>    labels:<br>      severity: page<br>    annotations:<br>      summary: &quot;Critical disk usage &gt; 95%&quot;<br>      description: &quot;Critical disk usage on host {{$labels.instance}} : (current value: {{ $value }}).&quot;</pre><p>This alert rule file has to specified in the prometheus.yml.</p><p>Prometheus evaluates the rules as per evaluation interval and if any one of the alert is triggered, it will forward the alert with severity label to the alertmanager, configured under alerting section.</p><pre># Alertmanager configuration<br>alerting:<br>  alertmanagers:<br>    - scheme: http<br>    static_configs:<br>      - targets:<br>        - &#39;alertmanager-ipaddress:9093&#39;</pre><pre>rule_files:<br>  - &quot;/etc/prometheus/alertmanager/hostmetrics_rules.yml&quot;</pre><p><strong>Alert Manager:</strong></p><p>The a<a href="https://github.com/prometheus/alertmanager">lertmanager</a> installation is as straightforward as prometheus. It comes up with an executable binary file and a configuration file. Alertmanager can be installed in the same instance where prometheus is running.</p><p>In configuration, you can specify the receiver of the alerts (team that needs to be notified) and the communication channels (pagerduty, email, slack) based on the severity of the alert.</p><p>We are using three channels for alerting — slack for all types of alerts, email for critical alerts and pagerduty for severe alerts (if the application is down, or the instance is down).</p><p>Below block is the configuration of alertmanager.</p><pre>route:<br>  repeat_interval: 3h<br>  receiver: slack-notifications<br>  routes:<br>    - match:<br>        severity: warning<br>      repeat_interval: 1h<br>      continue: true<br>      receiver: slack-notifications<br>    - match:<br>        severity: critical<br>      repeat_interval: 30m<br>      continue: true<br>      receiver: slack-notifications<br>    - match:<br>        severity: page<br>      repeat_interval: 10m<br>      continue: true<br>      receiver: slack-notifications<br>    - match:<br>        severity: critical<br>      repeat_interval: 30m<br>      continue: true<br>      receiver: email-notifications<br>    - match:<br>        severity: page<br>      repeat_interval: 5m<br>      continue: true<br>      receiver: pagerduty-notifications</pre><pre>receivers:<br>  - name: slack-notifications<br>    slack_configs:<br>      - channel: &#39;prometheus-alerts&#39;<br>        api_url: &#39;&#39;<br>        send_resolved: true<br>        icon_url: <a href="https://avatars3.githubusercontent.com/u/3380462">https://avatars3.githubusercontent.com/u/3380462</a><br>        title: &#39;{{ template &quot;custom_title&quot; . }}&#39;<br>        text: &gt;-<br>          {{ range .Alerts }}<br>            *Alert:* {{ .Annotations.summary }} - `{{ .Labels.severity }}`<br>            *Description:* {{ .Annotations.description }}<br>            *Details:*<br>            {{ range .Labels.SortedPairs }} *{{ .Name }}:* `{{ .Value }}`<br>            {{ end }}<br>          {{ end }}</pre><pre>- name: email-notifications<br>    email_configs:<br>      - to: <a href="/service/mailto:team.dev-ops@zeotap.com">r</a>eceiver@example.com<br>        from: <a href="/service/mailto:alerts@zeotap.com">alerts@example.com</a><br>        smarthost: &quot;smtp.sendgrid.net:587&quot;<br>        send_resolved: true</pre><pre>- name: pagerduty-notifications<br>    pagerduty_configs:<br>      - service_key: &quot;&quot;</pre><p>Below is one of the alert notifications we received on Slack for the critical disk usage event in one of the servers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gfSlQcCVCezUeObO5kCTTw.png" /></figure><p><strong>Grafana</strong></p><p>As we mentioned in the introduction, trending helps you to identify how your systems, applications performing over time. Though prometheus comes up with a graph console it is not user friendly.</p><p>Grafana is an open source metric analytics &amp; visualization suite. It is most commonly used for visualizing time series data for infrastructure and application analytics. We can integrate Grafana with prometheus and visualize the metrics captured by prometheus.</p><p>All we have to do is add prometheus as a data source and create dashboards for the important metrics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jlG9Ps7ewG8FHu8xdXsAYg.png" /><figcaption>Grafana dashboards</figcaption></figure><p>For dashboard graphs, you can use the same PromQL expression (ex: disk_used_percent, cpu_usage_user).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*okwC6w2vTiWFllM2iYtbQw.png" /><figcaption>Creating Graph</figcaption></figure><p>Grafana supports variables, so you can specify metric labels as variables (region, application, instance) and pass the variables in the PromQL expression which enables multi selection in the graphs.</p><h3>Summary</h3><p>Monitoring ideally should liberate the ops engineers from spending time in monitoring systems, applications and notify only when human intervention is required. One should not spend every day adding, removing servers to monitoring and it should be taken care of by the system itself. It should be flexible enough to customize the alerts, versatile enough to monitor servers, applications, services, processes, etc, and not to mention, it should not burn too much of your company’s money either.</p><p>We found that Prometheus with the combination of telegraf, alertmanager, grafana is ticking all the boxes for us (opensource, customisable, alerts integration, visualisation) and the cherry on top is that we are running this in a single t2.medium instance.</p><h3>About The Author</h3><p>Kannan Anandakrishnan is a devops Engineer with 7 years of experience in managing large infrastructures and data platforms.</p><p>He is a Senior DataOps Engineer at zeotap, currently managing the infrastructure of zeotap with keen focus on Infrastructure as Code, building immutable infrastructure and automation.</p><p>We’re hiring for devops engineers at zeotap. Check out our open positions <a href="https://www.zeotap.com/careers">here.</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7e0093517c7d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Estimation of Samples : Part II]]></title>
            <link>https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-ii-bf87c157f1e0?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf87c157f1e0</guid>
            <category><![CDATA[distribution]]></category>
            <category><![CDATA[estimations]]></category>
            <category><![CDATA[sampling]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[embedding]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 13 Dec 2018 11:05:45 GMT</pubDate>
            <atom:updated>2018-12-13T18:02:26.073Z</atom:updated>
            <content:encoded><![CDATA[<h3>The ZeoTech Series: Estimation of Samples : Part II</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*6-zJs407rQWE_nzsg7v-nA.png" /></figure><blockquote>In the previous <a href="https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-i-f8822a64c2f5">post</a>, we learnt why and how to transform our real world, high- dimensional-categorical data to a <strong>lower dimensional continuous space, ‘Embeddings’</strong>. In this post, we shall learn two ways to generate samples from these embeddings.</blockquote><h3>PART II : Generating samples</h3><p>The idea of sampling methods is to be able to sample all such points in an appropriate number that best explains the <strong>variance</strong> in data, such that, it gives a close representation of input data. The two techniques defined below take <strong>embeddings of training data as input </strong>and <strong>outputs samples</strong>. Check the previous <a href="https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-i-f8822a64c2f5">post</a> to learn how we reached to embeddings from high dimensional categorical input.</p><h3>1. RBF-based Sampling</h3><p>With this sampling technique, we are going to exploit embeddings to our advantage, as embeddings can be used to attain a quantifiable value by calculating the Euclidean distance between two embedding vectors. This is then used to find similarity between two training data points. The basic idea is to pick more samples that are closer to the mean and move radially outwards.</p><p>Let’s dig into the specifics of RBF:</p><p><strong>What is RBF ?</strong></p><p>Radial Basis Function is a real-valued function, Φ, whose value only depends on the distance from some other point, c, called a center, so that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/324/1*NaqdBHwScfIamt4DKNJKwA.png" /><figcaption>Fig.1 Any function, phi, that satisfies this property is called a radial function. (Also used in neural networks to approximate functions in a linear combination)</figcaption></figure><p>The norm is usually <a href="https://en.wikipedia.org/wiki/Euclidean_distance">Euclidean distance</a>, although other <a href="https://en.wikipedia.org/wiki/Distance_function">distance functions</a> are also possible. Reference:<a href="https://en.wikipedia.org/wiki/Radial_basis_function">https://en.wikipedia.org/wiki/Radial_basis_function</a></p><p><strong>What is Gaussian RBF ?</strong></p><p>There exist multiple <a href="https://en.wikipedia.org/wiki/Radial_basis_function">types</a> of RBFs. Here’s <strong>Gaussian RBF</strong>, where x is the sample picked, μ is the mean (the c vector from above) and Φ is a defined function, check Fig. 2. Gaussian RBF is used to our advantage in the calculation of probability as the output of this function is contained between 0 and 1 for positive inputs. The output value represents the probability of acceptance of sample, x.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/506/1*j1BoJL-_sNFkaa8vDMoKBQ.png" /><figcaption>Fig. 2 Gaussian Radial Basis Function, β controls the width of the curve, shown below</figcaption></figure><p>This definition of probability calculation will be clearer by taking two boundary conditions. If the input (candidate sample, x) is equal to μ (from Fig. 2), then the output of this RBF will be 1, i.e., the probability of selection is 1. As the distance between the input and the prototype grows, the response falls off exponentially towards 0. This ensures more selection of data points that are closer to the mean.</p><p><strong>Graph for RBF:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*Lrhdvknbqjjnk_i9S6FuFg.png" /><figcaption>Fig. 3 The shape of the RBF response is a bell curve, as illustrated in the figure. Tuning values of β would determine the proportion of samples to be taken from a low probability region to a high probability region</figcaption></figure><p><strong>Algorithm:</strong></p><pre>1. Calculate the mean (μ) of embeddings for all dimensions.</pre><pre>2. Pick a batch of samples and calculate the mean, μ(z), of embeddings of the batch.</pre><pre>3. Calculate the probability of selection from Gaussian rbf, refer Fig. 2. Taking μ from Step1 and x as μ(z) from Step2.</pre><pre>4. Accept/Reject the batch of samples based on the output value from Step 3 with Roulette wheel selection.</pre><p><strong>Notes :</strong></p><ul><li>Roulette wheel selection is an acceptance criterion in which a random value is uniformly sampled from a unit distance [0,1]. This value is then compared to the sample probability selection from Gaussian RBF. If the output of RBF (probability of acceptance) is greater than the random sample from, the candidate sample is selected.</li><li>Tweaking the values of β in RBF to get the desired ratio of high probability values vs low probability values is a challenge. Otherwise, it results in biased sampling, favoring more of high probability values.</li></ul><h3>2. Variant of Stratified Sampling</h3><p><a href="https://en.wikipedia.org/wiki/Stratified_sampling"><strong>Stratification/Stratified sampling</strong></a><strong> </strong>is the process of dividing members of the population into homogeneous subgroups (strata) before sampling. This is followed by applying <a href="https://en.wikipedia.org/wiki/Simple_random_sampling">simple random sampling</a> or <a href="https://en.wikipedia.org/wiki/Systematic_sampling">systematic sampling</a> within each stratum. The objective is to improve the precision of the sample by reducing <a href="https://en.wikipedia.org/wiki/Sampling_error">sampling errors</a>. Reference:<a href="https://en.wikipedia.org/wiki/Stratified_sampling">https://en.wikipedia.org/wiki/Stratified_sampling</a></p><p><em>Stratified Sampling:</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/423/1*LNkDUqAJ8Z_9G3RnJxjrww.gif" /><figcaption>Fig. 4 There are 4 strata with 3 members in each stratum. Now, the number of members in each stratum can be different, depending on the dividing policy. Sampling can then be carried out by random sampling a fixed percentage of data from each stratum.</figcaption></figure><p>Stratified sampling is performed on a latent space (i.e. the embedding space) and not directly on input data. Like the previous method, this sampling technique also exploits a property of embeddings, which is, attaining a quantifiable figure to find a similarity between two embedding vectors. The algorithm is defined in detail below.</p><p><strong>Notations:</strong></p><p>μ<em> — Mean of embeddings</em></p><p><em>M — Number of strata points to be defined</em></p><p><em>N — Total number of input examples</em></p><p><em>f — Fraction of data to be taken from each strata</em></p><p>K — Number of samples desired</p><p><strong>Algorithm:</strong></p><pre>1. Calculate mean (μ) of embeddings for all dimensions.</pre><pre>2. For every vector of embedding, calculate Euclidean distance of it from the μ. Check Fig. 5</pre><pre>3. Lay out these distances on a number line with minimum and maximum distances found from Step 2. as its extremes. Check Fig. 6</pre><pre>4. Divide this line of distance into M uniform buckets/strata.</pre><pre>5. Take a fraction, f, of samples from each strata (mini-samples)</pre><pre>6. Combine all such M mini-samples to form a big sample.</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AaGjJtDAqcaZwp_gtYExuw.png" /><figcaption>Fig. 5 Conversion from Embeddings (left) to distances (right) from μ.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Za_uHrPeEW6seGWQKzhbWg.png" /><figcaption>Fig. 6 Distances number line, divided into M buckets of uniform length</figcaption></figure><p><strong>Estimation of parameters:</strong></p><p>Parameters N and μ are known.</p><p><em>How to define M and f ?</em></p><ol><li><em>First, fix on a number of samples desired, say, K &lt; N.</em></li><li><em>Now, fix on a value of the number of strata points, M. The bigger the M, the better the sampling. But as M increases, so does the computational workload. There’s a trade-off between accuracy and computation here.</em></li><li><em>f*N ~ K. Considering this as an equation, we can calculate the value of f.</em></li></ol><p><strong><em>Convergence criteria for the above two sampling techniques: </em></strong><em>The</em><strong><em> </em></strong><em>mean(μ) and standard deviation(σ) of the embeddings of the </em><strong><em>sample-set</em></strong><em> and the </em><strong><em>entire data </em></strong><em>should be of the same range. The closer they are, the better the approximation.</em></p><p>In conclusion, a real-world high dimensional, sparse and categorical data is first transformed to a lower dimensional continuous space, embeddings. These embeddings are then taken as input for sampling techniques. RBF based sampling and variant of stratified sampling are two techniques that were discussed in this post.</p><p>Thanks for reading! If you have any feedback or questions, please leave a response below!</p><p><strong>ABOUT THE AUTHOR</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/265/0*OcKcXVcchvB9JKGr" /></figure><p>Shreya Jain is a Senior Data Scientist at zeotap. Based out of Bangalore, Shreya’s core areas of work include Machine Learning modelling, Probabilistic modelling, Neural Networks and Recommendation systems. After her studies at Birla Institute of Technology and Science Pilani, she worked at Samsung Electronics as a Software Engineer for two years before deciding to embark in an exciting data journey with zeotap!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf87c157f1e0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Estimation of Samples : Part I]]></title>
            <link>https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-i-f8822a64c2f5?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/f8822a64c2f5</guid>
            <category><![CDATA[sampling]]></category>
            <category><![CDATA[estimations]]></category>
            <category><![CDATA[distribution]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[embedding]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 13 Dec 2018 11:01:25 GMT</pubDate>
            <atom:updated>2018-12-13T18:01:51.926Z</atom:updated>
            <content:encoded><![CDATA[<h3>The ZeoTech Series: Estimation of Samples : Part I</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/983/1*M_LPvAAaQSk5OQAOuN3wwQ.png" /></figure><blockquote>In a real world scenario, we often come across data with high <strong>sparsity</strong>, a large number of features with a <strong>high categorical dimensionality</strong>. Due to such complexity of the data, defining a distribution for the same is a challenging task. As the distribution is not a standard one, basic sampling techniques cannot be applied here.</blockquote><blockquote>We overcome this challenge by transforming this complex data into a lower dimensional continuous space called <strong>Embeddings,</strong> followed by sampling techniques that approximate this data distribution.</blockquote><p>Part I: <a href="https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-i-f8822a64c2f5">Problems and solutions for real-world data</a></p><p>Part II: <a href="https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-ii-bf87c157f1e0">Generating samples</a></p><p><em>Why should I be interested in dataset samples ?</em></p><ol><li>With the samples (representation of the dataset), we can run the numbers to quantify various estimates real quick, and simply extrapolate those numbers for the entire population.</li><li>The samples are meant to capture the majority of the variance of the data. Hence, training any model on this sampled data would reflect if there’s something terribly wrong with the architecture of the model.</li><li>In the case of unsupervised learning, it’s difficult to gather intuition in the form of correlations about the features. Hence, the manual dropout of features or manually observing samples would help gain meaningful data insights.</li><li>Hyper-parameter tuning for AutoML requires training on efficient samples.</li></ol><h3>PART I : Problems and solutions for real-world data</h3><h4>A) Problems associated with real-world data</h4><p>First things first, problems associated with widely sparse and high dimensional data are immense. To make matters worse, categorical inputs with no ordinal relationships amongst themselves are also encountered. Let’s call this data — DEVIL(Data Everyone Vexes In Life) and learn more about it.</p><ol><li><strong>Computational workload</strong></li></ol><p><strong>High dimensional input</strong> poses various problems. To start with, the computation cost increases exponentially as the number of possible combinations of the variables increases. This would result in an explosion of <strong>computational workload</strong> as depicted in Fig. 1 below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h0eoJtMz5GGqCbXgGEgc0Q.png" /><figcaption>Fig. 1 Explosion of possible combinations</figcaption></figure><p><strong>2.</strong> <strong>Dimensional Redundancy</strong></p><p>There might exist strong collinearity between features as in most cases, attributes are unlikely to be independent of one another. This introduces <strong>dimensional redundancy</strong>.</p><p>The problem of high dimensional data can be taken care of by feature engineering, but we’ll take an algorithmic approach to solve this issue.</p><p><strong>3.</strong> <strong>Fitting data into standard distribution</strong></p><p>In higher dimensions, see Fig. 2, data is more sparse and more flung out in the outer region than in the center of the hypercube. Hence, <strong>defining a standard probability distribution (i.e. a hypersphere) </strong>for such data (all points in the hypercube) which would contain most of its points in/around the mean of the probability distribution (say, multivariate Gaussian distribution), gets difficult as dimension increases. See, the standard <a href="http://blog.cloudera.com/blog/2015/12/common-probability-distributions-the-data-scientists-crib-sheet/">probability distribution cheatsheet</a>. As defining a probability distribution for this data is not straightforward, basic sampling techniques won’t be applied here.</p><p><em>The three points above constitute what’s known as the</em><a href="https://medium.freecodecamp.org/the-curse-of-dimensionality-how-we-can-save-big-data-from-itself-d9fa0f872335"><strong><em>Curse of Dimensionality</em></strong></a><em>. Long story short, the common theme of these problems is that when the dimensionality increases, the </em><a href="https://en.wikipedia.org/wiki/Volume"><em>volume</em></a><em> of the space increases so fast that the available data becomes sparse. This sparsity is problematic for any method that requires statistical significance. In order to obtain a statistically sound and reliable result, the amount of data needed to support the result often grows exponentially with the dimensionality. Also, organizing and searching data often relies on detecting areas where objects form groups with similar properties; in high dimensional data, however, all objects appear to be sparse and dissimilar in many ways, which prevents common data organization strategies from being efficient. Reference:</em><a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality"><em>https://en.wikipedia.org/wiki/Curse_of_dimensionality</em></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j8x-hW3pZEq7edUadwzUoQ.png" /><figcaption>Fig . 2 More percentage of points will lie in an empty space outside of a hypersphere in case of the 3-D cube than 2-D and the same trend is followed as the dimension increases.</figcaption></figure><p><strong>4.</strong> <strong>Ineffectiveness of similarity measures</strong></p><p>Referring to Fig.2, if a constant number of examples is distributed uniformly in a high-dimensional hypercube, beyond some dimensionality most examples are closer to a face of the hypercube than to their nearest neighbor. If we approximate a hypersphere, (a distribution) by inscribing it in a hypercube, in high dimensions almost all the volume of the hypercube is outside the hypersphere, i.e. the points essentially become uniformly distant from each other. So, <strong>similarity measures</strong> based on distance functions (like Euclidean metric) are rendered <strong>less effective</strong>.</p><p>Similarity measures are even less effective when dealing with <strong>categorical variables</strong> instead of continuous, as the value would just indicate the presence/absence of certain values and no insights on the degree of similarity. The combination of multi-variate features for distance functions would further take away any knowledge of which particular features went missing.</p><p><strong>5. Insignificance of joint probability distribution</strong></p><p>With high dimension features and sparse categorical data, there would be a huge number of possible combinations of feature values. So, the joint probability of most of the combinations would be too low to gather any meaningful insights out of it. Referring to Fig. 3, the <strong>joint probability distribution</strong> is expressed as a probability mass function, as for our discrete case.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5vUDrqenZB-RA5YqCtK7Wg.png" /><figcaption>Fig. 3 Probability mass function for discrete variables.</figcaption></figure><p>Hence, it makes sense to describe our joint probability distribution in the form of continuous random variables instead of discretizing every variable. So, <a href="https://en.wikipedia.org/wiki/Probability_density_function">probability density distribution</a> (continuous) needs to be defined to get some significant values.</p><p><strong>6. Random sampling limitations on complex data</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/725/1*H6XodlitlGDl9YdbwaZLMw.png" /><figcaption>Fig. 4 Class imbalance with high dimensional data</figcaption></figure><p>Simple random sampling can lead to <strong>under-sampling </strong>or<strong> over-sampling</strong> of data. This is because input data are not all alike so it requires more percentage of samples to be picked up from the more frequent type of data points and vice versa for less frequent ones. Hence, a class imbalance is another reason to move into continuous space, so that input data properties can be exploited (using embeddings, defined below) to pick a percentage of samples in accordance to their existence.</p><p>Because of all the above-mentioned problems associated with high dimensional and sparse categorical input, we compress the DEVIL into <strong><em>lower-dimensional continuous space </em></strong>called<strong><em> Embeddings, </em></strong>which is explained in section B below.</p><h4>B) Solutions to real-world data problems</h4><p>An <strong>embedding</strong> is a relatively low-dimensional space into which you can translate high-dimensional vectors. Ideally, an embedding captures some of the semantics of the input by placing semantically similar inputs close together in the embedding space, quantifiable with the help of distance metric. Reference: <a href="https://en.wikipedia.org/wiki/Embedding">https://en.wikipedia.org/wiki/Embedding</a></p><p>This transformation to continuous space, wherein two data points are comparable, is the basis of the definition of our sampling techniques. We shall read about it in detail in the next post. Let’s continue with embeddings now.</p><p><strong><em>Visualization of embeddings:</em></strong></p><p><em>I.Tabular form</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QlKM9_6q5v9gqybi2GKJqQ.png" /><figcaption>Fig. 5 Original Input Data (Categorical and Continuous) on the left and Latent Space on the right.</figcaption></figure><p>In Fig. 5, we see that the left table with categorical and continuous data has 6 features which are reduced/compressed into 3 continuous features in the latent space. The latent space is vectors of 3 dimensions, D1, D2 and D3.</p><p><em>II. Embedding space</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eo6Ztk-30iz_FEQDWzMvLw.png" /><figcaption>Fig. 6 Semantically similar items are together and dissimilar items are far apart</figcaption></figure><p>Position (distance and direction) in the vector space can encode semantics in a good embedding. For example, the above visualizations of real embeddings show geometrical relationships that capture semantic relations like the relationship between a country and its capital.</p><p><strong><em>Embedding generation techniques for DEVIL:</em></strong></p><ol><li>Non-Linear dimensionality reduction techniques: <a href="http://Linear reduction techniques: LDA (supervised ), PCA (unsupervised)">Auto-Encoder github link</a> , What are <a href="https://towardsdatascience.com/deep-inside-autoencoders-7e41f319999f">auto-encoders</a>?</li><li>Co-occurrence based models (<a href="https://towardsdatascience.com/emnlp-what-is-glove-part-i-3b6ce6a7f970">GloVe architecture</a>, <a href="https://github.com/GradySimon/tensorflow-glove/blob/master/tf_glove.py">github link</a> )</li><li>Linear dimensionality reduction techniques: <a href="https://jakevdp.github.io/PythonDataScienceHandbook/05.09-principal-component-analysis.html">PCA</a> (unsupervised)</li></ol><p>To learn more about embeddings, check <a href="https://developers.google.com/machine-learning/crash-course/embeddings/translating-to-a-lower-dimensional-space">this</a> out.</p><p>In summary, we learned about the problems linked with high dimensional, sparse and categorical input and how we overcome these challenges by converting them into a lower dimensional continuous space called embeddings. These embeddings are then going to help us in our pursuit of sampling.</p><p>Now, proceed on to the second <a href="https://medium.com/@zeotapstories/the-zeotech-series-estimation-of-samples-part-ii-bf87c157f1e0"><strong>post</strong></a> in this series to learn about various sampling techniques.</p><p><strong>ABOUT THE AUTHOR</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/265/0*RR-pldXgxR9bV35u" /></figure><p>Shreya Jain is a Senior Data Scientist at zeotap. Based out of Bangalore, Shreya’s core areas of work include Machine Learning modeling, Probabilistic modeling, Neural Networks and Recommendation systems. After her studies at Birla Institute of Technology and Science Pilani, she worked at Samsung Electronics as a Software Engineer for two years before deciding to embark in an exciting data journey with zeotap!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f8822a64c2f5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Cloud Miser]]></title>
            <link>https://medium.com/@zeotapstories/the-zeotech-series-cloud-miser-80a3e73852fa?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/80a3e73852fa</guid>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[optimization]]></category>
            <category><![CDATA[recommendations]]></category>
            <category><![CDATA[ec2]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Wed, 28 Nov 2018 12:01:19 GMT</pubDate>
            <atom:updated>2018-11-28T15:29:27.265Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/620/1*6U9nGLwXNWJoTefAsBxfzw.jpeg" /></figure><p>At zeotap, we are using AWS for all our cloud processing needs. By early 2018, we found out that our AWS expenditure had been increasing at an alarming rate with a CAGR of 8.05%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TUM0mwz17NBtKRq9" /></figure><p>This was worrisome. For almost every startup, the cost is one of the top priorities in infra setup (this is where cloud platforms are saviors for them) and saving big expenses also contributes to revenue for a startup. It is a big problem if we are not able to attribute the spends and analyze their worthiness, and that’s when we decided the problem had to be tackled. After closely analyzing it and having implemented a solution, we were able to reduce our expenditure by 50%. We came out with three strategies, and the savings with each can be found in the chart below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/606/0*GDPy0PPquZaF8_xw" /><figcaption><em>Fig. Savings distribution per plan on EC2</em></figcaption></figure><p>Now, isn’t this intriguing! Let us tell you the whole story of how we were able to cut down our spending in such a drastic way.</p><h3>Prologue</h3><p>It all started over a beer 🍺 while having long conversations about AWS costs. We knew something had to be done and fast. Finally, the time came around when we heard zeotap was announcing its first hackathon [1], and that’s when we knew that was just the right project for us.</p><h3>Problem Statement</h3><p>Though we were spending hundreds of thousands of dollars, there were some basic questions that we could not answer:</p><ol><li>Why is our cost increasing so much?</li><li>Who is actually spending so much?</li><li>Are we following industry practices on having correct visibility on expenditure?</li></ol><p>After the questions were clear, we knew the scope of this project should be to provide the following features:</p><ul><li><strong>Transparency over services:</strong> When you have multiple modules and products running across teams, it’s difficult to find out the break up of cost per module/product/team or owner. If we know who is spending how much, we can ask the right questions and make better management calls like ROI calls over projects.</li><li><strong>Monitoring resource utilization across services: </strong>We need to monitor our resources properly to know whether we are over-provisioning.</li><li><strong>Providing better alternatives with respect to cost:</strong> Choose from multiple billing models like RI and Storage/Compute instances from AWS.</li></ul><h3>Data Analysis and Problem Scoping</h3><p>We started out by analyzing our AWS billing data. In order for us to limit the scope of our problem, we first identified the cost distribution per AWS service per region. You can find the analysis below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6pvQ4wKM2I8vr6zx" /></figure><p>This graph shows the cost distribution per service and region. Just by looking at the graph, we understood right away, what to focus on. For our hackathon, we limited the cost optimization scope to EC2 [2] and RDS [3] alone which accounted for 80% of the cost. In this article, we will only talk about EC2 since RDS has a very similar story.</p><p>EC2 provides two types of instances: On-Demand [4] and Spot Instances [5]. Out of the total cost of EC2, more than 70% was attributed to On-Demand EC2 Nodes. Hence, we chose to look more closely at on-demand EC2/RDS spend to recommend more optimal instance types in order to reduce our expenditure.</p><h3>Literature Survey</h3><p>After reading through people’s experience of how they actually save costs over a cloud infra and what best practices are recommended, we came out with the following points:</p><ul><li><strong>Right-Sizing</strong>: Allocating just the best instance for the task. An instance is best for the task when it is optimally used in terms of memory and CPU (i.e an instance should be neither too large to be underutilized nor too small to miss the SLA of the running job).</li><li><strong>Monitoring:</strong> For right resizing, we need to continuously monitor our instances (their usage) on the regular interval and calculate the optimal required capacity of the instances.</li><li><strong>Reserved Instance: </strong>This is another technique where AWS provides significant discounts (up to 70%) compared to the on-demand instance. AWS provides different purchasing options for Reserved Instances based on customers’ comfort. For more details on RI, see the reference [6].</li><li><strong>Dayparting:</strong> This is another great way to reduce the spends. Dayparting means to scale up and down as per requirement. i.e scale down/stop/terminate the machines when not in use. Alternatively, for scheduled jobs/services we can launch the cluster when required and terminate once the job is done.</li></ul><p>We picked the top 3 strategies in our project. In order to implement them, we needed a better understanding of the AWS pricing model [7] and the AWS reserved Instances [6]. Not only this, but we also needed to find ways to collect the resource utilization data. We found two viable sources to do so, Cloudwatch [8] and Prometheus [9]. Cloudwatch, though natively provided by AWS, is a paid solution while Prometheus is an open source solution. We, therefore, decided to go ahead with Prometheus.</p><h3>Data Collection and Analysis Architecture</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/791/0*X3VqIEs89V7w4o3w" /></figure><p>The diagram above shows the complete stack and architecture we used to come up with our solution. We used the following tools:</p><ol><li>Java for data collection and munging the data from AWS and Prometheus</li><li>Athena [10] for computations</li><li>DOMO [11] for visualization</li></ol><p>The following datasets were collected in order to arrive at the optimized recommendations.</p><h3>EC2 Instance Data</h3><p>Tagging was a prerequisite for this data. As per our process, all EC2 instances should be tagged with Team-Name, Owner, and Project. We collected all the relevant data from the instances using AWS SDK, which comprised of Instance ID, Instance Type, Launch Date Time, Availability Zone, Tenancy, Private IP Address, Status, Region and tags data.</p><h3>AWS master pricing data</h3><p>AWS provides an API to fetch the current on-demand pricing. We extracted this data to check for instances that have a similar configuration but are cheaper. We then checked all the instance types that were provided by AWS, and what their On Demand and RI (Reserved Instances) price were under various models.</p><h3>Utilization Data from Prometheus</h3><p>We found out that IO (Network and Disk) was seldom a problem for us but the CPU and Memory were. Hence, we extracted out the utilization data for CPU and Memory using Prometheus. Prometheus collects metrics every 5 seconds. To aggregate these metrics, we experimented with max, min, average and percentile calculations.</p><p>Percentile [12] came out as the best option and gave better recommendations. Percentile intuition is explained in the algorithm section below. We took the 80th percentile of resource utilization data over a 15 days window, and we pulled <strong>host address, timestamp, aggregate utilization value</strong> per instance. This was possible since Prometheus has inbuilt support for percentile aggregation.</p><h3>EC2 Recommendation Algorithm</h3><p>For providing size recommendations for any given instance, we required the following data:</p><ol><li>AWS master pricing data</li><li>Prometheus data (Usage)</li><li>EC2 Instance data</li></ol><p>From the AWS master pricing data, we needed to extract the following details for all the available instances on AWS:</p><ul><li>InstanceType (e.g. m4.2xlarge)</li><li>Available CPU and Memory (# of cores and memory)</li><li>Region (as we have different prices in different regions)</li><li>On-demand price per unit</li><li>RI price per unit for no upfront</li></ul><p>Using AWS APIs, we can extract all the instance details running in our AWS account.</p><h3>Algorithm Example</h3><p>Consider we have one EC2 instance running in our AWS account with the following configuration:</p><p><strong>Instance type:</strong> c3.4xlarge</p><p><strong>Available CPU:</strong> 16 core</p><p><strong>Available Memory:</strong> 30GB</p><p><strong>Price (per hour):</strong> $0.840</p><p>From the Prometheus data for this instance, we observe the following metrics:</p><p><strong>CPU usage:</strong> 25% (80 percentile metrics used here)</p><p><strong>Memory usage:</strong> 30% (80 percentile metrics used here)</p><p>* 80 percentile or above means that 80% of the time the CPU usage is less than 25%.</p><p>Using this instance usage information, we calculated, the required CPU and Memory for the same instance based on its usage would be:</p><p><strong>Required CPU</strong>: 16 * 25% = <strong>4 core</strong></p><p><strong>Required Mem</strong>: 30 * 30% = <strong>9 GB</strong></p><p>As we had taken the 80th percentile, we added an additional buffer on the CPU and Memory of the recommended instance as the below formula shows:</p><p>Actual Memory Usage = Instance Memory * Usage</p><p>Buffer Memory = sqrt(Actual Memory Usage)</p><p><em>Recommended Memory = Actual Memory Usage + Buffer Memory</em></p><p>The same calculation can be done for CPU cores as well.</p><p>Why is the buffer memory calculated using the Square root function? Consider the following two examples.</p><p><strong>Case 1.</strong> The actual memory usage is 4GB. According to the formula, the buffer memory will come out to be 2GB which is more than sufficient in this case.</p><p><strong>Case 2. </strong>Let’s say the actual memory usage is 128GB. Again as per the formula, the buffer memory will come out to be approximately 128= 11GB.</p><p>In both the cases, we see it is a reasonable buffer to add.</p><p>Square root curve accentuates lower data points and penalizes high data points giving us an acceptable value of buffer size.</p><p>The same applies to the CPU usage as well, and now we have the following recommended instance properties as per the above formula:</p><p><strong>Recommended CPU</strong> = 4 + √4 = <strong>6 core</strong></p><p><strong>Recommended Memory</strong> = 9 + √9 = <strong>12 GB</strong></p><p>If we have to visualize this, see the bubble chart of AWS instances based on its CPU (x-axis), Memory (y-axis), and Price (size of bubble). The goal is to find the top N closest and cheapest instance available with the above configurations. The black bubble in the below diagram shows the required Instance with the configurations we calculated above. We find the cheapest top 3 recommendations which satisfy the required configurations and sort by the price of the instance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/781/0*KOBgv4xvs14UHSQg" /></figure><p>As a result, our recommendations would be following:</p><ul><li>t3.2xlarge (CPU — <strong>8 core</strong>, Mem — <strong>32 GB</strong> and Price : <strong>$0.332</strong>)</li><li>c5.2xlarge (CPU — <strong>8 core</strong>, Mem — <strong>16GB</strong> and Price : <strong>$0.340</strong>)</li><li>t2.2xlarge (CPU — <strong>8 core</strong>, Mem — <strong>16GB</strong> and Price : <strong>$0.371</strong>)</li></ul><p>If we resize our current instance based on the above recommendations, we would end up saving around:</p><p><strong>Savings per hour</strong> = $0.840 — $0.332 = <strong>$0.51 per hour</strong></p><p><strong>Savings (in %age)</strong> = $0.51 / $0.84 * 100 = <strong>60% approx</strong></p><p>And this saving is for just one instance. We can apply the same algorithm on all the on-demand instances (EC2 or RDS) based on its usage and save the company a whole lot of money.</p><h3>Conclusion</h3><p>AWS provides a variety of ways to reduce the infrastructure cost. We have to choose the right model and the right size for each of the AWS services. For example, we should consider using Spot Instances for EMR related jobs and Reserve Instances (RIs) for long-running instances with known utilization. Further, understanding and analyzing the billing data over a period of time helps identify the optimal mix of various pricing model’s to address your infrastructure requirements.</p><p>Transparency in infra is another very important aspect to achieve infra cost optimizations and assist in ROI calls for the services deployed. Tagging the instance is one of the mandatory practices for transparency. Regular monitoring of the infra, removing or resizing underutilized resources, goes a long way in reducing spends. We are running this recommendation algorithm once a month and looking at more automated ways of applying the optimization recommendations to our cloud infrastructure.</p><h3>References</h3><p>[1] Zeotap Hackathon — <a href="https://www.zeotap.com/hackathon">https://www.zeotap.com/hackathon</a></p><p>[2] EC2 — <a href="https://aws.amazon.com/ec2/details/">https://aws.amazon.com/ec2/details/</a></p><p>[3] RDS — <a href="https://aws.amazon.com/rds/">https://aws.amazon.com/rds/</a></p><p>[4] On Demand — <a href="https://aws.amazon.com/ec2/pricing/on-demand/">https://aws.amazon.com/ec2/pricing/on-demand/</a></p><p>[5] Spot Instances — <a href="https://aws.amazon.com/ec2/spot/">https://aws.amazon.com/ec2/spot/</a></p><p>[6] AWS Reserved Instances — <a href="https://aws.amazon.com/ec2/pricing/reserved-instances/">https://aws.amazon.com/ec2/pricing/reserved-instances/</a></p><p>[7] AWS pricing model — <a href="https://aws.amazon.com/pricing/">https://aws.amazon.com/pricing/</a></p><p>[8] Cloud Watch — <a href="https://aws.amazon.com/cloudwatch/">https://aws.amazon.com/cloudwatch/</a></p><p>[9] Prometheus — <a href="https://github.com/prometheus/prometheus">https://github.com/prometheus/prometheus</a></p><p>[10] Athena — <a href="https://aws.amazon.com/athena/">https://aws.amazon.com/athena/</a></p><p>[11] Domo — <a href="https://www.domo.com/solution/data-visualization">https://www.domo.com/solution/data-visualization</a></p><p>[12] Percentile — <a href="https://en.wikipedia.org/wiki/Percentile">https://en.wikipedia.org/wiki/Percentile</a></p><h3>About The Authors</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/838/1*hmk8wA8FUb0F-e4ys8j_cA.png" /></figure><p><a href="https://www.linkedin.com/in/rakesh-sharma-5a658a78/">Rakesh Sharma</a> and <a href="https://www.linkedin.com/in/aman-verma-113a9b40/">Aman Verma</a> are both Senior Engineers that work on the Data Engineering team which handles the massive scale of data at zeotap. They work on the core data platform zeoCore, responsible for ingesting, refining and distributing data from our multiple data partners. Rakesh studied computer science and engineering and had a stint with Minjar Cloud Solutions before joining Zeotap. His areas of expertise include data engineering, distributed systems, and AWS Services amongst others. Aman studied electronics and communications engineering and worked at one of the largest e-commerce startups in India Snapdeal before moving to Zeotap. His areas of expertise include data engineering, distributed systems, and data platforms.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=80a3e73852fa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Git Workflow : Rebase vs Merge]]></title>
            <link>https://medium.com/@zeotapstories/the-zeotech-series-git-workflow-rebase-vs-merge-93cf0866ed35?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/93cf0866ed35</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[git]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[data-engineering]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 20 Sep 2018 12:23:15 GMT</pubDate>
            <atom:updated>2018-09-20T12:23:15.281Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Sd60-mvX_2rMjCblj0kplA.png" /></figure><p>I joined the company last year in April into a team of Data Engineers. The team consisted of 3 interns and 3 Data Engineers collaborating using the version control system, Git. We didn’t have much of a structure in place to peer review the code that went into production. We did use pull requests on Github at the time, but we had all the issues in our git repositories that an early stage startup might have. We added inessential files into the repositories (such as .DS_Store) along with hardcoded paths to local files, no proper commit messages, no logical commits to name a few.</p><p>Since then, we have come a long way. Our team has grown from 6 engineers to 14 engineers. We have made significant improvements to our code review process. We ensure that at least two engineers see every piece of code before it goes into production. Our git commits are much more logical and always accompanied with much more meaningful and detailed commit messages than before. Through all of this, one of the decisions we had to make was to choose the right Git Workflow, specifically whether to “merge” pull requests or use “rebase and merge” instead.</p><p>In this article, I first discuss the “rebase” workflow that we follow at zeotap in the Data Engineering team. Then, I argue that the “rebase” workflow builds better team dynamics compared to the “merge” workflow. While the “rebase” workflow is trickier to follow, it has numerous benefits. For instance, it forces the developers to make logical commits resulting in better code readability and code ownership. It also fits well with the agile software development strategy by ensuring frequent commits and pull requests.</p><h3>The Choice</h3><p>At zeotap, every developer works on a particular part/feature of a project, building commits in their own branch. Once a logical portion of the feature is complete, the developer raises a pull request on Github. Then, at least two different peer engineers review this pull request before it can be merged into the <em>master/sprint</em> branch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/975/1*Ztf8QbarDBPXkgjVmRm2PQ.png" /><figcaption>[Reference: <a href="https://blog.github.com/2016-09-26-rebase-and-merge-pull-requests/">https://blog.github.com/2016-09-26-rebase-and-merge-pull-requests/</a>]</figcaption></figure><p>While merging the pull request, we consider two choices. We can either “merge” the pull request resulting in a merge commit, which is a special kind of commit merging a developer branch into the master/sprint branch [<a href="https://www.atlassian.com/git/tutorials/merging-vs-rebasing">https://www.atlassian.com/git/tutorials/merging-vs-rebasing</a>]. A sequence of merge commits in a Git repository builds a complex tree of commits leading up to the current version of the code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zs9ktALgrT3Vn-oGPpbNJg.png" /><figcaption>[Reference: <a href="https://www.atlassian.com/git/tutorials/merging-vs-rebasing">https://www.atlassian.com/git/tutorials/merging-vs-rebasing</a>]</figcaption></figure><p>Another choice is to “rebase” all the commits into a developer branch on top of the master/sprint branch and merge the pull request using a fast-forward merge strategy. [<a href="https://ariya.io/2013/09/fast-forward-git-merge">https://ariya.io/2013/09/fast-forward-git-merge</a>]. This results in a structure where it looks as if all the commits were made on the master/sprint branch directly and the developer branch never really existed. In practice, we actually delete the developer branch once the pull request is merged.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DtLp0ScJ62uz3nvX1k1Tuw.png" /><figcaption>[Reference: <a href="https://www.atlassian.com/git/tutorials/merging-vs-rebasing">https://www.atlassian.com/git/tutorials/merging-vs-rebasing</a>]</figcaption></figure><h3>Rebase Workflow at zeotap</h3><p>Before we compare both the workflows, below is the “rebase” workflow that we follow at zeotap. In this workflow, we never force-update the <em>master/sprint </em>branch, since only developers can force-update their branch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/860/1*tfdRwWrN1FDKxmZ9NKw9SA.png" /></figure><h3>Rebase vs Merge</h3><p>Now, let’s compare both the “merge” and “rebase” workflows.</p><h3>Rebase is Hard</h3><p>First of all, let me accept that “rebase” is complex, destructive and takes time to get used to. If there are conflicts, they need to be resolved for each commit that is rebased on the current branch. Once “rebase” is complete, one has to destructively force push to the remote branch resulting in not a safe practice. Github does have mechanisms available to ensure that the sprint/master branch is never modified once committed, nevertheless, the process is complex.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1WTBWQ0GQ3XEocu7GmJesw.png" /></figure><p>On the other hand, “merge” is relatively easier, requires little effort and git does most of the work for you. It creates a “merge” commit explicitly marking the merge of the developer branch into the sprint/master branch. This can lead to conflicts as well, but those are resolved in a single step for all commits in a branch unlike with the “rebase” approach.</p><h3>Logical Commits</h3><p>While “rebase” is hard and destructive, it reinforces the idea of logical commits. By logical commits, I mean that each commit has a complete logical unit of code such as fixing a bug, adding a feature or a part of it. This has really helped us improve the readability of our commits in a git repository. When a developer has to rebase their branch on the master/sprint branch, the sequence of new commits on the developer’s branch are applied on top of the master/sprint branch. If these commits are not logical, it could be tedious to resolve conflicts in this process. Logical commits allow us to distinctly identify how to resolve the conflicts based on the logical change that has been made in a given commit. This reinforces the idea of building logical commits while writing code. On the contrary, in the case of the “merge” workflow, the conflicts need to be resolved in the “merge” commit and no such need of creating logical commits ever arises.</p><h3>Developer’s Responsibility / Ownership</h3><p>When git is unable to perform “merge” due to conflicts in the code, it typically informs the developer, and he has to take the matter into his own hands. In the case of the “rebase” workflow, this is done while the pull request is getting reviewed. Whereas, in the case of the “merge” workflow, the conflicts are sometimes resolved while merging the pull request. What we really liked about the “rebase” workflow is that it takes away the choice of merging later. It becomes the responsibility of the developer to ensure all the changes go are done correctly before the pull request is merged. The developer has the responsibility of rebasing his changes on the master/sprint branch before creating the pull request compared to relying on Git or code reviewers for merging the changes in the pull request.</p><h3>Commit History &amp; Reverting Commits</h3><p>This is a straightforward consequence of using the “rebase” workflow. It provides a linear history of commits in a repository which is easy to reason about. Also, from the history, it is easier to understand how the current state of the code is achieved. It is also much easier to undo commits from the linear history of commits. Whereas, with the “merge” commit, a much more complex tree structure of commits is built, which makes it difficult to identify and revert.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iTuyqplT2y6kQTLccv8IrQ.png" /><figcaption>[Reference: <a href="https://www.atlassian.com/git/tutorials/undoing-changes/git-revert">https://www.atlassian.com/git/tutorials/undoing-changes/git-revert</a>]</figcaption></figure><h3>Agile World</h3><p>This is the final advantage that I’d like to discuss in this article. I believe that the “rebase” approach fits better into the world of Agile Software Development. Zeotap’s environment is extremely fast-paced and therefore requirements and code tend to change very frequently. In such a fast paced environment, the “rebase” workflow helps us keep up with the code changes made by peer developers. We create and merge our code frequently through pull requests to ensure that our branch does not lag behind the master/sprint branch. Given that “rebase” requires resolving conflicts commit by commit for all the commits in the pull request, the fewer the commits, the easier it becomes to resolve conflicts. It, therefore, pushes developers to merge changes as frequently as possible to reduce the workaround effort when resolving conflicts. Sometimes when we do find conflicts, we also engage in discussions among ourselves to understand the reason behind them. This always leads us into becoming a much more collaborative team.</p><h3>Conclusion</h3><p>The “Rebase” and “Merge” approach are both excellent strategies for the Git workflow which can be adopted at the workplace. Nevertheless, I think the major difference is that the “rebase” workflow is destructive and takes some time to get used to whereas the “merge” workflow is easier to use and doesn’t require training. Despite that, the “rebase” workflow has a slight advantage over “merge” in an agile environment: it requires developers to think more deeply about their commits in order to build logical commits and take ownership of each pull request, providing a linear history which is easy to reason about.</p><p>We have been using the “rebase” workflow for more than a year now. Whenever developers join the team, we help onboard them with our Git workflow. Once they get used to this workflow, the rest of the benefits simply follow.</p><h3>About the Author</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/339/0*akFWFSscK47_uNMf" /></figure><p>Aman Mangal, Software Engineer at zeotap, is one of the many team members that works with a tremendous amount of data on a daily basis. His main tasks include managing the different partners’ data ingestion into zeotap’s systems as well as monitoring and optimizing the data pipeline for smooth functioning. After completing his computer science studies both in India and the US, Aman had the opportunity to contribute to companies like Nokia Bell Labs, Zerostack and Lumiata before joining zeotap. His areas of expertise include data engineering, distributed systems and linux containers amongst others.</p><p>For more info on Aman’s profile, visit his Linkedin page <a href="https://www.linkedin.com/in/amanmangal/">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93cf0866ed35" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Growing A Data Billionaire — AWS RedShift]]></title>
            <link>https://medium.com/@zeotapstories/growing-a-data-billionaire-aws-redshift-dca4ba38a280?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/dca4ba38a280</guid>
            <category><![CDATA[query]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[aws-redshift]]></category>
            <category><![CDATA[data-modeling]]></category>
            <category><![CDATA[amazon-web-services]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Mon, 23 Jul 2018 14:32:07 GMT</pubDate>
            <atom:updated>2018-09-20T12:28:09.719Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/835/1*wyzcQaNULR7OXVSfZ1O0gg.png" /></figure><p>At Zeotap we source data from countless enterprise partners to then refine, blend and convert it to high-performing data segments to be used for targeting. Last year the number of profiles under management grew 10X from 250M to 2.5Bn.</p><p>An introduction to our data pipeline was given here — <a href="https://medium.com/@zeotapstories/oozie-its-pouring-data-1dfb7bfb635b">Oozie! It’s pouring data!</a>. In this article we will now further elaborate on our profile store, its purposes and our choice on tech. Our profile store is where every data feed finds it place. It is the heart of our advertising intelligence.</p><p><strong>Key Terms</strong></p><p><em>Profile — all attributes of a user ranging from demographic to interest and intent.</em></p><p><em>Profile store — a denormalized store of all user profiles</em></p><p><em>Segment — a collection of users which belong to a particular group satisfying a criteria.</em></p><h3>Use Cases &amp; Scaling Up</h3><p>We already have dozens of data points for each user and this number is constantly growing. Some regions have data in more than <em>2 Bn</em> rows and there are by now in excess of 7000 queries to treat the data.</p><p>Following are some of the significant data segment requirements of this database / store:</p><ol><li><strong>Creation and Export</strong> — Given a combination of attributes as filters, the database returns the matching rows or users. It also can export these segments to a file location so that they can be consumed across multiple channels.</li><li><strong>Revenue Attribution</strong> — Allocating the generated revenue per segment to zeotap’s different data partners based on their contributions, data quality parameters and different prices for different attributes</li><li><strong>Size Estimator</strong> — Calculating the approximate number of users in a segment in real-time</li><li><strong>Analytics </strong>— Running aggregate queries to create real-time dashboards and reports on live segments</li></ol><h3>Data Model</h3><p>The below table depicts the overall structure of our data profile store:</p><p>There are three main types of columns in this table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/983/1*oI24wyW4nyqmQx68mIF7Pw.png" /></figure><ol><li><strong><em>ZUID</em></strong> — Zeotap Unique ID — a unique and purely internal randomly assigned user ID</li><li><strong><em>User Attributes</em></strong> — such as age, gender and interest categories</li><li><strong><em>Data Partner ID</em></strong> — data partner that the attribute orginates from</li></ol><h3>Query Patterns</h3><p>Assuming that the table name is <strong>profileStore</strong> and that the attributes are the ones listed above, typical queries to build a meaningful user segment for the advertising market looks like this.</p><p><strong>Segment creation and export queries</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/845/1*uM0xPruGmH3Pj9q8mhCo1g.png" /></figure><p><strong>Segment revenue attribution queries</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/829/1*F17L7RYa0SsdqiqoBDLUYA.png" /></figure><p>The query above will calculate the proportion of attributes shared by each data partner from the final database for a specific user segment — called “All female users in the profileStore” in this case. Imagine this running for 500+ columns and you can picture the complexity and scale of this task.</p><p><strong>Segment size estimation queries</strong></p><p>We estimate the size of a specific segment by using a representative data sample from the profile store. The query on this sample would look like this:</p><p>select count(distinct zuid) from profileStoreSample where age &gt; 25 AND gender = ‘Male’</p><p>Important to note here is that since the filters / criteria used to create a segment can vary enormously between applications, any of the hundreds of columns with the respective AND / OR / Other combinations can be relevant.</p><p>The result of any query then, is a list of ZUIDs. The less filters we apply, the bigger the size of the segment. A simple segment comprising only males can easily be in the billions. As a result of such scale, we need an efficient way to export these results to an easily accessible stored format.</p><h3>Column Oriented &amp; MPP Databases</h3><p>Now that we have described our technical and to some degree commercial use cases and some sample query patterns, let’s take a look at the core technology behind the <em>ProfileStore</em>: our <strong>Column Oriented Databases </strong>and<strong> MPP</strong> (Massively Parallel Processing).</p><h4>Column-Oriented Databases</h4><p>Column-oriented databases store all the values of a particular column together on one disk. When a query is needed, only a few disk blocks which contain the required columns are fetched to calculate the result. This requires significantly less disk I/O as opposed to row-oriented databases. A couple of smart techniques such as compression and late materialization are additionally used. They further reduce disk I/O and improve speed. [<a href="http://db.csail.mit.edu/projects/cstore/abadi-sigmod08.pdf">2</a>][<a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS">3</a>]</p><p>The main difference between row and column oriented storage types is shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*peqVZ_H2qOj_O5v33cQ59A.png" /></figure><p><em>Figure 1: Difference in storage mechanism for row and column-oriented database for a sample table</em></p><h4>MPP Database</h4><p>In Massively Parallel Processing (MPP) databases, data is partitioned across multiple servers or nodes with each server/node having memory/processors to treat the data. All communication is done via a network interconnect. Hence, disk-level sharing or contention are of no concern which is why this approach is also referred to as a ‘shared-nothing’ architecture.<strong> </strong>Leveraging parallelism, the queries can run multiple times faster than on a single host.</p><h3>Amazon Redshift</h3><p>During the scaling journey we’ve carefully chosen Redshift as our ProfileStore. Redshift is a database provided by AWS for data warehousing and OLAP workloads and stores its data as laid out above in a column oriented fashion on disks. It is also a MPP database with a shared-nothing architecture.</p><p>At zeotap, using Redshift goes along with the following main benefits:</p><h4>AWS Managed</h4><p>Redshift is a fully managed database by AWS. Operability is easier than maintaining an owned equivalent database such as Greenplum.</p><h4>Bulk Load and Unload</h4><p>Data can be copied from S3 to Redshift in multiple formats such as CSV, AVRO, GZIP using a COPY command. Since most of our data is stored in S3, this is very convenient. Also, loading plain CSV data into Redshift is much faster than AVRO. [<a href="https://blog.stitchdata.com/redshift-database-benchmarks-copy-performance-of-csv-json-and-avro-9062f71f814">4</a>]</p><p>Redshift also has a feature to automatically store the query results to an S3 location using the <strong><em>UNLOAD</em></strong> command. This suits us very well since the results from our queries are usually very large and need to be consumed across multiple channels.</p><h4>Postgres Extension</h4><p>You can directly query AWS redshift using Postgres extensions such as <em>dblink</em> and <em>postgres_fdw</em>. This is useful in one of our products which is constantly sampling data from Redshift to estimate segment sizes. For more details about extension, take a look at our dedicated blog post on note: [<a href="https://aws.amazon.com/blogs/big-data/join-amazon-redshift-and-amazon-rds-postgresql-with-dblink/">5</a>].</p><h4>Redshift @Zeotap</h4><p>The largest cluster we currently have comes from the US. This cluster is running with 10 nodes and also has the largest number of user records. We use dc2.large types of nodes since they come with SSD and are faster compared to a Dense Storage type of nodes.</p><h3>Limitations and Future Work</h3><p>While Redshift is generally a very good fit, one of its current advantages is also one of its biggest limitations. And that is that it does not support data structures such as JSON or Map. As a result, table modelling is limited by a flat structure of columns using trivial types. To support a <em>one-to-many relationship</em>, we have to model two separate tables with a loosely imposed Foreign key and use JOIN to combine multiple layers of results. When one of the tables is much smaller than another, Redshift uses HASH JOIN and otherwise MERGE JOIN to compute the results. One problem we have observed is that MERGE JOIN is slow when both tables are sufficiently large to put the memory to a challenge.</p><p>Another limitation is that with growing amounts of data, the time needed to COPY — as the only way to upload data into Redshift — processing times increase as well. Therefore, it is not too suitable for streaming or near-real time use cases.</p><p>Now luckily, at zeotap we don’t like to live with limitations, which is why we’ll dive in detail into how we overcame these challenges using other databases such as Presto (SQL query engine for files stored on HDFS or S3) in our next blog article. Stay tuned and reach out if you also never settle with the average. It’s how innovation is born and mind and skillset evolve.</p><h3><strong>References</strong></h3><ol><li><a href="http://use-the-index-luke.com/sql/anatomy">http://use-the-index-luke.com/sql/anatomy</a></li><li><a href="http://db.csail.mit.edu/projects/cstore/abadi-sigmod08.pdf">http://db.csail.mit.edu/projects/cstore/abadi-sigmod08.pdf</a></li><li><a href="https://en.wikipedia.org/wiki/Column-oriented_DBMS">https://en.wikipedia.org/wiki/Column-oriented_DBMS</a></li><li><a href="https://blog.stitchdata.com/redshift-database-benchmarks-copy-performance-of-csv-json-and-avro-9062f71f8148">https://blog.stitchdata.com/redshift-database-benchmarks-copy-performance-of-csv-json-and-avro-9062f71f814</a></li><li><a href="https://aws.amazon.com/blogs/big-data/join-amazon-redshift-and-amazon-rds-postgresql-with-dblink/">https://aws.amazon.com/blogs/big-data/join-amazon-redshift-and-amazon-rds-postgresql-with-dblink/</a></li><li><a href="http://docs.aws.amazon.com/redshift/latest/dg/c_columnar_storage_disk_mem_mgmnt.html">http://docs.aws.amazon.com/redshift/latest/dg/c_columnar_storage_disk_mem_mgmnt.html</a></li><li><a href="http://docs.aws.amazon.com/redshift/latest/dg/c_high_level_system_architecture.html">http://docs.aws.amazon.com/redshift/latest/dg/c_high_level_system_architecture.html</a></li><li><a href="http://docs.aws.amazon.com/redshift/latest/dg/t_Loading_data.html">http://docs.aws.amazon.com/redshift/latest/dg/t_Loading_data.html</a></li><li><a href="http://docs.aws.amazon.com/redshift/latest/dg/c_unloading_data.html">http://docs.aws.amazon.com/redshift/latest/dg/c_unloading_data.html</a></li></ol><h3><strong>About the author:</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/349/0*qY3kAS-LIR4T7Fom" /></figure><p>Chaitanya Bendre, Senior Software Engineer at zeotap, is one of our colleagues who handles tremendous amounts of data on a daily basis. He works on our core data engineering platform, zeoCore, which ingests and refines data from multiple partners to distribute it across a wide range of buying and other application platforms. After completing his degree in Engineering at BITS Pilani, Chaitanya had the opportunity to work with companies like Goldman Sachs and Juspay on cloud computing and data platform related projects. His areas of interests include data engineering, functional programming and distributed systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dca4ba38a280" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The ZeoTech Series: Oozie! It’s pouring data!]]></title>
            <link>https://medium.com/@zeotapstories/oozie-its-pouring-data-1dfb7bfb635b?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/1dfb7bfb635b</guid>
            <category><![CDATA[india]]></category>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[apache-hadoop]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[apache]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Mon, 06 Nov 2017 16:12:07 GMT</pubDate>
            <atom:updated>2018-09-20T12:28:49.269Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/764/1*t3XjjDqNkZPmLXZ-5JVIvw.png" /></figure><p>zeotap makes large scale, deterministic data assets easily available within the digital advertising ecosystem and other industries. Our data engineering team transforms unstructured raw data that we receive from a multitude of data partners into structured readily queryable data which can then be monetized through different distribution channels. In this article, I will begin with a high-level introduction to our data pipeline and elaborate on how we use Apache Oozie as a distributed controller for our data platform with some customisations to make it work for our use case.</p><p>Our whole data platform runs on Amazon Web Services (AWS) making heavy use of a diverse set of tools and services provided by AWS. We receive data from various data partners every day in designated S3 buckets in pre-decided formats. We then process it on AWS Elastic MapReduce (EMR) using Apache Spark through various stages in the pipeline. The processed data is uploaded to Amazon Redshift Clusters (an OLAP MPP Database) and made available for efficient querying by different teams as well as by various internal APIs that we have built on top of it. All of this is scheduled and monitored through Apache Oozie that comes along with Amazon’s EMR service.</p><h3>Apache Oozie</h3><p>Apache Oozie is a workflow scheduler for the Hadoop ecosystem that can schedule various MapReduce jobs across an EMR cluster with the help of yarn resource manager. Each Oozie workflow consists of a directed acyclic graph of control flow nodes called actions. A workflow can be executed once using Oozie controller or scheduled periodically using an Oozie Coordinator. An Oozie workflow is described in an XML file. Those are usually not a favourite to work with, but in Oozie they are supported with some really powerful abstractions such as workflow parametrization, fork-join action nodes for parallel execution, a rich library of EL (Expression Language) expressions and email notifications so we can quickly get over it.</p><h3>Data Pipeline in Oozie</h3><p>In order to set up our data pipeline in Oozie, we composed each stage of the pipeline as a sub-workflow consisting of one or more Spark actions. We also made use of HDFS actions to create the necessary folder structure and shell actions to ensure that the expected data is in fact available. We wrapped all the sub-workflows in yet another parent workflow to run them in sequence and perform the bookkeeping operations as needed. The decoupling of a parent workflow with sub-workflows for each stage enabled us to run one or more stages independently for testing and debugging purposes.</p><p>We parametrized the parent workflow to process data for every data partner using Oozie variables. We set up Oozie coordinators to run the corresponding parent workflow every day at specific times of the day. Here, we made use of EL expressions to compute paths to a folder in an S3 bucket containing the latest data for a given data partner. Finally, we added another parent workflow consisting of a Spark action for aggregating data across all data partners followed by a Shell action to upload aggregated data to Redshift.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*9--OZiytZzsui3BnZhLeKw.png" /></figure><h3>Problems Faced — Oozie on EMR</h3><h4>Oozie Data Trigger</h4><p>We wanted to use data triggers in our data pipeline and run a workflow as soon as all the input data would be available. Oozie’s synchronous data triggers didn’t work for our use case as a workflow can only be triggered at a fixed time interval. To circumvent the issue, we simply wrote a Shell script polling data availability every 5 minutes to check whether processed data had been written by the previous workflow.</p><h4>Duplicate Action Execution</h4><p>We use the autoscaling feature of AWS EMR for task nodes to minimize our cost which created an issue at the final stage of the pipeline. While the upload shell script is running, AWS scales down the cluster due to low memory requirements leading to failure of the YARN container running the script. Even when YARN brilliantly restarts the container on a different node, it delays the pipeline unnecessarily.</p><p>Looking deeper into the issue, we found out that Oozie runs two containers for each shell action, a driver container and a task container. AWS EMR schedules the driver container only on core nodes whereas task containers can run anywhere in the cluster. As we do not use auto-scaling for core nodes in the EMR cluster, the driver container continues to execute, whereas the task container dies when EMR scales down the cluster. As the driver container keeps running, it restarts the task container on failure.</p><p>To resolve the issue, we wanted to figure out a way to schedule the task container on core nodes as well. Interestingly, Oozie allowed us to uberize containers for shell actions. This meant an action could be executed inside the driver container without running any task containers. As the driver container would be scheduled only on core nodes, we would not face the issue anymore.</p><p>This is achieved by setting the property <em>oozie.action.launcher.mapreduce.job.ubertask.enable </em>to <em>true</em> in the Oozie configuration module.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/1*20Q6XACpX2-0g6ZSbk91ow.png" /></figure><h4>Shared Libraries</h4><p>Oozie shared libraries don’t have the appropriate Spark libraries required to run a Spark action job. We resolved the issue by manually adding/replacing the following libraries to the Oozie shared library folder (Oozie version 4.3.0 on EMR emr-5.6.0) -</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/540/1*POEIB4zbHI874CPaw-a5_A.png" /></figure><h4>HDFS Action</h4><p>While running the pipeline, we observed that the HDFS actions available in Oozie do not work for EMRFS (HDFS equivalent library for S3) due to the unavailability of the EMRFS assembly JAR in the Oozie local libraries. To deal with this issue, we simply added a soft link to the EMRFS assembly JAR already available on the EMR Master node in the Oozie local jar directory.</p><p>This is of course just a sneak peak of all the challenges we face while dealing with data at zeotap. The fun part of all of this is that on top of being constantly challenged, we end up overcoming all of this together with the rest of the team members while learning from each other and building the team’s relationship. This definitely helps with keeping motivation high, and one can just feel it in the air.</p><h3>About the author</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/339/1*rg3q-7nsmJ6mJWTSv5a4mg.png" /></figure><p>Aman Mangal, Software Engineer at zeotap, is one of the many team members that works with a tremendous amount of data on a daily basis. His main tasks include managing the different partners’ data ingestion into zeotap’s systems as well as monitoring and optimizing the data pipeline for smooth functioning. After completing his computer science studies both in India and the US, Aman had the opportunity to contribute to companies like Nokia Bell Labs, Zerostack and Lumiata before joining zeotap. His areas of expertise include data engineering, distributed systems and Linux Containers amongst others.</p><p>For more info on Aman’s profile, visit his Linkedin page <a href="https://www.linkedin.com/in/amanmangal/">here</a>.</p><blockquote><strong><em>Want to join the zeotap team? Check out our career site </em></strong><a href="https://www.zeotap.com/careers"><strong><em>here</em></strong></a><strong><em>!</em></strong></blockquote><blockquote><strong><em>Follow us on: </em></strong><a href="https://de.linkedin.com/company/zeotap"><strong><em>Linkedin</em></strong></a><strong><em>, </em></strong><a href="https://www.facebook.com/zeotap/"><strong><em>Facebook</em></strong></a><strong><em>, </em></strong><a href="https://twitter.com/zeotap"><strong><em>Twitter</em></strong></a><strong><em>.</em></strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/822/1*rf9NERh5Mxo3J9x2y5KzCw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1dfb7bfb635b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Meet the Team: Ieva Meldere, Head of Business Development in North America]]></title>
            <link>https://medium.com/@zeotapstories/meet-the-team-ieva-meldere-head-of-business-development-in-north-america-7a836edcf6f7?source=rss-bb1de2321a69------2</link>
            <guid isPermaLink="false">https://medium.com/p/7a836edcf6f7</guid>
            <category><![CDATA[big-data]]></category>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[india]]></category>
            <dc:creator><![CDATA[zeotap]]></dc:creator>
            <pubDate>Thu, 05 Oct 2017 15:57:32 GMT</pubDate>
            <atom:updated>2017-10-05T15:57:32.033Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*fhcOCUSReGbrPnerd_niLA.png" /></figure><h3><em>Today</em></h3><p>Ieva leads zeotap´s business development efforts in North America. As part of the role Ieva and the team engage and test dozens of data partners every week against zeotap´s strict high-quality data standards. Her day-to-day responsibilities also involve leading our identity business globally and working on other strategic business development opportunities.</p><h3><em>Background</em></h3><p>Ieva joined zeotap in the winter of 2015 and moved to company´s headquarters in Berlin from the ancient halls of University of St. Andrews, Scotland, where she finished her Master’s degree in Finance. Ieva set foot in St. Andrews with an ambition to pursue a career in investment banking, but it was the University´s entrepreneurship professor who inspired her to move into the growing data and mobile advertising industry. Prior to that, Ieva was with the management consulting firm EY, working on a variety of data-related projects across the Europe.</p><h3><em>zeotap journey</em></h3><p>Ieva is one of our colleagues who reinvented her own role several times while zeotap kept growing. Over the 2 years she wore many hats and ventured from telecom operator partnerships into building agency and brand relationships in Spain and Italy. When zeotap was just getting ready to enter the US, Ieva’s ambition was to go for the highest quality data owners in the biggest advertising market in the world. With her move to the NYC, she also opened our US office in Chelsea, Manhattan.</p><p>Also, during her time with zeotap, Ieva had a chance to dive into the Indian culture — during her visit to the Bangalore office, her colleagues helped her discover the colourful customs this country has to offer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B48iDCJAX_XkvulGLBZLpw.jpeg" /></figure><h3><em>Fun fact</em></h3><p>Over the years, wherever she lived, Ieva has always dedicated a part of her free time to Corporate Social Responsability and volunteering projects — from her hometown Riga, where she was mentoring youth in SOS Children Villages and developing advanced teaching materials for high school students, approved and taught in schools across the country, to helping out in soup kitchens and Christmas events in Atlanta. With her move to the NYC, Ieva will also be kicking off volunteering efforts as part of the University of St Andrews Alumni Club Board. Volunteering aside, Ieva is a big fan of a healthy and active lifestyle, but having spent almost a year in Glasgow, she would never say no to a naughty but oh-so-good deep-fried Mars candy bar if you offered her one. Or a generous serving of a good old Scottish classic of scotch-soaked haggis, neeps, and tatties.</p><blockquote><strong><em>Want to join the zeotap team? Check out our career site </em></strong><a href="https://www.zeotap.com/careers"><strong><em>here</em></strong></a><strong><em>!</em></strong></blockquote><blockquote><strong><em>Follow us on: </em></strong><a href="https://de.linkedin.com/company/zeotap"><strong><em>Linkedin</em></strong></a><strong><em>, </em></strong><a href="https://www.facebook.com/zeotap/"><strong><em>Facebook</em></strong></a><strong><em>, </em></strong><a href="https://twitter.com/zeotap"><strong><em>Twitter</em></strong></a><strong><em>.</em></strong></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/208/1*lfzDkvKLawmIKtxvwQS_8g.png" /><figcaption><a href="http://www.zeotap.com/">www.zeotap.com</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/338/1*5aLaZRvfwX_H_2WroHFAJg.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7a836edcf6f7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>