<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://tech.blueyonder.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://tech.blueyonder.com/" rel="alternate" type="text/html" /><updated>2024-03-18T17:36:13+00:00</updated><id>https://tech.blueyonder.com/feed.xml</id><title type="html">Blue Yonder Tech Blog</title><subtitle>A blog on technology and open source software.</subtitle><entry><title type="html">Ensuring Code Quality: A Guide to Dynamic GitHub Pull Request Gates</title><link href="https://tech.blueyonder.com/ensuring-code-quality-a-guide-to-dynamic-git-hub-pull-request-gates/" rel="alternate" type="text/html" title="Ensuring Code Quality: A Guide to Dynamic GitHub Pull Request Gates" /><published>2024-01-22T23:00:00+00:00</published><updated>2024-01-22T23:00:00+00:00</updated><id>https://tech.blueyonder.com/ensuring-code-quality-a-guide-to-dynamic-git-hub-pull-request-gates</id><content type="html" xml:base="https://tech.blueyonder.com/ensuring-code-quality-a-guide-to-dynamic-git-hub-pull-request-gates/"><![CDATA[<h1 id="ensuring-code-quality-a-guide-to-dynamic-github-pull-request-gates">Ensuring Code Quality: A Guide to Dynamic GitHub Pull Request Gates</h1>

<p>In the dynamic world of software development, maintaining the integrity of your codebase is paramount. 
GitHub, a collaborative coding platform, provides a robust toolkit to uphold the quality and security of your projects.</p>

<p>GitHub’s <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches">branch protection rules</a> 
with mandatory <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging">status checks</a> 
stand as a bulwark, ensuring that contributions can only merge into the main branch after passing through quality and 
security checks.</p>

<p>But hey, you’re still with us! 
Your interest in CI/CD, DevOps, and the intricate dance of collaboration in the software development world is palpable. 
Excellent!<br />
Now, let’s dive into the realm of <em>dynamic</em> status checks — a feature not directly supported by GitHub out of the box, 
but fear not, there are workarounds to achieve this goal.</p>

<h2 id="the-problem">The Problem</h2>

<p>In the branch protection rules of the main branch, configuring the required status checks for a
<a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests">pull request</a> 
is crucial. 
Standard checks, such as linting, unit tests, and code coverage, should be typically required for all pull requests.</p>

<p>The following is a workflow example to check the source code changes of an application.
The tests are executed in job <code class="language-plaintext highlighter-rouge">test</code> after the application got built and deployed.
Therefore, job <code class="language-plaintext highlighter-rouge">test</code> should be configured as required status check in the branch protection rules.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">src/**</span>
      <span class="pi">-</span> <span class="s">tests/**</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
    
  <span class="na">test</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-deploy</span>    
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
</code></pre></div></div>

<p>However, running this workflow is expensive and time-consuming because the application first needs to be built and 
deployed before the tests can be executed. 
And the tests themselves can also take quite some time.</p>

<p>Therefore, the test workflow should only be triggered if the source code or the tests themselves have changed.
In contrast, unrelated changes such as documentation changes should not trigger code related checks.<br />
This is why the <code class="language-plaintext highlighter-rouge">pull_request</code> workflow trigger is limited to changes in directories <code class="language-plaintext highlighter-rouge">src</code> and <code class="language-plaintext highlighter-rouge">tests</code>, as specified by
<code class="language-plaintext highlighter-rouge">paths</code>.</p>

<p>But what if the test workflow is not triggered because no files in those directories have changed?
Then, the pull request could not be merged because the required status check is not passing, as it was not executed at 
all.<br />
This is where the problem lies and why we need dynamic status checks that are only required if the relevant files have 
changed.</p>

<p>Fortunately, there are two workarounds!</p>

<h2 id="check-for-relevant-changes">Check for relevant Changes</h2>

<p>The workaround is to check for the relevant file changes in a job of the workflow itself and execute the actual status 
check only if the relevant files have changed.<br />
The <code class="language-plaintext highlighter-rouge">paths</code> of trigger <code class="language-plaintext highlighter-rouge">pull_request</code> are specified in the <code class="language-plaintext highlighter-rouge">files</code> input of the 
<a href="https://github.com/marketplace/actions/changed-files">tj-actions/changed-files</a> action (or any similar GitHub Action).</p>

<p>This status check doesn’t block the PR from merging if it gets skipped.
It only blocks it if it gets executed and fails.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Always execute, otherwise GitHub will wait forever for required checks</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">check-if-relevant</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">is-relevant</span><span class="pi">:</span> <span class="s">${{ steps.relevant-files-changed.outputs.any_changed }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># This step is needed by tj-actions/changed-files</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">fetch-depth</span><span class="pi">:</span> <span class="m">0</span>
      <span class="c1"># https://github.com/marketplace/actions/changed-files</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">tj-actions/changed-files@v41.0.1</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">relevant-files-changed</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">files</span><span class="pi">:</span> <span class="pi">|</span>
            <span class="s">src/**</span>
            <span class="s">tests/**</span>
 
  <span class="na">build-deploy</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">check-if-relevant</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">needs.check-if-relevant.outputs.is-relevant == 'true'</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
    
  <span class="na">test</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-deploy</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
</code></pre></div></div>

<h3 id="reusable-workflow-as-status-check">Reusable Workflow as Status Check</h3>

<p>The approach works fine for standard workflows, where any job can be configured as status check.
However, it doesn’t work for <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows">reusable workflows</a>.</p>

<p>In this example the test workflow utilizes reusable workflows for the build and deploy and the test steps.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">check-if-relevant</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">is-relevant</span><span class="pi">:</span> <span class="s">${{ steps.relevant-files-changed.outputs.any_changed }}</span>
    <span class="na">steps</span><span class="pi">:</span>
     <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
 
  <span class="na">build-deploy</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">check-if-relevant</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">needs.check-if-relevant.outputs.is-relevant == 'true'</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/release.build_deploy.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>    

  <span class="na">test</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-deploy</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/test.execution.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">app-url</span><span class="pi">:</span> <span class="s">${{ needs.build-deploy.outputs.app-url }}</span>
</code></pre></div></div>

<p>The calling job <code class="language-plaintext highlighter-rouge">test</code> can’t be set as status check because GitHub ignores its status.</p>

<p>Configuring a job in the called reusable workflow <code class="language-plaintext highlighter-rouge">test.execution.yml</code> is not an option either.<br />
If <code class="language-plaintext highlighter-rouge">test</code> in the calling workflow would be skipped, the pull request would wait forever for the status check in the 
reusable workflow as the skipping happens on workflow parent level, not child level.</p>

<p>The workaround is to configure an additional, standard job <code class="language-plaintext highlighter-rouge">test-completed</code> that concludes the workflow as status 
check and fails if the <code class="language-plaintext highlighter-rouge">test</code> failed before.<br />
Setting <code class="language-plaintext highlighter-rouge">if: always()</code> ensures that the job is always executed, even if the <code class="language-plaintext highlighter-rouge">test</code> job failed.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">check-if-relevant</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">is-relevant</span><span class="pi">:</span> <span class="s">${{ steps.relevant-files-changed.outputs.any_changed }}</span>
    <span class="na">steps</span><span class="pi">:</span>
     <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
 
  <span class="na">build-deploy</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">check-if-relevant</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">needs.check-if-relevant.outputs.is-relevant == 'true'</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/release.build_deploy.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>  

  <span class="na">test</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-deploy</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/test.execution.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">app-url</span><span class="pi">:</span> <span class="s">${{ needs.build-deploy.outputs.app-url }}</span>

  <span class="na">test-completed</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s1">'</span><span class="s">ubuntu-latest'</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">always()</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check test job status</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">needs.test.result == 'failure'</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">exit </span><span class="m">1</span>
</code></pre></div></div>

<h2 id="conclude-with-final-status-check">Conclude with final Status Check</h2>

<p>Another workaround is to add a concluding job like the <code class="language-plaintext highlighter-rouge">required-status-check</code> in the following example to all 
dynamically required workflows.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">src/**</span>
      <span class="pi">-</span> <span class="s">tests/**</span>

<span class="na">jobs</span><span class="pi">:</span>  
  <span class="na">build-deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>

  <span class="na">test</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-deploy</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">[</span><span class="nv">...</span><span class="pi">]</span>
    
  <span class="na">required-status-check</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s1">'</span><span class="s">ubuntu-latest'</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">always()</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check workflow status</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">needs.test.result != 'success'</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">exit </span><span class="m">1</span>
</code></pre></div></div>

<p>This job would return a failure state if the tests of job <code class="language-plaintext highlighter-rouge">test</code> failed.</p>

<p>In the repo settings configure <code class="language-plaintext highlighter-rouge">required-status-check</code> as required status check in the branch protection
rules of the main branch.<br />
The pull requests get blocked if none of the triggered workflows have implemented the <code class="language-plaintext highlighter-rouge">required-status-check</code> job or at
least one of them is failing.<br />
It gets unblocked if at least one <code class="language-plaintext highlighter-rouge">required-status-check</code> job is implemented and all of them are successful. 
It’s worth noting that at least one workflow needs to run in any case (e.g. a lint or source formatting check) and can be piggybacked to implement
this.</p>

<h2 id="comparison">Comparison</h2>

<h3 id="pro-relevant-changes-check">Pro relevant Changes Check</h3>

<ul>
  <li>This approach is less complex as the final status check workflow because its fallback workflow has to be kept in mind.</li>
  <li>It’s transparent for a GitHub repo admin what status checks are actually executed by just taking a look at the
branch protection rules. Whereas it is not obvious with a general status check such as <code class="language-plaintext highlighter-rouge">required-status-check</code>.</li>
</ul>

<h3 id="pro-final-status-check">Pro final Status Check</h3>

<ul>
  <li>Only those workflows get executed that actually apply to the changed files, because the list of relevant files are 
declared at the <code class="language-plaintext highlighter-rouge">pull_request</code> trigger.<br /> 
As a result, the status check overview of the pull request is limited to the relevant checks and no workflow is run
unnecessarily.</li>
  <li>
    <p>The triggers and specific trigger conditions can be configured declaratively.<br />
Additional triggers besides the <code class="language-plaintext highlighter-rouge">pull_request</code> that have to run in any case (even without relevant file changes) can
be configured in a clean way. And the files restriction of the <code class="language-plaintext highlighter-rouge">pull_request</code> trigger are also declared where you 
would expect them: in the <code class="language-plaintext highlighter-rouge">pull_request</code> trigger.
<br />
<br />
Declarative config:</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
  <span class="na">schedule</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0</span><span class="nv"> </span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">1'</span> <span class="c1"># 00:00 on Mondays.</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">package-lock.json</span>
</code></pre></div>    </div>

    <p>Job config:</p>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
  <span class="na">schedule</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0</span><span class="nv"> </span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">1'</span> <span class="c1"># 00:00 on Mondays.</span>
  <span class="na">pull_request</span><span class="pi">:</span>

<span class="na">jobs</span><span class="pi">:</span>    
  <span class="na">check-if-relevant</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">outputs</span><span class="pi">:</span>
    <span class="na">is-relevant</span><span class="pi">:</span> <span class="s">${{ (github.event_name == 'pull_request' &amp;&amp; steps.relevant-files-changed.outputs.any_changed == 'true') || github.event_name != 'pull_request' }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># https://github.com/marketplace/actions/changed-files</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">tj-actions/changed-files@v41.0.1</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">github.event_name == 'pull_request'</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">relevant-files-changed</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">files</span><span class="pi">:</span> <span class="s">package-lock.json</span>
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>GitHub’s branch protection rules with required status checks are a powerful tool to ensure quality and security.
They are only missing the ability to configure dynamic status checks that are only required if relevant changed had been
committed.</p>

<p>However, we have two workarounds to solve this problem:</p>
<ol>
  <li>Check for the relevant file changes in a job of the workflow itself and execute the actual status check only for 
relevant changes.</li>
  <li>Add a concluding job to all dynamically required workflows and configure it as required status check.</li>
</ol>

<p>With GitHub continuously evolving and gathering user feedback, our hope is that the need for such workarounds becomes 
obsolete.<br /> 
We look forward to a future where seamless and dynamic status checks align effortlessly with the needs of developers,
making these inventive workarounds a thing of the past.</p>]]></content><author><name>David Werner</name></author><category term="technology" /><category term="github" /><category term="devops" /><summary type="html"><![CDATA[Ensuring Code Quality: A Guide to Dynamic GitHub Pull Request Gates]]></summary></entry><entry><title type="html">Taking Text Embedding and Cosine Similarity for a Test Drive</title><link href="https://tech.blueyonder.com/text-embedding-and-cosine-similarity/" rel="alternate" type="text/html" title="Taking Text Embedding and Cosine Similarity for a Test Drive" /><published>2023-09-12T23:00:00+00:00</published><updated>2023-09-12T23:00:00+00:00</updated><id>https://tech.blueyonder.com/text-embedding-and-cosine-similarity</id><content type="html" xml:base="https://tech.blueyonder.com/text-embedding-and-cosine-similarity/"><![CDATA[<h1 id="taking-text-embedding-and-cosine-similarity-for-a-test-drive">Taking Text Embedding and Cosine Similarity for a Test Drive</h1>

<h2 id="introduction">Introduction</h2>

<p>Computational processing of natural languages has always been a difficult task for computers and programmers alike. A given concept has many representations in written text and a given written text can have many different interpretations. In addition, spelling, grammar, and punctuation are not consistent from person to person. Add in metaphors, sarcasm, tone, dialects, jargon, etc. and all compound the problem. The numerous difficulties have created a situation where, until recently, computers have been very poor at working in natural language. Recall the desperation you feel to break out of a company’s chatbot or voice-activated question-answering systems to get to a human being.</p>

<p>One specific problem that, if solved, would have many real-world applications, is the following: If we could confidently determine if two texts have the same conceptual meaning, regardless of the wording that is used, this could have a big impact on search engines. If we could also determine if text had similar or opposite meanings that would strengthen search capabilities even further.</p>

<p>Word embeddings have become a fundamental technique for capturing the semantic meaning of text. An embedding algorithm, when given text to process, returns an array, or more precisely a vector, of numbers that represents the conceptual meaning of the original text. These vectors can be of any length, but we currently see ranges from the low hundreds to over a thousand floating point numbers. Similar concepts will yield similar embedding vectors; the more different the concepts are, the more divergent the embedding vectors will be.</p>

<p>To solve our search problem, we also need a way to measure how similar or different the embeddings are from each other. There are multiple algorithms for this including Euclidean distance and cosine similarity. In this post, we will explore using cosine similarity to assess how different variations in phrasing impact semantic similarity between sentences. We will see how changes like synonyms only slightly alter vector orientations, while sentences having opposite meanings or are completely unrelated cause larger divergence.</p>

<p>The following is a two-dimensional graph showing sample embedding vectors for car, cat, and dog. The cosine similarity between the cat and dog embedding vectors is fairly small but is larger between cat and car.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-13-text-embedding-and-cosine-similarity/image-1.png" />
  <figcaption>Example of Car, Cat and Dog embedding vectors and the cosine similarity between cat and dog as well as between cat and car.</figcaption>
</figure>

<p>While cosine similarity has a range from -1.0 to 1.0, users of the OpenAI embedding API will typically not see values less than 0.4. A thorough explanation of the reasons behind this are beyond the scope of this article, but you can learn more by searching for articles about text embedding pooling.</p>

<h2 id="obtaining-embeddings-and-cosine-similarity">Obtaining Embeddings and Cosine Similarity</h2>

<p>To make this process more tangible, let’s walk through a simple example.</p>

<p>We’ll start with our original phrase: The cat ran quickly.</p>

<p>Using the OpenAI embedding model, “text-embedding-ada-002”, this will produce a 1536-dimensional vector like:  (-0.024, …, -0.021)</p>

<p>Now let’s compare it to a similar phrase: The kitten ran quickly. (-0.018, …, -0.018)</p>

<p>To quantify the similarity of these embeddings, we can use the cosine similarity metric which measures the angle between two vectors on a scale from -1 to 1. A score of 1 means identical, 0 is orthogonally different, and -1 is opposite.</p>

<p>The cosine similarity between our original phrase and synonym phrase, “The kitten ran quickly.” is 0.978, indicating the vectors are very close in meaning.</p>

<p>In contrast, an unrelated phrase like “The car was blue,.” with an embedding of  (-0.007, …, -0.017) would have a  lower cosine similarity to our original phrase, around 0.818.</p>

<h2 id="example-sentences">Example Sentences</h2>

<p>To demonstrate the impact of preprocessing on word embeddings, we will use the original phrase and show its abbreviated embedding:</p>

<p><strong>Original</strong></p>

<p>The original sentence we will compare the others to is “The cat ran quickly.” This will be compared against itself and the expected similarities would be 1.0. The original sentence provides the baseline for comparison.</p>

<table>
  <thead>
    <tr>
      <th>Sentence</th>
      <th>Embedding Vector</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The cat ran quickly.</td>
      <td>(-0.024, …, -0.021)</td>
    </tr>
  </tbody>
</table>

<p><strong>Almost Identical Meaning</strong></p>

<p>These sentences have almost the same meaning as the original sentence, with only minor variations. We expect these to have high similarity scores close to 1.0.</p>

<table>
  <thead>
    <tr>
      <th>Sentence</th>
      <th>Embedding Vector</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A cat ran quickly.</td>
      <td>(-0.025, …, -0.022)</td>
    </tr>
    <tr>
      <td>The the the The cat ran quickly.</td>
      <td>(-0.023, …, -0.019)</td>
    </tr>
    <tr>
      <td>The CaT RAn Quickly.</td>
      <td>(-0.031, …, -0.028)</td>
    </tr>
    <tr>
      <td>The cat ran, quickly!</td>
      <td>(-0.021, …, -0.029)</td>
    </tr>
    <tr>
      <td>Quickly the cat ran.</td>
      <td>(-0.022, …, -0.016)</td>
    </tr>
    <tr>
      <td>Quickly ran the cat.</td>
      <td>(-0.029, …, -0.013)</td>
    </tr>
  </tbody>
</table>

<p><strong>Conceptually Close</strong></p>

<p>These sentences are conceptually similar to the original, using synonyms or adding details. We expect moderately high similarity scores.</p>

<table>
  <thead>
    <tr>
      <th>Sentence</th>
      <th>Embedding Vector</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The kitten ran quickly.</td>
      <td>(-0.018, …, -0.018)</td>
    </tr>
    <tr>
      <td>The feline sprinted rapidly.</td>
      <td>(-0.015, …, -0.019)</td>
    </tr>
    <tr>
      <td>A kitten dashed swiftly.</td>
      <td>(-0.016, …, -0.014)</td>
    </tr>
    <tr>
      <td>The cat that was brown ran quickly.</td>
      <td>(-0.024, …, -0.027)</td>
    </tr>
    <tr>
      <td>The brown cat ran quickly</td>
      <td>(-0.023, …, -0.024)</td>
    </tr>
    <tr>
      <td>The cat’s paws moved quickly.</td>
      <td>(-0.001, …, -0.016)</td>
    </tr>
    <tr>
      <td>The cat and dog ran quickly.</td>
      <td>(-0.021, …, -0.013)</td>
    </tr>
    <tr>
      <td>The cat ran quickly?</td>
      <td>(-0.015, …, -0.026)</td>
    </tr>
  </tbody>
</table>

<p><strong>Opposites/Negations</strong></p>

<p>This group of sentences expresses the opposite meaning or negates the original sentence. We expect lower similarity scores.</p>

<table>
  <thead>
    <tr>
      <th>Sentence</th>
      <th>Embedding Vector</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The cat did not run quickly.</td>
      <td>(-0.017, …, -0.031)</td>
    </tr>
    <tr>
      <td>The cat walked slowly.</td>
      <td>(0.001, …, -0.022)</td>
    </tr>
    <tr>
      <td>The cat stopped.</td>
      <td>(-0.014, …, -0.027)</td>
    </tr>
  </tbody>
</table>

<p><strong>Unrelated Concepts</strong></p>

<p>These sentences have no relation in meaning to the original sentence about a cat running. We expect very low similarity scores.</p>

<table>
  <thead>
    <tr>
      <th>Sentence</th>
      <th>Embedding Vector</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The automobile drove fast.</td>
      <td>(-0.027, …, -0.008)</td>
    </tr>
    <tr>
      <td>The student studied math.</td>
      <td>(0.011, …, -0.04)</td>
    </tr>
    <tr>
      <td>The tree was green.</td>
      <td>(0.004, …, -0.031)</td>
    </tr>
    <tr>
      <td>The car was blue.</td>
      <td>(-0.007, …, -0.017)</td>
    </tr>
    <tr>
      <td>3+5=8</td>
      <td>(-0.015, …, -0.034)</td>
    </tr>
  </tbody>
</table>

<h2 id="computing-similarities">Computing Similarities</h2>

<p>Next, we compute the similarity between the embedding of our original sentence and each of the other sentence’s embeddings. The computed similarity between the original sentence’s embeddings and itself is included for reference. The below table shows the similarities, in descending order.</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Sentence</th>
      <th>Cosine Similarity</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Original</td>
      <td>The cat ran quickly.</td>
      <td>1.0</td>
    </tr>
    <tr>
      <td>Almost Identical</td>
      <td>A cat ran quickly.</td>
      <td>0.994</td>
    </tr>
    <tr>
      <td>Almost Identical</td>
      <td>The the the The cat ran quickly.</td>
      <td>0.989</td>
    </tr>
    <tr>
      <td>Almost Identical</td>
      <td>Quickly the cat ran.</td>
      <td>0.982</td>
    </tr>
    <tr>
      <td>Almost Identical</td>
      <td>The cat ran, quickly!</td>
      <td>0.979</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The kitten ran quickly.</td>
      <td>0.978</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The brown cat ran quickly</td>
      <td>0.975</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The cat ran quickly?</td>
      <td>0.971</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The cat that was brown ran quickly.</td>
      <td>0.968</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The feline sprinted rapidly.</td>
      <td>0.965</td>
    </tr>
    <tr>
      <td><em>Almost Identical</em></td>
      <td>Quickly ran the cat.</td>
      <td>0.965</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The cat and dog ran quickly.</td>
      <td>0.959</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>A kitten dashed swiftly.</td>
      <td>0.956</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>The cat’s paws moved quickly.</td>
      <td>0.943</td>
    </tr>
    <tr>
      <td>Opposites/Negations</td>
      <td>The cat did not run quickly.</td>
      <td>0.922</td>
    </tr>
    <tr>
      <td>Opposites/Negations</td>
      <td>The cat walked slowly.</td>
      <td>0.898</td>
    </tr>
    <tr>
      <td><em>Almost Identical</em></td>
      <td>The CaT RAn Quickly.</td>
      <td>0.89</td>
    </tr>
    <tr>
      <td>Opposites/Negations</td>
      <td>The cat stopped.</td>
      <td>0.885</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>The automobile drove fast.</td>
      <td>0.884</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>The car was blue.</td>
      <td>0.818</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>The tree was green.</td>
      <td>0.811</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>The student studied math.</td>
      <td>0.784</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>3+5=8</td>
      <td>0.75</td>
    </tr>
  </tbody>
</table>

<h2 id="analysis">Analysis</h2>

<p>The table below shows the average cosine similarity for each category, sorted in descending order. We can see pretty much what we would expect. The more similar a category of sentences is to the original sentence, the closer to 1.0 its average cosine similarities are.</p>

<table>
  <thead>
    <tr>
      <th>Category</th>
      <th>Average Cosine Similarity</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Original</td>
      <td>1.0</td>
    </tr>
    <tr>
      <td>Almost Identical</td>
      <td>0.966</td>
    </tr>
    <tr>
      <td>Conceptually Close</td>
      <td>0.964</td>
    </tr>
    <tr>
      <td>Opposites/Negations</td>
      <td>0.902</td>
    </tr>
    <tr>
      <td>Unrelated Concepts</td>
      <td>0.809</td>
    </tr>
  </tbody>
</table>

<p>The following bar chart shows each sentence and its similarity. The bar color indicates the category that the sentence belongs to:</p>

<ul>
  <li>Black for the Original sentence</li>
  <li>Blue for Almost Identical sentences</li>
  <li>Green for Conceptually Close sentences</li>
  <li>Red for Opposites/Negations</li>
  <li>Yellow for Unrelated Concepts</li>
</ul>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-13-text-embedding-and-cosine-similarity/image.png" />
  <figcaption>Graph showing Sentence similarities with the bars colored by category.</figcaption>
</figure>

<p>The bar chart visually shows that the categories clustered as expected, with the most similar sentences having cosine similarities closest to 1.0. This validates that the cosine similarity of embeddings captures semantic closeness.</p>

<p>A few possible anomalies we can see from the graph include:</p>
<ul>
  <li>“The CaT RAn Quickly.” Although it is in the “Almost Identical” category, it is lower than any sentence in the “Conceptually Close” category and is on par with “Opposites/Negations”. Differences in upper and lower case letters can affect the embedding algorithm.</li>
  <li>“The automobile drove fast.” Is particularly high among the other sentences in “Unrelated Concepts” and is closer to the “Opposites/Negatives”. This may be because the word fast implies movement which is also, represented in the original sentence.</li>
  <li>“Quickly ran the cat.” looks like it could be in the “Conceptually Close” or the “Almost Identical” categories. It is not clear why the cosine similarity is marginally smaller than most other “Almost Identical” sentences, but the difference is small.</li>
</ul>

<p>This was a small experiment but did highlight the potential of using cosine similarity of embedding vectors in language processing tasks. There does appear to be room for improvement through natural language processing techniques, such as lowercasing. However, blindly lowercasing may also negatively impact documents that are rich in acronyms, where capitalization carries meaning. As a result, a more deliberate technique may be needed.</p>

<p>Overall, we can conclude that:</p>
<ul>
  <li>The similar performance of simple averaging versus a visual categorization shows embeddings numerically capture intuitive human judgments of similarity.</li>
  <li>Synonyms and minor variations like changes in punctuation did not drastically alter the embeddings. This suggests embeddings derive meaning from overall context rather than exact word choice.</li>
  <li>The small gap between “Almost Identical” and “Conceptually Close” categories shows there’s some subjectivity in assessing similarity. The embeddings reflect nuanced gradients of meaning.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Our experiments illustrated how the cosine similarity of embeddings allows us to numerically measure the semantic closeness of text. We saw how small variations may not greatly affect similarity, while more significant changes lead to larger divergence.</p>

<p>Having the ability to map text to concepts, numerically, and then being able to compare the concepts instead of the text strings, unlocks new and improved applications such as:</p>

<ul>
  <li>Search engines - Match queries to documents based on conceptual relevance, not just keywords</li>
  <li>Chatbots/dialog systems - Interpret user intent and determine appropriate responses</li>
  <li>Voice-activated QA systems - Understand and respond accurately to spoken questions</li>
  <li>Document classifiers - Automatically group texts by topics and meaning</li>
  <li>Sentiment analysis - Identify subtle distinctions in emotional tone beyond keywords</li>
  <li>Text summarization - Determine the degree of shift in meaning between a document and its summary</li>
  <li>Machine translation -  Determine the quality of translated text</li>
</ul>

<p>Potential next steps include trying more diverse text, comparing embedding models, and testing various natural language preprocessing techniques such as the previously mentioned lowercasing of text.</p>

<hr />

<p>Bob Simonoff is</p>

<ul>
  <li>A Senior Principal Software Engineer, Blue Yonder Fellow at <a href="http://www.blueyonder.com">Blue Yonder</a>.</li>
  <li>A founding member of the <a href="https://llmtop10.com">OWASP Top 10 for Large Language Model Applications</a>.</li>
  <li>on LinkedIn at <a href="https://www.linkedin.com/in/bob-simonoff/">www.linkedin.com/in/bob-simonoff</a></li>
</ul>]]></content><author><name>Bob Simonoff</name></author><summary type="html"><![CDATA[Taking Text Embedding and Cosine Similarity for a Test Drive]]></summary></entry><entry><title type="html">Exploring ChatGPT Hallucinations and Confabulation through the 6 Degrees of Kevin Bacon Game</title><link href="https://tech.blueyonder.com/exploring-chatgpt-hallucinations-and-confabulation/" rel="alternate" type="text/html" title="Exploring ChatGPT Hallucinations and Confabulation through the 6 Degrees of Kevin Bacon Game" /><published>2023-09-07T23:00:00+00:00</published><updated>2023-09-07T23:00:00+00:00</updated><id>https://tech.blueyonder.com/exploring-chatgpt-hallucinations-and-confabulation</id><content type="html" xml:base="https://tech.blueyonder.com/exploring-chatgpt-hallucinations-and-confabulation/"><![CDATA[<h1 id="exploring-chatgpt-hallucinations-and-confabulation-through-the-6-degrees-of-kevin-bacon-game">Exploring ChatGPT Hallucinations and Confabulation through the 6 Degrees of Kevin Bacon Game</h1>

<h2 id="introduction">Introduction</h2>

<p>I know we’ve all heard about ChatGPT and the issue of hallucinations. <strong>Hallucinations</strong> refer to a model generating fabricated information that has no basis. While large language models are constantly improving, eliminating hallucinations continues to be a challenge. There are prompting techniques that can enhance accuracy and reduce hallucination, including few-shot learning, chain of thought, and tree of thought. But no technique today can fully eliminate hallucinations.</p>

<p>Confabulation involves the model filling in gaps in its knowledge by making up plausible-sounding information. So, while not completely fabricated, confabulated information may be incorrect or unverifiable.</p>

<p>While I was preparing an introductory presentation about ChatGPT, I was experimenting with various prompts to hone my demonstration. I planned to show ChatGPT acting as a brainstorming partner, automotive problem troubleshooter, and language translator. I also wanted to show that ChatGPT has limits to its knowledge and abilities. Ideally, I would be able to show hallucination and confabulation to help the audience understand they should not blindly accept all ChatGpt says.</p>

<p>One fun demonstration I decided upon involves the game “6 Degrees of Kevin Bacon”. The idea is one person chooses an actor, and then the other player tries to connect that actor to Kevin Bacon through a series of co-stars. You keep linking actors together through shared films until you get to Kevin Bacon.</p>

<p>The following is an example.</p>

<h2 id="demonstrating-6-degrees-of-kevin-bacon">Demonstrating 6 Degrees Of Kevin Bacon</h2>

<p>Let’s explore an example of the 6 Degrees of Kevin Bacon Game. The following shows how starting with Mila Kunis you can associate actors through their movie costars until you get to Kevin Bacon:</p>

<ol>
  <li>Mila Kunis → “Black Swan” → Natalie Portman</li>
  <li>Natalie Portman → “Cold Mountain” → Jude Law</li>
  <li>Jude Law→ “Contagion” → Matt Damon</li>
  <li>Matt Damon → “The Monuments Men ” → George Clooney</li>
  <li>George Clooney → “Ocean’s Thirteen” → Brad Pitt</li>
  <li>Brad Pitt → “Sleepers” → Kevin Bacon</li>
</ol>

<p>Here is the ChatGPT representation:</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_BsVO71zDWl9OVI_e3SWQQQ.png" />
  <figcaption>ChatGPT demonstrates that Mila Kunis can be connected to Kevin Bacon in 6 steps.</figcaption>
</figure>

<h2 id="chatgpt-may-tell-you-if-it-does-not-know">ChatGPT May Tell You If It Does Not Know</h2>

<p>ChatGPT can tell you if it doesn’t know about the actor. In the following, I asked ChatGPT to connect a made-up actor named Danny Feznerali to Kevin Bacon. It correctly responds that it can’t find any information about that actor.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_uHH7uTI3NV3PBKQ8eh6SIQ.png" />
  <figcaption>ChatGPT says it could not find information on the made-up actor Danny Feznerali</figcaption>
</figure>

<h2 id="chatgpt-and-minor-misspellings">ChatGPT and Minor Misspellings</h2>

<p>To a limited extent, ChatGPT can correct misspelled names. When I asked ChatGPT to connect Dakota Pfanning to Kevin Bacon, it determined that I likely meant Dakota Fanning and connected her to Kevin Bacon.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_4aocPx7VY-tjUpSaifmyYg.png" />
  <figcaption>ChatGPT successfully determines that a misspelling of Dakota Fanning can be connected to Kevin Bacon in 2 steps</figcaption>
</figure>

<p>However, if the spelling is a bit more incorrect, as in ‘Dakota Pfenning’, ChatGPT confabulates an answer. Not only does it not tell me who the presumed actor was, but Dakota Fanning nor any actor whose name looks like hers is listed in the cast according to <a href="https://medium.com/r/?url=http%3A%2F%2Fimdb.com">http://imdb.com</a>. Rather than explaining that it does not know who the actor is, as it did with Danny Feznerali, or correcting the spelling error, it confidently gives an incorrect answer.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_m7oqP4nEhcvfpvsKbjWSqQ.png" />
  <figcaption>ChatGPT hallucinates when asked about a misspelling that is further from Dakota Fanning’s name.</figcaption>
</figure>

<p>If you ask ChatGPT about this, in an attempt to understand its reasoning, you just get an apology.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_LRC6e7Cp6L4jroY-8k5rrA.png" />
  <figcaption>ChatGPT apologizes for its mistake. </figcaption>
</figure>

<p>This article will dive deeper into hallucinations and confabulations in a few moments.</p>

<h2 id="chatgpt-does-not-always-follow-directions">ChatGPT Does Not Always Follow Directions</h2>

<p>Here, I ask ChatGPT to provide an example connecting an actor or actress to Kevin Bacon through 3 stages. It does select an actor, Tom Hanks but instead of three stages it does it in a single stage through the movie “Apollo 13”</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_M179rPFHg5x8xIey8_8jrQ.png" />
  <figcaption>ChatGPT connects Tom Hanks to Kevin Bacon in 1 step via the movie "Apollo 13" rather than the requested 3 steps.</figcaption>
</figure>

<h2 id="chatgpt-can-answer-more-complex-questions">ChatGPT Can Answer More Complex Questions</h2>

<p>When asked to connect the first actor to ever have played Dracula to Kevin Bacon, it correctly reasons that it first must figure out who the first actor was to play Dracula. After it determines that Bela Lugosi played Dracula in the movie “Abbott and Costello Meet Frankenstein” it then proceeds to follow actors in movies until it gets to Kevin Bacon. Note that ChatGPT determined to not consider Max Schreck as the first Dracula from the film <em>Nosferatu</em>, presumably because the character’s name was Count Orlok. The name was changed because the producers could not afford the rights to the name Dracula.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_yZkwat26JWoXr4LC6PT9bQ.png" />
  <figcaption>ChatGPT correctly determines that the first Dracula was played by Bela Lugosi and connects him to Kevin Bacon in 3 steps </figcaption>
</figure>

<h2 id="hallucination-and-confabulation--part-1">Hallucination and Confabulation — Part 1</h2>

<p>Taking this a step further, if asked to connect the first green-eyed actor to have played Dracula to Kevin Bacon, it determines that Christopher Lee meets the criteria, then connects him to Kevin Bacon. Unfortunately, however, Christopher Lee had brown eyes.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_KTQAfhlJO0R_dGnjZ8uvHA.png" />
  <figcaption>ChatGPT incorrectly says that Christopher Lee’s brown eyes were green </figcaption>
</figure>

<p>But…. When asked about the color of Christopher Lee’s eyes, ChatGPT described them as piercing blue. So, interestingly, ChatGPT treated them as green before and now proclaims they are blue, both of which are incorrect.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_y45_NgdZr2j_iBDq3NpW_w.png" />
  <figcaption>ChatGPT demonstrates that it seems to know that Lee’s eye color is blue</figcaption>
</figure>

<p>Prompting techniques teach us that the way you ask the question makes a big difference in the outcome. So, if we think about this differently, maybe we can coerce a different result. Let us ask ChatGPT the eye color of all Dracula actors.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_UcF6F1Pi7esJjGtZj7yAkA.png" />
  <figcaption>ChatGPT lists all Dracula movie actors and their eye color — Lee’s eye color is back to brown!</figcaption>
</figure>

<p>OK assuming this list is correct, none of the actors had green eyes, however, Christopher Lee now has brown eyes. ChatGPT seems to be disagreeing with itself first green, then blue, and now brown.</p>

<p>I would like to dig into the eye color question further to see if we can untangle this mess. We’ve established that ChatGPT thinks it knows Lee’s eye color, but is inconsistent in returning it.</p>

<p>According to the website <a href="https://medium.com/r/?url=https%3A%2F%2Fwww.horror.land%2Fhistory-freaky-vampire-eyes-p1%2F%23%3A~%3Atext%3DDracula%2520%25E2%2580%2593%25201958%2Ceyes%2520look%2520red%2520and%2520angry.">Horror Dot Land</a>. “Christopher Lee’s most famous look, using mini sclera contact lenses. Dark Brown iris with veined sclera that makes the eyes look red and angry.” The site also crops the image, focusing on the eyes to show the brown-eyed Dracula.</p>

<p>If we go to a different website, <a href="https://medium.com/r/?url=https%3A%2F%2Fwcelebrity.com%2Fchristopher-lee-height-weight-age-biography-husband-more%2F">WC (WCelebrity.com)</a>, it tells us that Christopher Lee has brown eyes and a size 11 shoe, if you care.</p>

<p>Another website <a href="https://www.romance.com.au/we-ranked-our-favourite-draculas-of-all-time/">romance.com.au</a> describes another actor Luke Evans** in “Dracula Untold” (2014) who was “… cut cheekbones. And unruly hair. A five o’clock shadow. <strong>Piercing blue</strong> eyes…”. This statement does appear on the same page as a separate description of Christopher Lee, however, Lee’s eye color is not mentioned.</p>

<p>So, what color were Christopher Lee’s piercing blue/brown/green eyes? Simply looking at pictures online, it is apparent that his eyes are brown.</p>

<p>This reveals how large language models like ChatGPT can make erroneous claims even when they seem knowledgeable. With no reasoning skills or factual grounding, ChatGPT generates plausible-sounding answers based solely on patterns in its training data. The very design of ChatGPT means it has no concept of how it “knows” something — it just predicts the next word in a sequence, regardless of overall paragraph accuracy.</p>

<p>We are not able to review the training data ChatGPT was exposed to or analyze its neural network, so we will never know why ChatGPT responded inconsistently and incorrectly.</p>

<p>This demonstrates that users should be skeptical of ChatGPT’s “facts”. Until models incorporate explainability, reasoning, common sense, and a sense of epistemology, mistakes will persist despite demonstrably impressive capabilities.</p>

<h2 id="hallucinations-and-confabulation--part-2">Hallucinations and Confabulation — Part 2</h2>

<p>To show that eye color was not a one-time problem, this example will demonstrate the same by exploring actors from the Czech Republic who played Dracula.</p>

<p>Let’s ask ChatGPT to connect the first Czech actor to have played Dracula with Kevin Bacon.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_erhRxj8caKNTBpYUnrWdjQ.png" />
  <figcaption>ChatGPT claims that the first Czech actor to play Dracula was Max Schreck</figcaption>
</figure>

<p>Max Schreck, interesting, is now Dracula. Even more interesting is that ChatGPT also knows that Max never lived in Czechoslovakia. Max lived in Germany his entire life according to ChatGPT. Nosferatu, however, was filmed in Czechoslovakia.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_OnLJ6pRyO2Ld6cCmvazJog.png" />
  <figcaption>ChatGPT shows that it also thinks that Max Schreck lived his whole life in Germany</figcaption>
</figure>

<p>Maybe a different tact will yield a Czech actor who played Dracula. Let’s ask for a list of all of the actors from Czechoslovakia who played Dracula.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_ieknrekcg7TMtrjDe9vuUQ.png" />
  <figcaption>ChatGPT claims there are no Czech actors to have played Dracula?!?!</figcaption>
</figure>

<p>ChatGPT claims there are no actors from Czechoslovakia to have played Dracula. But, I wonder if ChatGPT knows otherwise. Let’s ask if Hrabě Drakula is a Czechoslovakian Dracula movie.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_sUJRzFBc_obdGLDPl1NMLg.png" />
  <figcaption>ChatGPT affirms there there is indeed a Czech Dracula movie where Jiří Hrzán played Dracula</figcaption>
</figure>

<p>Indeed it is! But maybe ChatGPT is confusing the idea of a Czechoslovakian Dracula movie with a Czechoslovakian Dracula actor.</p>

<figure>
  <img src="https://tech.blueyonder.com/assets/images/2023-09-08-exploring-chatgpt-hallucinations/1_PTyDcnNQiyJ1rJL9-Bq6sQ.png" />
  <figcaption>ChatGPT demonstrates knowledge that Jiří Hrzán is from Czechoslovakia. </figcaption>
</figure>

<p>Nope, just like the eye color question, ChatGPT seems to have confused itself. It knows the answer but doesn’t return it unless the question is asked differently. Also, Jiří Hrzá was not listed in the list of Dracula actors ChatGPT created earlier.</p>

<h2 id="conclusion">Conclusion</h2>

<p>ChatGPT represents an incredibly powerful technology, with new applications being uncovered daily as more explore its diverse capabilities — from law and medicine to wine expertise. However, as shown through examples of hallucination and confabulation, limitations exist in its knowledge and reasoning.</p>

<p>While future versions may overcome current limitations, for now, users should approach ChatGPT’s responses with skepticism and fact-check against authoritative sources. Its answers cannot be taken as absolute truth without capabilities like reasoning, common sense, and self-consistency. Increased transparency into its training data and methodology could also help users gain confidence in ChatGPT’s responses.</p>

<p>When used with care, ChatGPT can be a helpful assistant, but attribution should be provided if directly using its output. ChatGPT has enormous promise but still requires human discernment. By combining its strengths with the strengths of the human mind, we can leverage this very new and powerful tool.</p>

<p><em>Note: <a href="https://medium.com/r/?url=http%3A%2F%2Fclaude.ai">claude.ai</a> from Anthropic was used for grammar and spelling corrections. It was also used for brainstorming ideas in the conclusion section, however, all words are strictly my own.</em></p>

<p>Bob Simonoff is</p>

<ul>
  <li>A Senior Principal Software Engineer, Blue Yonder Fellow at <a href="http://www.blueyonder.com">Blue Yonder</a>.</li>
  <li>A founding member of the <a href="https://llmtop10.com">OWASP Top 10 for Large Language Model Applications</a>.</li>
  <li>on LinkedIn at <a href="https://www.linkedin.com/in/bob-simonoff/">www.linkedin.com/in/bob-simonoff</a></li>
</ul>]]></content><author><name>Bob Simonoff</name></author><summary type="html"><![CDATA[Exploring ChatGPT Hallucinations and Confabulation through the 6 Degrees of Kevin Bacon Game]]></summary></entry><entry><title type="html">Coiled Webinar ‘Science Thursday’: Data Processing at Blue Yonder - One Supply Chain at a Time</title><link href="https://tech.blueyonder.com/coiled-webinar-post-event-writeup/" rel="alternate" type="text/html" title="Coiled Webinar ‘Science Thursday’: Data Processing at Blue Yonder - One Supply Chain at a Time" /><published>2020-12-08T23:00:00+00:00</published><updated>2020-12-08T23:00:00+00:00</updated><id>https://tech.blueyonder.com/coiled-webinar-post-event-writeup</id><content type="html" xml:base="https://tech.blueyonder.com/coiled-webinar-post-event-writeup/"><![CDATA[<h1 id="coiled-webinar-science-thursday-data-processing-at-blue-yonder---one-supply-chain-at-a-time">Coiled Webinar “Science Thursday”: Data Processing at Blue Yonder - One Supply Chain at a Time</h1>

<blockquote>
  <p>This is a guest article by <a href="https://coiled.io/blog/author/christiana/">Christiana Cromer</a> and first appeared on the 
<a href="https://coiled.io/blog/data-processing-at-blue-yonder-one-supply-chain-at-a-time-2/">Coiled blog</a>. 
It is a write-up of the Coiled “Science Thursday” session with Florian Jetter from Blue Yonder.
<a href="https://coiled.io/">Coiled</a> is the company founded by 
<a href="https://www.linkedin.com/in/matthew-rocklin-461b4323/">Matthew Rocklin</a>, creator and maintainer of <a href="https://dask.org/">Dask</a></p>
</blockquote>

<p>Recently, Coiled’s Head of Marketing and Evangelism Hugo Bowne-Anderson and Software Engineer James Bourbeau were joined 
by <a href="https://www.linkedin.com/in/florian-jetter-5202a9146/">Florian Jetter</a>, Senior Data Scientist at Blue Yonder, for a 
Science Thursday on “Data Processing at Blue Yonder: One 
Supply Chain at a Time”. Blue Yonder provides software-as-a-service products around supply chain management.</p>

<p>In supply chain management, billions of decisions must be made surrounding how much to order, when to ship products, how 
much stock to keep in distribution centers, and so on. Blue Yonder automates and optimizes the decision making processes 
of supply chains with machine learning and scaled data science. They heavily leverage Python and Dask for processing 
data-intensive workloads, and a significant part of Florian’s job surrounds maintaining Dask for Blue Yonder.</p>

<p>You can catch the live stream replay on our YouTube channel by clicking below.</p>

<iframe width="924" height="520" src="https://www.youtube.com/embed/etMNL2zn9cM" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>Thanks to Florian’s comprehensive demo, after reading this post, you’ll know:</p>

<ul>
  <li>That buying milk at your local supermarket is a highly non-trivial process,</li>
  <li>How to incorporate a relational database into your data pipeline,</li>
  <li>How to build a ML data pipeline at terabyte scale using Parquet, Dask, and Kartothek,</li>
  <li>How to join data at scale without breaking a sweat (kind of),</li>
  <li>Where to go next with resources on Dask, supply chains, and more.</li>
</ul>

<p>Thank you to Florian and his colleague <a href="https://www.linkedin.com/in/sebastian-neubauer">Sebastian Neubauer</a>, 
Senior Data Scientist at Blue Yonder, who also joined us for this live stream, for the feedback on an earlier draft of 
this article!</p>

<h1 id="blue-yonders-approach-to-the-problem-of-supply-chain-management">Blue Yonder’s approach to the problem of supply chain management</h1>

<blockquote>
  <p>“We need to crunch this together, ideally in a way that is cheap to store and in a way that data scientists can 
scale-out, and this is where Dask comes in.”</p>
</blockquote>

<p>First things first, we were curious to know how many clusters Blue Yonder uses. Florian said, “We have a lot of 
customers and different environments, this all totals to around 700 clusters at the moment.” 200 of those clusters are 
production use cases, which means they’re fully automated with no human involvement.</p>

<p>Before we jumped into the demonstration, Florian gave us a lesson on supply chain networks, outlined through the 
example of a grocery store buying milk. When thinking about supply chain fulfillment, two questions emerge:</p>

<p>1) The prediction: What is the demand? (which turns out to be a huge machine learning problem), and</p>

<p>2) The decision: What’s the optimal amount we should order, considering the current state of the supply chain?</p>

<p>Blue Yonder approaches the concept of supply chain management by breaking it down into pieces. A relatively simple 
example could be: store level demand forecast (ML), store/distribution center network optimization, vendor ordering, 
and truckload, as seen in the graph below.</p>

<p><img src="/assets/images/2020-12-09-coiled-webinar-post-event4.png" alt="supply chain network" /></p>

<p>This an ongoing and iterating process. Florian noted that “the number of feedback loops involved makes this a highly 
non-trivial problem”. So, your glass of milk is more complicated than it looks!</p>

<p>We then jumped into a demonstration in which Florian walked us through an example of building out a ML data pipeline 
for a mid-sized vendor. We saw how quickly this involves 10 million time series. You can follow along with this 
<a href="https://github.com/fjetter/coiled-kartothek-demo">notebook</a>.</p>

<p><img src="/assets/images/2020-12-09-coiled-webinar-post-event5.png" alt="supply chain network" /></p>

<p>As an example, using 10 years of data from the customer, Blue Yonder has to process 40 terabytes of uncompressed data, which 
represents a huge data engineering challenge.</p>
<blockquote>
  <p>“We need to crunch this together, ideally in a way that is cheap to store 
and in a way that data scientists can scale-out, and this is where Dask comes in.”</p>
</blockquote>

<p>Everything after the initial data 
ingestion, which is a web service, is then exclusively powered by Dask. Optimizing each customer’s specific network for 
things like strategy and regional events creates another layer of complexity and data processing.</p>

<p><img src="/assets/images/2020-12-09-coiled-webinar-post-event6.png" alt="Science Thursday" /></p>

<p>James asked a great question about how COVID-19 impacted the machine learning models and engineers at Blue Yonder. 
“When COVID hit us it was a big deal because our customers were hugely affected and I must admit our machine learning 
models couldn’t easily cope with this drastic of a change on demand and supply.” In response to the pandemic, Florian 
explained how Blue Yonder created a task force to look at the way the machine learning models were impacted and build 
out different models that could better withstand the ongoing turbulent conditions. Furthermore, due to the high level 
of automation and standardization in the whole process, the changes could be applied to the customers much faster than 
it would be possible without machine learning.</p>

<h1 id="scaling-supply-chain-decisions-with-dask-path-to-an-augmented-dataset">Scaling Supply Chain Decisions with Dask: Path to an Augmented Dataset</h1>

<blockquote>
  <p>“The first problem we want to solve is how do we get this data out of the database and into a format where we can 
actually work with it.”</p>
</blockquote>

<p>To get giant amounts of customer data into Dask, Florian outlines five key ingredients for success:</p>

<ol>
  <li>Connection</li>
  <li>SQL / Query</li>
  <li>Schema (Depends on 2.)</li>
  <li>Partitions</li>
  <li>Avoid killing your database</li>
</ol>

<p>For a fast connection, Blue Yonder uses turbodbc, which you can learn 
more about <a href="https://turbodbc.readthedocs.io/en/latest/">here</a>. When it comes to the SQL Query, you of course need to 
write your query in a way that you can download 
subsets of data. Blue Yonder does this by slicing by time dimension and by a partition ID. Finding the right partition 
ID and splitting your data up into smaller, more manageable pieces is essentially the pre-processing groundwork before 
Dask can take over.</p>

<p>Florian then dove into showing us how Dask comes in and scales out the process. James put on his Dask maintainer hat to 
thank the Blue Yonder team for contributing, owning, and maintaining the semaphore implementation. Florian noted that 
though it looks easy, this process was imperative to avoiding bottlenecks and deadlocks with their customer data.</p>

<blockquote>
  <p>“This whole thing needed to be resilient. What I’m showing is a simple problem but in the background, it’s of course 
incredibly complex”</p>
</blockquote>

<h1 id="ml-data-pipeline-at-terabyte-scale">ML data pipeline at terabyte scale</h1>

<p>Florian then showed us how to build out a ML data pipeline at terabyte scale using Parquet, Dask, and Kartothek.</p>

<blockquote>
  <p>“We want to persist intermediate data as a parquet file using Kartothek to create resilience, consistency (e.g. data 
may change an hour later), and for data lineage purposes.”</p>
</blockquote>

<p><a href="https://kartothek.readthedocs.io/en/latest/">Kartothek</a> is an open-source library that is essentially a storage layer 
for parquet data created by Blue yonder. Florian noted:</p>

<blockquote>
  <p>“If you have huge parquet data sets and you need more control over how they are produced and managed and additional 
metadata information, this is where Kartothek comes into play.”</p>
</blockquote>

<p>He also explained the implementation of a partition encoding key for compatibility purposes.</p>

<p>Where exactly is the ML? Using Dask, Florian was able to apply a machine learning model to each partition of his 
dataset. This lets Blue Yonder’s machine learning experts make complex predictions. Florian said:</p>

<blockquote>
  <p>”Once we have these predictions it’s also just a Dask dataframe. I’ll store them again as a Kartothek data set and I 
can build other indices on my predictions…and then data scientists can browse these prediction tables however they 
want”.</p>
</blockquote>

<h1 id="resources">Resources</h1>

<p>A huge thank you to Florian Jetter for leading us through this fantastic session and to the entire team at Blue Yonder! 
We covered a lot of ground during this live stream and in this blog. Here are some resources for further learning:</p>

<ul>
  <li>The <a href="https://github.com/fjetter/coiled-kartothek-demo">notebook</a> Florian used for this session</li>
  <li><a href="turbodbc-turbocharged-database-access-for-data-scientists">Turbodbc - Turbocharged database access for data scientists</a></li>
  <li><a href="../introducing-kartothek/">Introducing Kartothek - Consistent parquet table management powered by Apache Arrow and Dask</a></li>
  <li><a href="../cube-functionality-to-kartothek/">Cube functionality (easy joining of datasets) in Kartothek</a></li>
  <li><a href="../dask-usage-at-blue-yonder/">Dask Usage at Blue Yonder</a></li>
</ul>]]></content><author><name>Sebastian Neubauer</name></author><category term="technology" /><category term="python" /><category term="data-engineering" /><summary type="html"><![CDATA[Coiled Webinar “Science Thursday”: Data Processing at Blue Yonder - One Supply Chain at a Time]]></summary></entry><entry><title type="html">The Sunk Cost Fallacy</title><link href="https://tech.blueyonder.com/the-sunk-cost-fallacy/" rel="alternate" type="text/html" title="The Sunk Cost Fallacy" /><published>2020-11-08T11:08:00+00:00</published><updated>2020-11-08T11:08:00+00:00</updated><id>https://tech.blueyonder.com/the-sunk-cost-fallacy</id><content type="html" xml:base="https://tech.blueyonder.com/the-sunk-cost-fallacy/"><![CDATA[<h1 id="the-sunk-cost-fallacy">The Sunk Cost Fallacy</h1>

<p>I gazed out of the window. The birds were chirping and the weather was sunny. People were trying to get their first dose of coffee from the nearby coffee shop. Some tables were occupied by people who were hustling and busy getting work done on their laptops while some tables were occupied by people trying to relax and take pleasure from every sip of their coffee. I smirked while observing them, I felt their collective range of emotions in past seven months. One day, I was the guy trying to make every minute count while another day trying to take in the essence of the surroundings.</p>

<h2 id="t---life-is-beautiful">T - Life is beautiful</h2>

<p>I was already late for the meeting. I rushed through the corridor to reach the meeting hall to find my superior waiting for me. I had to take over a feature project from him and had a knowledge transfer session with him. The project included an end to end feature which means touching and adding lots of code. I analysed the design and cross-checked the expectations with the team and stakeholders, everything seemed fine. Exciting times were ahead!</p>

<p>A meeting was called upon with members assigned to this feature development project. We had to decide if we want to make our feature scalable and flexible for further iterations to come. Pros and cons were discussed for every point made and it was decided we would spend more time to make the feature scalable and flexible so that milestones concerning further feature iterations would be achieved in no time. Implementation seemed fairly simple and major points made in the meeting were duly noted.</p>

<h2 id="t--2---fire">T + 2 - Fire</h2>

<p>I was looking at my coffee mug. The coffee looked extra dark today. My mind was boggling with one hypothesis and I was trying to convince myself that it’s a lie. The coffee was not dark. My thoughts were.</p>

<p>Z: “Hey! You okay?”</p>

<p>My colleague was standing beside me without his laptop. He was seemingly not happy.</p>

<p>Z: “I think this is a bug, it should not work like this.”</p>

<p>Me: “It is correct, It’s part of the feature.”</p>

<p>Z: “We did not discuss this in the design meeting.”</p>

<p>Me: “We should have discussed this, I remember it being documented.”</p>

<p>I checked the documentation and this part of the feature was not to be found anywhere. My two worst fears had come true.</p>

<ul>
  <li>
    <p>I didn’t document the meeting notes of my KT and this part of the feature somehow skipped being documented in design notes.</p>
  </li>
  <li>
    <p>Every team member working on this feature has a different understanding of the feature.</p>
  </li>
</ul>

<h2 id="t--4---let-it-go">T + 4 - Let it go</h2>

<p>I was walking by the river. The sky was looking beautiful with clouds of different shapes chatting with each other and the air passing through the meadow beneath seemed like it was whispering something to me. I closed my eyes focusing on its voice.</p>

<p><em><strong>“Let it go”</strong></em>.</p>

<p>I opened my eyes. The world was on fire. Knowledge transfer became more difficult. Pair programming was no longer sitting beside your colleagues and watching them code but it was highly dependent on your house’ internet connectivity.</p>

<p>The progress of the feature seemed like a hallucination. In pursuit of scalability and adaptability, the design had become complicated, to begin with. We had already crossed the deadlines. There was no escape from this “black hole”.</p>

<p>I fetched myself a cup of coffee, sat on my desk and opened my laptop. My dream was still lingering in my mind and I was not sure what the dream meant. I looked at the code. A thought came to me.</p>

<p>“Why not rewrite the implementation of this feature with a much simpler and leaner design !?.”</p>

<p>But another thought hit me, which threw me into a dilemma.</p>

<p>“We have invested so much time working on this for a couple of months. Even though the design is complicated, we should continue following it since we are already midway. It will anyway fulfil the requirement even if it takes longer than undoing current implementation and rewriting a new one. “</p>

<p>After some thought process, I came to a conclusion,</p>

<p>“The initial “complicated” design consisted of so many assumptions out of which some were revealed to be not incorrect. We had to anyway rethink those parts of design again. This initial design also aimed to be inclusive of future milestones which we were never certain that they would get approved by the management. It makes sense to rewrite this feature with narrowed down design to cover just the customer requirements and not think much of the future and milestones.”</p>

<p>I discussed this with the team and we chose to rewrite this feature code with a simple design.</p>

<h2 id="t--7---the-end">T + 7 - The end</h2>

<p>It was a pleasant morning. I had 10 minutes left for the release meeting. A quote from my favourite game, Skyrim struck me.</p>

<p><em>“I fight so that all the fighting I’ve already done hasn’t been for nothing. I fight… because I must.”</em></p>

<p>The king wanted to keep fighting even after he lost half of his men, just so that their sacrifices don’t go to waste.</p>

<p>Economists coined a term for this, <strong>“sunk cost fallacy”</strong>.</p>

<p>The Decision Lab defined it as</p>

<p><strong>“The Sunk Cost Fallacy describes our tendency to follow through on an endeavor if we have already invested time, effort or money into it, whether or not the current costs outweigh the benefits.”</strong></p>

<p>There are various ways to avoid this, the easiest being to let go of your fear and attachment to failing projects and make room for better brighter things. And who knows, this might actually pave out a way to further successful and profitable projects.</p>

<p>It was time for my meeting. My manager was about to announce the completion of this feature which we devoted several months on.</p>

<p>“So finally we managed to complete this feature this release”.</p>

<p>We sighed with relief. We had just finished the most complicated and long-lasting feature project our team had ever faced. One of my team members even took a long holiday once we were finished.</p>

<p>I quickly glanced through my diary. It had scribbles of the lessons I had learnt. They were written as follows:</p>

<ul>
  <li>
    <p>Use sunk cost fallacy whenever applicable.</p>
  </li>
  <li>
    <p>Don’t assume things. Take note of “known unknowns” and find answers to them before making major decisions.</p>
  </li>
  <li>
    <p>Document everything.</p>
  </li>
  <li>
    <p>Follow the KISS (<strong>(K)eep (I)t (S)imple (S)tupid</strong>) methodology wherever possible.</p>
  </li>
  <li>
    <p><strong>Teamwork</strong> is necessary to revive failing projects. It is the foundation on which successful projects stand upon.</p>
  </li>
</ul>

<p>I closed my diary. “It was a long adventure”, I thought. Now it’s time for another one.</p>

<p><em>“Blessed are the curious as they shall have adventures”</em> – Lovelle Drachman</p>

<p>Disclaimer: The sole aim of the article is to emphasize sunk cost fallacy and not to criticise anyone’s decision making or code.</p>]]></content><author><name>Aniruddh Goteti</name></author><category term="management-philosophy" /><category term="sunk-cost-fallacy" /><summary type="html"><![CDATA[The Sunk Cost Fallacy]]></summary></entry><entry><title type="html">Java: Giants and Infinite Loops</title><link href="https://tech.blueyonder.com/java-giants/" rel="alternate" type="text/html" title="Java: Giants and Infinite Loops" /><published>2020-09-01T12:00:00+00:00</published><updated>2020-09-01T12:00:00+00:00</updated><id>https://tech.blueyonder.com/java-giants</id><content type="html" xml:base="https://tech.blueyonder.com/java-giants/"><![CDATA[<h1 id="java-giants-and-infinite-loops">Java: Giants and Infinite Loops</h1>

<p>I have an easy-to-code, hard-to-spot, impossible-to-debug infinite loop puzzle for us to solve.  To help us see the issue, I also have a small handful of amazing people to introduce who have helped me solve numerous problems.  But, first, some introductory quotes:</p>

<blockquote>
  <p>This should terrify you! – Douglas Hawkins, java JVM and JIT master, while introducing this puzzle.</p>
</blockquote>

<blockquote>
  <p>If I have seen further it is by standing on the shoulders of Giants – Isaac Newton</p>
</blockquote>

<p>The first is from a java JVM and just-in-time (aka JIT) compiler master, as quoted from one blurb in a 100+ minute presentation on his area of expertise. The second is from a scientist who himself became a giant whose shoulders uncounted others – famous, incredible others – have stood upon.  Both are relevant to this blog, but first the puzzle:</p>

<h2 id="the-seemingly-impossible-infinite-loop">The Seemingly-Impossible Infinite Loop</h2>
<p>Let’s start with Hawkins and his “terrifying” situation, who at the time was explaining a trivial-to-code <em>infinite-loop</em> bombshell.</p>

<p><strong>Shared Variables:</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Object</span> <span class="n">dataShared</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>  <span class="c1">//some data that the producer will create.  </span>
<span class="kt">boolean</span> <span class="n">sharedDone</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>  <span class="c1">// true only when we have produced dataShared.  </span>
</code></pre></div></div>
<p><strong>Producer Thread:</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dataShared</span> <span class="o">=</span> <span class="err">…</span><span class="o">;</span>   <span class="c1">// some data is being produced here by the thread</span>
<span class="n">sharedDone</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>  <span class="c1">//we are done producing so let the consumer make use of it</span>
</code></pre></div></div>
<p><strong>Consumer Thread:</strong></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="o">(</span> <span class="n">sharedDone</span> <span class="o">==</span> <span class="kc">false</span><span class="o">);</span>   <span class="c1">//busy wait spin until producer is finished.   </span>
<span class="n">print</span> <span class="o">(</span><span class="n">dataShared</span><span class="o">);</span>   <span class="c1">// executes once sharedDone is set to true by the other thread</span>
</code></pre></div></div>
<p>The intent is for Consumer Thread to busy-spin while Producer Thread finishes creating dataShared.  Once notified that dataShared is ready (via a true value for sharedDone), the consumer will make use of dataShared.</p>

<p>Unfortunately, this is likely to loop infinitely.  Do you see the issue?   Amazingly, if you create a test program and debug this, you will never reproduce the infinite loop.   If you create a trivial test program, you also likely will <em>not</em> reproduce the issue.  If you put this into production with real producer and consumer logic, you most likely <em>will</em> reproduce the infinite loop fairly consistently (except this time you were really hoping you would <em>not</em>).   What on earth could the issue here be?</p>

<p>This brings me to Newton.</p>

<h2 id="isaac-newton">Isaac Newton</h2>
<p>Newton’s statement reads like a humble admission but is more accurately a <strong>roadmap for success</strong>.  <a href="https://www.brainpickings.org/">Brain Pickings</a> has a nice article, <a href="https://www.brainpickings.org/2016/02/16/newton-standing-on-the-shoulders-of-giants/">Standing on the Shoulders of Giants</a>, on the background of the Newton quote.  At the time, the notion that one should study past masters while in pursuit of new knowledge was a minority viewpoint.   Somehow the need to rely on others was a bruise to the ego.   Newton understood, however, that revolutionary ideas can come from combining existing knowledge from mundane sources.   Einstein would later call this creative process <strong>“combinatory play”</strong>.  And, indeed, what Newton produced was truly revolutionary.   Let’s take his advice and tour some masters to see if they can help with this puzzle.</p>

<h2 id="martin-thompson">Martin Thompson</h2>
<p>So, if at first glance the code looks clean then maybe there is something hidden in the hardware architecture.  The giant who first introduced me to the great impact of computer architecture on production software is <strong>Martin Thompson</strong>, as found in a one-hour presentation <a href="https://www.infoq.com/presentations/LMAX/">How to do 100K TPS at Less than 1ms Latency</a> with Michael Barker that is very relevant today despite having been recorded in 2010.  Martin Thompson, borrowing a term coined by racing driver Jackie Stewart, explains that you must have some <strong>“mechanical sympathy”</strong> to understand how to write code that runs well on production-quality hardware.   Even if you do not use the software pattern ultimately revealed in this video, the video itself is an excellent display of using mechanical sympathy in software design considerations.</p>

<p>I will pause a moment while you watch the video…. All good? So, having learned all about the hidden magic of CPUs, caches, and main memory, my first educated guess for the infinite loop above is the following:   Perhaps we have multiple copies of “sharedDone” in our L1 to L3 CPU core caches.  While one CPU’s cache has been set to “true”, perhaps the other may still say “false”.   If true, the Consumer Thread, not knowing it has a stale version of the truth, would loop on and on.   Is this actually possible?</p>

<p>In my first version of this blog, I thought it was indeed possible for caches to have mismatched information such that they could create an infinite loop.  Luckily I asked for a blog review before posting and the person I will properly introduce next disabused me of this idea.   Although there is indeed something missing from our code above to prevent the infinite loop, the reason <em>why</em> in this case has nothing to do with hardware.   For a given memory location (such as the value of <em>sharedDone</em>), the hardware must give the illusion of a single value across all memory.   So, although in reality you may have different values between caches and main memory from time to time, you will never <em>observe</em> this to be true.</p>

<p>Let’s properly introduce our next master.</p>

<h2 id="aleksey-shipilev">Aleksey Shipilev</h2>
<p>So, if the hardware is happy, is the problem in the java memory model somehow? The next giant on our tour is someone who can speak to that: JVM master <strong>Aleksey Shipilev</strong>.   Just reading through his articles in <a href="https://shipilev.net/jvm/anatomy-quarks/">JVM Anatomy Quarks</a> will amaze you with how much you don’t know you don’t know.  See also his widely-used microbenchmark framework <a href="https://openjdk.java.net/projects/code-tools/jmh/">JMH</a>.  For this specific exercise, we can learn much from another hour-long video titled <a href="https://www.youtube.com/watch?v=TK-7GCCDF_I">Java Memory Model Unlearning</a>.   Please enjoy.</p>

<p>…Okay, well, if you are like me, you found the start of the video a bit intimidating.   However, if you had the patience to get through it, I am sure by the end you appreciated the journey (and the speaker).  Hey, he’s a giant – these topics are indeed challenging!   As a reward for your patience, you almost certainly now know more about this subject than <em>anyone else</em> at your company – a newly minted giant yourself.</p>

<p>So, now we see we must tell java that more than one thread will be updating and reading this variable by using the keyword “volatile”.  With “volatile”, java knows that any changes to “sharedDone” must be instantly published to all threads that can observe this field.   This is why you may have already found “volatile” in your career if you have searched for “java double checked locking”.  (My favorite Shipilev quote from the video: “If you are not sure where to put volatile, put it freakin’ everywhere!”)</p>

<p>(In fact, Shipilev reminds me that since we only have <em>one</em> variable that requires coherency between the threads, we could technically use “getOpaque()” and “setOpaque()” with “VarHandle” rather than the stronger “volatile”.  If you watched his talk, or if you would like to study the Opaque section in Doug Lea’s paper <a href="http://gee.cs.oswego.edu/dl/html/j9mm.html">Using JDK 9 Memory Order Modes</a>, which Shipilev pointed me to, you can further appreciate this fine point.   Finally, note that Shipilev contributed comments and suggestions to the Doug Lea paper, as did other giants – I find this community of computer scientists so impressive…)</p>

<p>Well, we know how to fix the problem, but do we really understand <em>how</em> the absence of “volatile” creates an infinite loop?   If the hardware must give the illusion of a single value across memory, why exactly is “volatile” important?   How exactly do all of these concepts Shipilev just taught us really come into play?</p>

<h2 id="douglas-hawkins">Douglas Hawkins</h2>
<p>Well, I guess I must give up and return to the person who proposed the puzzle, our giant <strong>Douglas Hawkins</strong>.   Of his many presentations (watch them all!), my favorite is the 100+ minute presentation <a href="https://www.youtube.com/watch?v=oH4_unx8eJQ">Understanding the Tricks Behind the JIT</a> where he mentions this specific puzzle. Here, Hawkins gives an expert and entertaining dive into the java just in time (aka JIT) compiler.   I will again pause for you to watch it, including minute 54 that sparked this post.</p>

<p>…All done watching?   So, now you fully understand the final reason the above will loop infinitely.   The code you write, which is compiled to bytecode, is much closer to a “statement of intent” than a specific “mandate” to do exactly what you say.   The debugging experience will fool you into thinking every line is executed as-is, in code order.   However, in production, this is a lie.</p>

<p>The JIT employs many optimizations as it transforms bytecode to assembler code.   By “optimization” the JIT means “I’ll rewrite your code for optimal performance”.   One of these optimizations is called “loop invariant hoisting” where a value which cannot change in the current thread can be rewritten as a local constant.   If you do not use “volatile”, the JVM assumes that multi-thread situations need not be considered.  “Volatile” and “VarHandle” do not exist to tackle issues caused by the hardware.  They exist to better inform the JIT optimizer of what it can and cannot do!</p>

<p>So, expanding on the Hawkins video further for this puzzle, we have:</p>

<ol>
  <li>The Consumer thread code can legally be rewritten by the JIT (in the absence of the “volatile” keyword) as the following:
    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">boolean</span> <span class="n">localDone</span> <span class="o">=</span> <span class="n">sharedDone</span><span class="o">;</span> <span class="c1">// local constant, will never change</span>
<span class="k">while</span> <span class="o">(</span> <span class="n">localDone</span> <span class="o">==</span> <span class="kc">false</span><span class="o">);</span>   <span class="c1">//infinite spin, clearly localDone is never updated.  </span>
<span class="n">print</span> <span class="o">(</span><span class="n">dataShared</span><span class="o">).</span>  <span class="c1">//never gonna happen </span>
</code></pre></div>    </div>
  </li>
  <li>This rewriting is done by the JIT as it transforms bytecode to assembler at run time, if it so chooses to do this optimization.   If sharedDone is false at optimization time, we loop infinitely.  If it is true at optimization time, you would probably throw an exception repeatedly while trying to use uninitialized objects (because the Producer actually has not completed).</li>
  <li>When stepping through the debugger, you are stepping over code in “bytecode land” only, not the rewritten assembler.   So, this infinite loop is impossible to reproduce in the debugger, and suddenly you are questioning your career choice.</li>
</ol>

<p>Terrifying?   Yes, yes I believe so.</p>

<h2 id="chris-newland">Chris Newland</h2>
<p>Our mystery is solved, but the greater lesson here is that the JIT will rewrite your code under high loads for performance, and a “loose understanding” of java coding and memory-model rules can lead to unintended consequences.   A great (surprisingly short) article to see JIT rewriting in detail is Shipilev’s first JVM Anatomy Park article <a href="https://shipilev.net/jvm/anatomy-quarks/1-lock-coarsening-for-loops">Lock Coursening and Loops</a>.   But, we can do even better.</p>

<p>A fourth giant related to this discussion is <strong>Chris Newland</strong> who created the visualization tool <a href="https://github.com/AdoptOpenJDK/jitwatch">JITWatch</a> for you to see your code, the compiled bytecode, and the JIT assembler all in one glorious screen (and more!).   With this tool you could verify the infinite loop in the assembly.   Enjoy my favorite presentation of his on his tool called <a href="http://vimeo.com/181925278">Understanding HotSpot JVM Performance with JITWatch</a>.</p>

<p>To really show how different assembly can be from the original, and as an excuse to play with JMH and JITWatch, suppose we have a simpler JMH test program with the following:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nd">@Param</span><span class="o">(</span><span class="s">"525600"</span><span class="o">)</span>
    <span class="kt">long</span> <span class="n">answer</span><span class="o">;</span>

    <span class="kd">static</span> <span class="kt">long</span> <span class="nf">howDoYouMeasure_MeasureAYear</span><span class="o">(</span>
            <span class="kt">int</span> <span class="n">daylights</span><span class="o">,</span> <span class="kt">int</span> <span class="n">sunsets</span><span class="o">,</span>
            <span class="kt">int</span> <span class="n">midnights</span><span class="o">,</span> <span class="kt">long</span> <span class="n">cupsOfCoffee</span><span class="o">,</span>
            <span class="kt">int</span> <span class="n">inches</span><span class="o">,</span> <span class="kt">int</span> <span class="n">miles</span><span class="o">,</span> <span class="kt">long</span> <span class="n">laughter</span><span class="o">,</span> <span class="kt">short</span> <span class="n">strife</span><span class="o">){</span>

        <span class="kt">int</span> <span class="n">minutes</span> <span class="o">=</span> <span class="mi">525_600</span><span class="o">;</span>  <span class="c1">//FYI 0x80520 in hex</span>
        <span class="kt">long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">daylights</span> <span class="o">+</span> <span class="n">sunsets</span> <span class="o">+</span> <span class="n">midnights</span> <span class="o">+</span> <span class="n">cupsOfCoffee</span><span class="o">;</span>
        <span class="n">result</span> <span class="o">+=</span> <span class="n">inches</span> <span class="o">+</span> <span class="n">miles</span> <span class="o">+</span> <span class="n">laughter</span> <span class="o">+</span> <span class="n">strife</span><span class="o">;</span>
        <span class="n">result</span> <span class="o">-=</span> <span class="o">(</span><span class="n">result</span> <span class="o">-</span> <span class="n">minutes</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Benchmark</span>
    <span class="nd">@CompilerControl</span><span class="o">(</span><span class="nc">CompilerControl</span><span class="o">.</span><span class="na">Mode</span><span class="o">.</span><span class="na">DONT_INLINE</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>
        <span class="kt">long</span> <span class="n">result</span> <span class="o">=</span> <span class="n">howDoYouMeasure_MeasureAYear</span><span class="o">(</span><span class="n">daylights</span><span class="o">,</span> <span class="n">sunsets</span><span class="o">,</span> <span class="n">midnights</span><span class="o">,</span> <span class="n">cupsOfCoffee</span><span class="o">,</span>
                <span class="n">inches</span><span class="o">,</span> <span class="n">miles</span><span class="o">,</span> <span class="n">laughter</span><span class="o">,</span> <span class="n">strife</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">result</span> <span class="o">==</span> <span class="n">answer</span><span class="o">;</span>
    <span class="o">}</span> 
</code></pre></div></div>
<p>This method always returns 525_600 regardless of input and convoluted logic.   The first time that the JIT compiles this to assembler, it will transform the method “as-is”.   In JITWatch, we see the following for the get() method, where the code is on the left, the bytecode for the get() method is in the middle, and the assembly on the right: 
<img src="/assets/images/2020-09-14-get-c1.png" alt="get_c1" /></p>

<p>Figure 2:  get() with bytecode and assembly.</p>

<p>I will zoom in so you can see the assembly better:  <br />
<img src="/assets/images/2020-09-14-get-c1-zoomed.png" alt="get_c1_zoomed" /></p>

<p>Figure 3:  C1 get() assembly zoomed</p>

<p>Note all the assembly for gathering member variable data in preparation for the call, as well as the “call” command itself to invoke the child method. In fact, this probably matches your expectations fairly well.   Even the variables are in the same order as the code.</p>

<p>The assembly version above is the output of the C1 JIT compiler, which does fast simple-stupid when possible.   The assembly for the called method, howDoYouMeasure_MeasureAYear(), is equally straightforward so I’ll skip showing it, but even the pointless additions and subtractions are included.</p>

<p>After a few seconds, however, the JIT realizes it has CPU cycles available to take another look.  The output of this advanced JIT processor (called the C2) is far more interesting.  Now JITWatch shows the following:
<img src="/assets/images/2020-09-14-get-c4.png" alt="get_c4" /></p>

<p>Figure 4:  get() with optimized assembly</p>

<p>Of course, the java code and the bytecode are still the same. But, the assembly is very different.  I will zoom in again:</p>

<p><img src="/assets/images/2020-09-14-get-c4-zoomed.png" alt="get_c4_zoomed" /></p>

<p>Figure 5:  get() with new optimized assembly zoomed</p>

<p>Now, if you stare long enough at the assembly, you’ll notice several things about this rewrite:</p>
<ol>
  <li>The JIT is no longer bothering to load member variables into memory in preparation for a call.</li>
  <li>The JIT is actually no longer even calling our child method howDoYouMeasure_MeasureAYear().  It copied the relevant assembly into get() itself.
    <ul>
      <li>This is an optimization called “inlining”, as called out in the popup.</li>
    </ul>
  </li>
  <li>The JIT has simply put the method result it always gets, 0x80520 (or 525_600 in hex), as a constant for get() to use.</li>
  <li>The JIT simply compares field “answer” and 525_600 in hex and returns 1 (true) if they match.</li>
  <li>If the JIT is ever wrong about field “answer” matching 525_600 in hex, it will abort the optimization and call the original method as its assumptions are no longer valid.
    <ul>
      <li>This is called an “uncommon trap”.</li>
    </ul>
  </li>
</ol>

<p>Given all the addition and subtraction operations in the original method, this is a significant rewrite of our code!   Here is a portion of the assembly of the original method, all of which has now been <em>removed</em> by the C2:</p>

<p><img src="/assets/images/2020-09-14-original-child-c1.png" alt="get_child_c1" /></p>

<p>Figure 6:  Original (now removed) assembly of “howDoYouMeasure_MeasureAYear()”</p>

<p>Again, if you try to debug the changes, you will simply step over the unchanged bytecode, not the assembly, and so will never be able to observe it.</p>

<p>If you are curious, a colleague of mine has indeed seen this in production.  It was, of course, only through research like the above that the issue was resolved.  “Volatile” is definitely on our checklist for spotting errors of omission.</p>

<h2 id="conclusion">Conclusion</h2>
<p>This post was about an infinite loop puzzle, but also much more.   As software engineers, we often search for magic incantations (code snippets) from online communities for our immediate problem at hand, enjoy introductory videos for the latest technologies or concepts, and certainly (hopefully) peruse online manuals for the same.   However, without studying the software engineering “giants”, you will only learn to solve the issues that <em>you know about</em>.   You will miss learning the solutions to all the unknown issues you have yet to encounter, often interweaving concepts you have yet to come across.  For me, at the time I was first studying Hawkins, this puzzle was just one such example, and Hawkins was just one of several “giants” introducing me to the vast amount I did not know.  Enjoy studying my giants above, or discover your own, and amaze yourself with how much farther you begin to see.</p>

<p>Special thanks to Aleksey Shipilev and Sebastian Neubauer for all the comments and suggestions.</p>

<p>Credit to song <a href="https://en.wikipedia.org/wiki/Seasons_of_Love">Seasons of Love</a> for inspiration for my method.</p>]]></content><author><name>Andrew Laird</name></author><category term="technology" /><summary type="html"><![CDATA[Java: Giants and Infinite Loops]]></summary></entry><entry><title type="html">Python calling C++</title><link href="https://tech.blueyonder.com/python-calling-c++/" rel="alternate" type="text/html" title="Python calling C++" /><published>2020-08-24T12:55:28+00:00</published><updated>2020-08-24T12:55:28+00:00</updated><id>https://tech.blueyonder.com/python-calling-c++</id><content type="html" xml:base="https://tech.blueyonder.com/python-calling-c++/"><![CDATA[<h1 id="python-calling-c">Python calling C++</h1>

<h2 id="what-is-the-motivation-for-accessing-c-code-from-python">What is the motivation for accessing C++ code from Python?</h2>

<p>Blue Yonder has few projects in which few segments of code are in Python and few in C++ and hence it makes for an important use case to find some methodology to make these two languages communicate with each other efficiently. It is extremely crucial to find a tool for Python’s seamless integration with the code written in C++.</p>

<p>Before diving deeper into how to access C++ code from Python, let us try and understand why or under what circumstances would we want to do that:</p>

<ol>
  <li>
    <p><strong>You already have a large, tested, stable library written in C++</strong> that you’d like to take advantage of in Python. This may be a communication library or a library to solve a specific purpose in the project.</p>
  </li>
  <li>
    <p><strong>You want to speed up a section of your Python code</strong> by converting a critical section to C++. Not only does C++ have faster execution speed, but it also allows you to break free from the limitations of the Python Global Interpreter Lock (GIL).</p>
  </li>
  <li>
    <p><strong>You want to use Python test tools</strong> to do large-scale testing of their systems.</p>
  </li>
</ol>

<p>One of the method to access C++ code from Python is to write a python interface and place python bindings on it in order to give python access, we can do that or use a pre-built tool such as Boost.Python library which is much easier to do.  But before going into details of this method let’s see what are the possible ways of combining Python and C++.</p>

<p><img src="/assets/images/2020-08-24-python-calling-c++.png" alt="python calling c++" /></p>

<h2 id="what-are-possible-ways-of-combining-python-and-c">What are possible ways of combining Python and C++?</h2>

<p>There are two basic models for combining C++ and Python:</p>

<ol>
  <li>
    <p><strong>Extending,</strong> in which the end-user launches the Python interpreter executable and imports Python extension modules written in C++. It’s like taking a library written in C++ and giving it a Python interface so Python programmers can use it. From Python, these modules look just like regular Python modules. Extending is writing a shared library that the Python interpreter can load as part of an import statement.</p>
  </li>
  <li>
    <p><strong>Embedding,</strong> in which the end-user launches a program written in C++ that in turn invokes the Python interpreter as a library subroutine. It’s like adding scriptability to an existing application. Embedding is inserting calls into your C or C++ application after it has started up in order to initialize the Python interpreter and call back to Python code at specific times.</p>
  </li>
</ol>

<p>Note that even when embedding Python in another program, extension modules are often the best way to make C/C++ functionality accessible to Python code, so the use of extension modules is really at the heart of both models. Embedding takes more work than extending. Extending gives you more power and flexibility than embedding. Many useful python tools and automation techniques are much harder, if not impossible, to use if you’re embedding.</p>

<p>Boost.Python library discussed above is used to quickly and easily export C++ to Python such that the Python interface is very similar to the C++ interface. Due to its advantage of being fast and convenient to be able to extend C++ libraries to Python we’ll be learning about it in this blog and how we can use it to make Python and C++ talk to each other. But before learning about it lets first find out what is Python Binding and why is it required.</p>

<h2 id="what-is-python-binding-and-why-is-it-required">What is Python Binding and why is it required?</h2>

<p>Binding generally refers to a mapping of one thing to another. In the context of software libraries, bindings are wrapper libraries that bridge two programming languages, so that a library written for one language can be used in another language. Many software libraries are written in system programming languages such as C or C++. To use such libraries from another language, usually of higher-level, such as Java, Common Lisp, Scheme, Python, or Lua, a binding to the library must be created in that language, possibly requiring recompiling the language’s code, depending on the amount of modification needed. Python bindings are used when an extant C or C++ library written for some purpose is to be used from Python.</p>

<p>To understand why Python bindings are required let’s take a look at how Python and C++ store data and what types of issues this will cause. C or C++ stores data in the most compact form in memory possible. If you use an uint8_t, then the space required to store it would be 8 bits if we don’t take structure padding into account.</p>

<p>In Python, on the other hand, everything is an object and the memory is heap allotted, integers in Python are big integers and their size may vary according to the value stored in them. This means that our Python bindings will need to convert a C/C++ integer to a Python integer for each integer passed across the boundary.</p>

<p>The process of transforming the memory representation of an object to a data format suitable for storage or transmission is called marshalling and Python bindings are doing a process similar to marshalling when they prepare data to move it from Python to C or vice versa.</p>

<h2 id="what-is-boostpython-library">What is Boost.Python library?</h2>

<p>The Boost.Python Library is a open source framework for interfacing Python and C++. It allows you to quickly and seamlessly expose C++ classes functions and objects to Python, and vice-versa, using no special tools, just your C++ compiler. It is designed to wrap C++ interfaces non-intrusively, so that you should not have to change the C++ code at all in order to wrap it, making Boost.Python ideal for exposing 3rd-party libraries to Python. The library’s use of advanced metaprogramming techniques simplifies its syntax for users, so that wrapping code takes on the look of a kind of declarative interface definition language (IDL).</p>

<p>The Boost.Python Library binds C++ and Python in a mostly seamless fashion. It is just one member of the boost C++ library collection at <a href="http://www.boost.org">http://www.boost.org</a>. Boost.Python bindings are written in pure C++, using no tools other than your editor and your C++ compiler.</p>

<h2 id="relationship-to-the-python-c-api">Relationship to the Python C API</h2>

<p>Python (more specific: CPython, the reference implementation of Python written in C) already provides an API for gluing together Python and C in the form of Python C API. Boost.Python is a wrapper for the Python/C API.</p>

<p>Using the Python/C API, you must deal with passing pointers back and forth between Python and C and worry about pointers hanging out in one place when the object they point to has been thrown away. Boost.Python takes care of much of this for you. In addition, Boost.Python lets you write operations on Python objects in C++ in OOP style.</p>

<h2 id="simple-example">Simple Example</h2>

<p>So now that we are done with the theoretical aspects of it, let’s get our hands dirty with some coding and see how it can be used through a short example.</p>

<p>To try it out yourself, check out this <a href="https://github.com/JDASoftwareGroup/boost.python-examples">repository</a> with basic build and execute instructions.</p>

<p>Before we get to the actual coding part let’s see what the prerequisites are to run a program with a boost library:</p>

<ol>
  <li>
    <p><a href="https://www.boost.org/">Boost</a> (version &gt;= 1.3.2)</p>
  </li>
  <li>
    <p><a href="http://www.python.org/">Python</a> (version &gt;= 2.2)</p>
  </li>
  <li>
    <p>A C++ compiler for your platform, e.g. <a href="https://gcc.gnu.org/">GCC</a> or <a href="http://www.mingw.org/">MinGW</a></p>
  </li>
</ol>

<p>Suppose we have the following C++ API which we want to expose in Python:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;string&gt;</span><span class="c1"> </span><span class="cp">
</span>
<span class="k">namespace</span> 
<span class="p">{</span>   
    <span class="c1">// Avoid cluttering the global namespace. </span>

    <span class="c1">// A couple of simple C++ functions that we want to expose to Python. </span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">greet</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="s">"hello, world"</span><span class="p">;</span> <span class="p">}</span> 
    <span class="kt">int</span> <span class="n">square</span><span class="p">(</span><span class="kt">int</span> <span class="n">number</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">number</span> <span class="o">*</span> <span class="n">number</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span> 
</code></pre></div></div>

<p>Here is the C++ code for a python module called getting_started1 which exposes the API.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;boost/python.hpp&gt;</span><span class="c1"> </span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">boost</span><span class="o">::</span><span class="n">python</span><span class="p">;</span> 
 
<span class="n">BOOST_PYTHON_MODULE</span><span class="p">(</span><span class="n">getting_started1</span><span class="p">)</span> 
<span class="p">{</span> 

    <span class="c1">// Add regular functions to the module. </span>
    <span class="n">def</span><span class="p">(</span><span class="s">"greet"</span><span class="p">,</span> <span class="n">greet</span><span class="p">);</span> 
    <span class="n">def</span><span class="p">(</span><span class="s">"square"</span><span class="p">,</span> <span class="n">square</span><span class="p">);</span> 
<span class="p">}</span> 
</code></pre></div></div>
<p>That’s it! If we build this shared library and put it on our PYTHONPATH we can now access our C++ functions from Python.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">getting_started1</span> 
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span> <span class="n">getting_started1</span><span class="p">.</span><span class="n">greet</span><span class="p">()</span>
 
<span class="n">hello</span><span class="p">,</span> <span class="n">world</span> 

<span class="o">&gt;&gt;&gt;</span> <span class="n">number</span> <span class="o">=</span> <span class="mi">11</span> 
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span> <span class="n">number</span><span class="p">,</span> <span class="s">'*'</span><span class="p">,</span> <span class="n">number</span><span class="p">,</span> <span class="s">'='</span><span class="p">,</span> <span class="n">getting_started1</span><span class="p">.</span><span class="n">square</span><span class="p">(</span><span class="n">number</span><span class="p">)</span> 

<span class="mi">11</span> <span class="o">*</span> <span class="mi">11</span> <span class="o">=</span> <span class="mi">121</span> 
</code></pre></div></div>

<p>So, as you can see from the example above the only additional library required to run our program is the Boost library apart from the regular C++ and Python compiler and all you need to do is compile and build the C++ library and import it in Python as a regular Python library and boom you’re ready to go.</p>

<h2 id="exposing-structclasses-written-in-c-to-python">Exposing struct/classes written in C++ to Python</h2>

<p>Now that we have explored running a simple Hello World program in Python which is written in C++, let’s see how we can do the same with C++ struct/classes through an example.</p>

<p>Assume we want to expose the below written C++ struct/class to Python.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">World</span>  
<span class="p">{</span> 
    <span class="kt">void</span> <span class="n">set</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">msg</span><span class="p">)</span> <span class="p">{</span> <span class="n">mMsg</span> <span class="o">=</span> <span class="n">msg</span><span class="p">;</span> <span class="p">}</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">greet</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">mMsg</span><span class="p">;</span> <span class="p">}</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">mMsg</span><span class="p">;</span>
<span class="p">};</span> 
 
<span class="k">using</span> <span class="k">namespace</span> <span class="n">boost</span><span class="o">::</span><span class="n">python</span><span class="p">;</span> 
 
<span class="n">BOOST_PYTHON_MODULE</span><span class="p">(</span><span class="n">classes</span><span class="p">)</span> 
        <span class="p">{</span> 
                <span class="n">class_</span><span class="o">&lt;</span><span class="n">World</span><span class="o">&gt;</span><span class="p">(</span><span class="s">"World"</span><span class="p">)</span> 
                        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"greet"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">World</span><span class="o">::</span><span class="n">greet</span><span class="p">)</span> 
                        <span class="p">.</span><span class="n">def</span><span class="p">(</span><span class="s">"set"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">World</span><span class="o">::</span><span class="n">set</span><span class="p">)</span> 
                <span class="p">;</span> 
        <span class="p">};</span> 
</code></pre></div></div>
<p>The corresponding Python code to call the above C++ class from Python would be:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">classes</span> 
 
<span class="n">t</span> <span class="o">=</span> <span class="n">classes</span><span class="p">.</span><span class="n">World</span><span class="p">()</span> 
<span class="n">t</span><span class="p">.</span><span class="nb">set</span><span class="p">(</span><span class="s">"Python says hi to C++."</span><span class="p">)</span> 
<span class="k">print</span> <span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">greet</span><span class="p">())</span>
</code></pre></div></div>
<p>The output of running the above code would be:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="n">Classes</span><span class="p">.</span><span class="n">py</span> 

<span class="n">Python</span> <span class="n">says</span> <span class="n">hi</span> <span class="n">to</span> <span class="n">C</span><span class="o">++</span><span class="p">.</span> 

<span class="n">Process</span> <span class="n">finished</span> <span class="k">with</span> <span class="nb">exit</span> <span class="n">code</span> <span class="mi">0</span> 
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>This is just the tip of the iceberg and there could be several use cases for making the two languages talk, especially while taking the edge of data science in the existing projects or while using the best features of both the languages. Although Boost.Python is a great tool for a quick start up, it has a few drawbacks like packaging projects where native dependencies could be challenging. Also, since it’s a python run-time dependency resolution it might be difficult to use all the C++ features, especially templates. But, having said that, it’s a great tool for getting started and running at least the basic C++ code as python libraries. With Boost.Python you can open up possibilities to a whole new world.</p>]]></content><author><name>Aditya Gupta</name></author><category term="python" /><category term="c++" /><category term="technology" /><summary type="html"><![CDATA[Python calling C++]]></summary></entry><entry><title type="html">Introducing Cube Functionality To Kartothek</title><link href="https://tech.blueyonder.com/cube-functionality-to-kartothek/" rel="alternate" type="text/html" title="Introducing Cube Functionality To Kartothek" /><published>2020-08-11T04:30:00+00:00</published><updated>2020-08-11T04:30:00+00:00</updated><id>https://tech.blueyonder.com/cube-functionality-to-kartothek</id><content type="html" xml:base="https://tech.blueyonder.com/cube-functionality-to-kartothek/"><![CDATA[<h1 id="introducing-cube-functionality-to-kartothek">Introducing Cube Functionality To Kartothek</h1>

<p>Last year, we introduced <a href="../introducing-kartothek/">Kartothek</a>, a table management python library powered by Dask. 
Our journey continued by adding another gem to our open source treasure: We empowered Kartothek with multi-dataset functionality.</p>

<p>You might think: “Kartothek already provides dataset features, do I really need multiple dataset interfaces?” Hang on,
 we will present you the story of <strong>Kartothek cubes</strong>, an interface that supports multiple datasets. 
Imagine a typical machine learning workflow, which might look like this:</p>
<ul>
  <li>First, we get some input data, or source data. In the context of Kartothek cubes, we will refer to the source data as seed data or seed dataset.</li>
  <li>On this seed dataset, we might want to train a model that generates predictions.</li>
  <li>Based on these predicitons, we might want to generate reports and calculate KPIs.</li>
  <li>Last, but not least, we might want to create some dashboards showing plots of the aggregated KPIs as well as the underlying input data.
What we need for this workflow is not a table-like view on our data, but a single (virtual) view on everything that we generated in these different steps.</li>
</ul>

<p>Kartothek Cubes deals with multiple Kartothek datasets, loosely modeled after <a href="https://en.wikipedia.org/wiki/Data_cube">Data Cubes</a>.
Cubes offer an interface to query all of the data without performing complex join operations manually each time. 
Because kartothek offers a view on our cube similar to large virtual pandas DataFrame, querying the whole dataset is very comfortable.</p>

<h2 id="how-to-use-cubes">How to use Cubes?</h2>

<p>Let us start with building the cube for <strong>geodata</strong>. Similar to Kartothek, 
we need a <a href="https://simplekv.readthedocs.io/">simplekv</a>-based store backend along with an abstract cube definition.
<strong>df_weather</strong> is a pandas dataframe created from reading a csv file:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">StringIO</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">df_weather</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">filepath_or_buffer</span><span class="o">=</span><span class="n">StringIO</span><span class="p">(</span><span class="s">"""
... avg_temp     city country        day
...        6  Hamburg      DE 2020-07-01
...        5  Hamburg      DE 2020-07-02
...        8  Dresden      DE 2020-07-01
...        4  Dresden      DE 2020-07-02
...        6   London      UK 2020-07-01
...        8   London      UK 2020-07-02
...     """</span><span class="p">.</span><span class="n">strip</span><span class="p">()),</span>
<span class="p">...</span>     <span class="n">delim_whitespace</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">parse_dates</span><span class="o">=</span><span class="p">[</span><span class="s">"day"</span><span class="p">],</span>
<span class="p">...</span> <span class="p">)</span>
</code></pre></div></div>

<p>Now, we want to store this dataframe using the cube interface.
To achieve this, we have to specify the cube object first by providing some meta-information about our data.
The <code class="language-plaintext highlighter-rouge">uuid_prefix</code> serves as identifier for our dataset. The <code class="language-plaintext highlighter-rouge">dimension_columns</code> are the dataset’s primary keys, so all rows within this datset have to be unique with respect to the <code class="language-plaintext highlighter-rouge">dimension_columns</code>.
The <code class="language-plaintext highlighter-rouge">partition_columns</code> specify the columns which are used to physically partition the dataset.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span><span class="c1">#we are creating a geodata cube instance
</span><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.core.cube.cube</span> <span class="kn">import</span> <span class="n">Cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">cube</span> <span class="o">=</span> <span class="n">Cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">uuid_prefix</span><span class="o">=</span><span class="s">"geodata"</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">dimension_columns</span><span class="o">=</span><span class="p">[</span><span class="s">"city"</span><span class="p">,</span> <span class="s">"day"</span><span class="p">],</span>
<span class="p">...</span>     <span class="n">partition_columns</span><span class="o">=</span><span class="p">[</span><span class="s">"country"</span><span class="p">]</span>
<span class="p">...)</span>
</code></pre></div></div>

<p>We use the simple <strong>kartothek.io.eager_cube</strong> backend to store the data:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">build_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_build</span> <span class="o">=</span> <span class="n">build_cube</span><span class="p">(</span>
<span class="p">...</span>   <span class="n">data</span><span class="o">=</span><span class="n">df_weather</span><span class="p">,</span>
<span class="p">...</span>   <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>   <span class="n">cube</span><span class="o">=</span><span class="n">cube</span>
<span class="p">...)</span>
</code></pre></div></div>

<p>where <strong>store</strong> is the <strong>simplekv</strong> store of storefactory. (For more details, please refer our <a href="../introducing-kartothek/">Kartothek</a> post.)</p>

<p>We have just preserved a single Kartothek dataset. Let’s print the content of seed dataset:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="s">", "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">datasets_build</span><span class="p">.</span><span class="n">keys</span><span class="p">())))</span>
<span class="n">seed</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">ds_seed</span> <span class="o">=</span> <span class="n">datasets_build</span><span class="p">[</span><span class="s">"seed"</span><span class="p">].</span><span class="n">load_all_indices</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="n">ds_seed</span><span class="p">.</span><span class="n">uuid</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="s">", "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">ds_seed</span><span class="p">.</span><span class="n">indices</span><span class="p">)))</span>
<span class="n">city</span><span class="p">,</span> <span class="n">country</span><span class="p">,</span> <span class="n">day</span>
</code></pre></div></div>

<p>Now we have a quick look at the store content. Note that we cut out UUIDs and timestamps here for brevity:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">re</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">def</span> <span class="nf">print_filetree</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s">""</span><span class="p">):</span>
<span class="p">...</span>     <span class="n">entries</span> <span class="o">=</span> <span class="p">[]</span>
<span class="p">...</span>     <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">keys</span><span class="p">(</span><span class="n">prefix</span><span class="p">)):</span>
<span class="p">...</span>         <span class="n">k</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">"[a-z0-9]{32}"</span><span class="p">,</span> <span class="s">"&lt;uuid&gt;"</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
<span class="p">...</span>         <span class="n">k</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">"[0-9]{4}-[0-9]{2}-[0-9]{2}((%20)|(T))[0-9]{2}%3A[0-9]{2}%3A[0-9]+.[0-9]{6}"</span><span class="p">,</span> <span class="s">"&lt;ts&gt;"</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
<span class="p">...</span>         <span class="n">entries</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
<span class="p">...</span>     <span class="k">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">entries</span><span class="p">)))</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">print_filetree</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">metadata</span><span class="p">.</span><span class="n">json</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">city</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">day</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">_common_metadata</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
</code></pre></div></div>

<p>We can <strong>extend</strong> this cube by adding new columns to the dataframes.</p>

<h2 id="extend-operation">Extend Operation</h2>

<p>Now let’s say we also would like to have longitude and latitude data in our cube.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">extend_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">df_location</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">filepath_or_buffer</span><span class="o">=</span><span class="n">StringIO</span><span class="p">(</span><span class="s">"""
...    city country  latitude  longitude
... Hamburg      DE 53.551086   9.993682
... Dresden      DE 51.050407  13.737262
...  London      UK 51.509865  -0.118092
...   Tokyo      JP 35.652832 139.839478
...     """</span><span class="p">.</span><span class="n">strip</span><span class="p">()),</span>
<span class="p">...</span>     <span class="n">delim_whitespace</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_extend</span> <span class="o">=</span> <span class="n">extend_cube</span><span class="p">(</span>
<span class="p">...</span>   <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s">"latlong"</span><span class="p">:</span> <span class="n">df_location</span><span class="p">},</span>
<span class="p">...</span>   <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>   <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)</span>
</code></pre></div></div>

<p>This results in an extra dataset:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="s">", "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">datasets_extend</span><span class="p">.</span><span class="n">keys</span><span class="p">())))</span>
<span class="n">latlong</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">ds_latlong</span> <span class="o">=</span> <span class="n">datasets_extend</span><span class="p">[</span><span class="s">"latlong"</span><span class="p">].</span><span class="n">load_all_indices</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="n">ds_latlong</span><span class="p">.</span><span class="n">uuid</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">print</span><span class="p">(</span><span class="s">", "</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">ds_latlong</span><span class="p">.</span><span class="n">indices</span><span class="p">)))</span>
<span class="n">country</span>
</code></pre></div></div>
<p>Note that for the second dataset, no indices for <strong>city</strong> and <strong>day</strong> exist. 
These are only created for the seed dataset, since that dataset forms the groundtruth about which <code class="language-plaintext highlighter-rouge">city-day</code> entries are part of the cube.</p>

<p>If you look at the file tree, you can see that the second dataset is completely separated. This is useful to copy/backup parts of the cube.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">print_filetree</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">metadata</span><span class="p">.</span><span class="n">json</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">_common_metadata</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">JP</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">latlong</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">metadata</span><span class="p">.</span><span class="n">json</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">city</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">day</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">_common_metadata</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
</code></pre></div></div>
<h2 id="querying-cubes">Querying Cubes</h2>

<p>The seed dataset presents the groundtruth regarding rows, all other datasets are joined via a left join.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">query_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">dfs</span> <span class="o">=</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">partition_by</span><span class="o">=</span><span class="s">"country"</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">dfs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>     <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>   <span class="n">latitude</span>  <span class="n">longitude</span>
<span class="mi">0</span>         <span class="mi">8</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">1</span>         <span class="mi">4</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">2</span>         <span class="mi">6</span>  <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">3</span>         <span class="mi">5</span>  <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">dfs</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
   <span class="n">avg_temp</span>    <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>   <span class="n">latitude</span>  <span class="n">longitude</span>
<span class="mi">0</span>         <span class="mi">6</span>  <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.509865</span>  <span class="o">-</span><span class="mf">0.118092</span>
<span class="mi">1</span>         <span class="mi">8</span>  <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.509865</span>  <span class="o">-</span><span class="mf">0.118092</span>
</code></pre></div></div>

<p>The query system also supports selection and projection:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.core.cube.conditions</span> <span class="kn">import</span> <span class="n">C</span>
<span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">query_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">payload_columns</span><span class="o">=</span><span class="p">[</span><span class="s">"avg_temp"</span><span class="p">],</span>
<span class="p">...</span>     <span class="n">conditions</span><span class="o">=</span><span class="p">(</span>
<span class="p">...</span>         <span class="p">(</span><span class="n">C</span><span class="p">(</span><span class="s">"country"</span><span class="p">)</span> <span class="o">==</span> <span class="s">"DE"</span><span class="p">)</span> <span class="o">&amp;</span>
<span class="p">...</span>         <span class="n">C</span><span class="p">(</span><span class="s">"latitude"</span><span class="p">).</span><span class="n">in_interval</span><span class="p">(</span><span class="mf">50.</span><span class="p">,</span> <span class="mf">52.</span><span class="p">)</span> <span class="o">&amp;</span>
<span class="p">...</span>         <span class="n">C</span><span class="p">(</span><span class="s">"longitude"</span><span class="p">).</span><span class="n">in_interval</span><span class="p">(</span><span class="mf">13.</span><span class="p">,</span> <span class="mf">14.</span><span class="p">)</span>
<span class="p">...</span>     <span class="p">),</span>
<span class="p">...</span> <span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>     <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>
<span class="mi">0</span>         <span class="mi">8</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>
<span class="mi">1</span>         <span class="mi">4</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>
</code></pre></div></div>

<h2 id="transform">Transform</h2>

<p>Query and extend operations can be combined to build powerful transformation pipelines. To better illustrate this we will use <strong>dask.bag_cube</strong> for the example.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.dask.bag_cube</span> <span class="kn">import</span> <span class="p">(</span>
<span class="p">...</span>     <span class="n">extend_cube_from_bag</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">query_cube_bag</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="n">df</span><span class="p">):</span>
<span class="p">...</span>     <span class="n">df</span><span class="p">[</span><span class="s">"avg_temp_country_min"</span><span class="p">]</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="s">"avg_temp"</span><span class="p">].</span><span class="nb">min</span><span class="p">()</span>
<span class="p">...</span>     <span class="k">return</span> <span class="p">{</span>
<span class="p">...</span>         <span class="s">"transformed"</span><span class="p">:</span> <span class="n">df</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span>
<span class="p">...</span>             <span class="p">:,</span>
<span class="p">...</span>             <span class="p">[</span>
<span class="p">...</span>                 <span class="s">"avg_temp_country_min"</span><span class="p">,</span>
<span class="p">...</span>                 <span class="s">"city"</span><span class="p">,</span>
<span class="p">...</span>                 <span class="s">"country"</span><span class="p">,</span>
<span class="p">...</span>                 <span class="s">"day"</span><span class="p">,</span>
<span class="p">...</span>             <span class="p">]</span>
<span class="p">...</span>         <span class="p">],</span>
<span class="p">...</span>     <span class="p">}</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">transformed</span> <span class="o">=</span> <span class="n">query_cube_bag</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store_factory</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">partition_by</span><span class="o">=</span><span class="s">"day"</span><span class="p">,</span>
<span class="p">...</span> <span class="p">).</span><span class="nb">map</span><span class="p">(</span><span class="n">transform</span><span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_transformed</span> <span class="o">=</span> <span class="n">extend_cube_from_bag</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">data</span><span class="o">=</span><span class="n">transformed</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store_factory</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">ktk_cube_dataset_ids</span><span class="o">=</span><span class="p">[</span><span class="s">"transformed"</span><span class="p">],</span>
<span class="p">...</span> <span class="p">).</span><span class="n">compute</span><span class="p">()</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">payload_columns</span><span class="o">=</span><span class="p">[</span>
<span class="p">...</span>         <span class="s">"avg_temp"</span><span class="p">,</span>
<span class="p">...</span>         <span class="s">"avg_temp_country_min"</span><span class="p">,</span>
<span class="p">...</span>     <span class="p">],</span>
<span class="p">...</span> <span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>  <span class="n">avg_temp_country_min</span>     <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>
<span class="mi">0</span>         <span class="mi">8</span>                     <span class="mi">6</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>
<span class="mi">1</span>         <span class="mi">4</span>                     <span class="mi">4</span>  <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>
<span class="mi">2</span>         <span class="mi">6</span>                     <span class="mi">6</span>  <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>
<span class="mi">3</span>         <span class="mi">5</span>                     <span class="mi">4</span>  <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>
<span class="mi">4</span>         <span class="mi">6</span>                     <span class="mi">6</span>   <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>
<span class="mi">5</span>         <span class="mi">8</span>                     <span class="mi">4</span>   <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>
</code></pre></div></div>
<p>Notice that the <code class="language-plaintext highlighter-rouge">partition_by</code> argument does not have to match the cube <code class="language-plaintext highlighter-rouge">partition_columns</code> to work. 
You may use any indexed column. Keep in mind that fine-grained partitioning can have drawbacks though, 
namely large scheduling overhead and many blob files which can make reading the data inefficient.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">print_filetree</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="s">"geodata++transformed"</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">metadata</span><span class="p">.</span><span class="n">json</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">_common_metadata</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">transformed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
</code></pre></div></div>

<h2 id="append">Append</h2>
<p>New rows can be added to the cube using an append operation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">append_to_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">df_weather2</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">read_csv</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">filepath_or_buffer</span><span class="o">=</span><span class="n">StringIO</span><span class="p">(</span><span class="s">"""
... avg_temp     city country        day
...       20 Santiago      CL 2020-07-01
...       22 Santiago      CL 2020-07-02
...     """</span><span class="p">.</span><span class="n">strip</span><span class="p">()),</span>
<span class="p">...</span>     <span class="n">delim_whitespace</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">parse_dates</span><span class="o">=</span><span class="p">[</span><span class="s">"day"</span><span class="p">],</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_appended</span> <span class="o">=</span> <span class="n">append_to_cube</span><span class="p">(</span>
<span class="p">...</span>   <span class="n">data</span><span class="o">=</span><span class="n">df_weather2</span><span class="p">,</span>
<span class="p">...</span>   <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>   <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">print_filetree</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="s">"geodata++seed"</span><span class="p">)</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">metadata</span><span class="p">.</span><span class="n">json</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">city</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">city</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">day</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">indices</span><span class="o">/</span><span class="n">day</span><span class="o">/&lt;</span><span class="n">ts</span><span class="o">&gt;</span><span class="p">.</span><span class="n">by</span><span class="o">-</span><span class="n">dataset</span><span class="o">-</span><span class="n">index</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">_common_metadata</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">CL</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">DE</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
<span class="n">geodata</span><span class="o">++</span><span class="n">seed</span><span class="o">/</span><span class="n">table</span><span class="o">/</span><span class="n">country</span><span class="o">=</span><span class="n">UK</span><span class="o">/&lt;</span><span class="n">uuid</span><span class="o">&gt;</span><span class="p">.</span><span class="n">parquet</span>
</code></pre></div></div>
<p>Indices are updated automatically. If we query the data now, we can see that only the seed dataset got updated but the additional columns are missing:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>  <span class="n">avg_temp_country_min</span>      <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>   <span class="n">latitude</span>  <span class="n">longitude</span>
<span class="mi">0</span>         <span class="mi">8</span>                   <span class="mf">6.0</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">1</span>         <span class="mi">4</span>                   <span class="mf">4.0</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">2</span>         <span class="mi">6</span>                   <span class="mf">6.0</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">3</span>         <span class="mi">5</span>                   <span class="mf">4.0</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">4</span>         <span class="mi">6</span>                   <span class="mf">6.0</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.509865</span>  <span class="o">-</span><span class="mf">0.118092</span>
<span class="mi">5</span>         <span class="mi">8</span>                   <span class="mf">4.0</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.509865</span>  <span class="o">-</span><span class="mf">0.118092</span>
<span class="mi">6</span>        <span class="mi">20</span>                   <span class="n">NaN</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">7</span>        <span class="mi">22</span>                   <span class="n">NaN</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
 
</code></pre></div></div>

<h2 id="remove-and-delete-operations">Remove and Delete Operations</h2>

<p>You can <strong>remove</strong> entire partitions from the cube using the remove operation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">remove_partitions</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_after_removal</span> <span class="o">=</span> <span class="n">remove_partitions</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">ktk_cube_dataset_ids</span><span class="o">=</span><span class="p">[</span><span class="s">"latlong"</span><span class="p">],</span>
<span class="p">...</span>     <span class="n">conditions</span><span class="o">=</span><span class="p">(</span><span class="n">C</span><span class="p">(</span><span class="s">"country"</span><span class="p">)</span> <span class="o">==</span> <span class="s">"UK"</span><span class="p">),</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>  <span class="n">avg_temp_country_min</span>      <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>   <span class="n">latitude</span>  <span class="n">longitude</span>
<span class="mi">0</span>         <span class="mi">8</span>                   <span class="mf">6.0</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">1</span>         <span class="mi">4</span>                   <span class="mf">4.0</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">2</span>         <span class="mi">6</span>                   <span class="mf">6.0</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">3</span>         <span class="mi">5</span>                   <span class="mf">4.0</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">4</span>         <span class="mi">6</span>                   <span class="mf">6.0</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">5</span>         <span class="mi">8</span>                   <span class="mf">4.0</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">6</span>        <span class="mi">20</span>                   <span class="n">NaN</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">7</span>        <span class="mi">22</span>                   <span class="n">NaN</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>        <span class="n">NaN</span>        <span class="n">NaN</span> 
</code></pre></div></div>

<p>You can also <strong>delete</strong> entire datasets (or the entire cube).</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">from</span> <span class="nn">kartothek.io.eager_cube</span> <span class="kn">import</span> <span class="n">delete_cube</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">datasets_still_in_cube</span> <span class="o">=</span> <span class="n">delete_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">datasets</span><span class="o">=</span><span class="p">[</span><span class="s">"transformed"</span><span class="p">],</span>
<span class="p">...</span> <span class="p">)</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">query_cube</span><span class="p">(</span>
<span class="p">...</span>     <span class="n">cube</span><span class="o">=</span><span class="n">cube</span><span class="p">,</span>
<span class="p">...</span>     <span class="n">store</span><span class="o">=</span><span class="n">store</span><span class="p">,</span>
<span class="p">...</span> <span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
   <span class="n">avg_temp</span>      <span class="n">city</span> <span class="n">country</span>        <span class="n">day</span>   <span class="n">latitude</span>  <span class="n">longitude</span>
<span class="mi">0</span>         <span class="mi">8</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">1</span>         <span class="mi">4</span>   <span class="n">Dresden</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">51.050407</span>  <span class="mf">13.737262</span>
<span class="mi">2</span>         <span class="mi">6</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">3</span>         <span class="mi">5</span>   <span class="n">Hamburg</span>      <span class="n">DE</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>  <span class="mf">53.551086</span>   <span class="mf">9.993682</span>
<span class="mi">4</span>         <span class="mi">6</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">5</span>         <span class="mi">8</span>    <span class="n">London</span>      <span class="n">UK</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">6</span>        <span class="mi">20</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">01</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
<span class="mi">7</span>        <span class="mi">22</span>  <span class="n">Santiago</span>      <span class="n">CL</span> <span class="mi">2020</span><span class="o">-</span><span class="mi">07</span><span class="o">-</span><span class="mi">02</span>        <span class="n">NaN</span>        <span class="n">NaN</span>
</code></pre></div></div>

<h2 id="cube-features-in-kartothek">Cube Features in Kartothek</h2>

<ul>
  <li>
    <p><strong>Multiple-datasets</strong>: When mapping multiple parts (tables or datasets) to Kartothek, using multiple datasets allow users to copy, backup and delete them separately.
 Index structures are bound to datasets. This was not possible with the existing multi-table (within a single dataset) feature present in kartothek.
 We intend to phase out the multi-table single dataset functionality soon.</p>
  </li>
  <li>
    <p><strong>Seed-Based Join System / Partition-alignment</strong>: When data is stored in multiple parts (tables or datasets), the question is how to expose it to the user during read operations.
 Seed-based join marks a single part as seed which provides seed dataset in the cube, all other parts are just additional columns.
 Cubes use a lazy approach for joins, since it better supports independent copies and backups of datasets and also simplifies some of our processing pipelines
 (e.g. geolocation data can blindly be fetched for too many locations and dates.)</p>
  </li>
</ul>

<h2 id="outlook">Outlook</h2>
<p>In the upcoming months we will continue to expand the Kartothek functionality. Here are a few highlights of what’s next:</p>

<ul>
  <li><strong>API cleanup:</strong> The API surface of kartothek grew organically over the years and we plan to re-design it. 
While doing so, we will incorporate our learnings regarding API design and will also prune some features that are not needed anymore or that did not match their expectations (e.g. the original multi-table design).</li>
  <li><strong>Ecosystem integration:</strong> At this point in time, there are multiple dataset formats (e.g. <a href="https://arrow.apache.org/docs/python/dataset.html">Apache Arrow</a>, 
<a href="https://iceberg.apache.org/">Apache Iceberg</a>, <a href="https://delta.io/">Delta Lake</a>) and we will investigate how to evolve kartothek as a library and as a format to align better with the ecosystem and enable new features (like schema migrations and time travel), 
while providing the stability and safety that our users demand.</li>
  <li><strong>Query Planning:</strong> Currently the kartothek query planner solely relies on file-level information (via file names for primary indices and separate index files for secondary indices). 
It would be great to also use the RowGroup-level statistics as specified by <a href="https://github.com/apache/parquet-format">Apache Parquet</a> to improve query performance.
We will have a look at <a href="https://github.com/dask/dask/blob/55445565c3746f97f2bc50d5628a484576cef90e/dask/dataframe/io/parquet/core.py">the work Dask already did</a> in this area.</li>
</ul>]]></content><author><name>Usha Nemani &amp; Marco Neumann</name></author><category term="technology" /><category term="python" /><category term="data-engineering" /><summary type="html"><![CDATA[Introducing Cube Functionality To Kartothek]]></summary></entry><entry><title type="html">Dask Usage at Blue Yonder</title><link href="https://tech.blueyonder.com/dask-usage-at-blue-yonder/" rel="alternate" type="text/html" title="Dask Usage at Blue Yonder" /><published>2020-06-19T09:00:00+00:00</published><updated>2020-06-19T09:00:00+00:00</updated><id>https://tech.blueyonder.com/dask-usage-at-blue-yonder</id><content type="html" xml:base="https://tech.blueyonder.com/dask-usage-at-blue-yonder/"><![CDATA[<h1 id="dask-usage-at-blue-yonder">Dask Usage at Blue Yonder</h1>

<p>Back in 2017, Blue Yonder started to look into 
<a href="https://distributed.dask.org">Dask/Distributed</a> and how we can leverage it to power
our machine learning and data pipelines.
It’s 2020 now, and we are using it heavily in production for performing machine learning based
forecasting and optimization for our customers.
Over time, we discovered that the way we are using Dask differs from how it is typically
used in the community. 
(Teaser: For instance, we are running about 500 Dask clusters in total and dynamically scale up and down 
the number of workers.)
So we think it’s time to take a a look at the way we are using Dask!</p>

<h2 id="use-case">Use case</h2>

<p>First, let’s have a look at the main use case we have for Dask. The use of Dask at Blue Yonder
is strongly coupled to the concept of <a href="https://tech.jda.com/introducing-kartothek/">datasets</a>,
tabular data stored as <a href="https://parquet.apache.org/">Apache Parquet</a> files in blob storage. 
We use datasets for storing the input data for our computations as well as intermediate results and the final output data
served again to our customers.
Dask is used in managing/creating this data as well as performing the necessary computations.</p>

<p>Our pipeline consists of downloading data from a relational database into a dataset
(we use Dask here for parallelization of the download) and then of several steps that each read
the data from an input dataset, do a computations on it, and write it out to another dataset.</p>

<p>In many cases, the layout of the source dataset (the partitioning, i.e., what data resides in which blob)
is used for parallelization. 
This means the algorithms work independently on the individual blobs of the source dataset. 
Therefore, our respective Dask graphs are embarassingly parallel.
The individual nodes perfom
the sequential operations of reading in the data from a source dataset blob, doing some computation on it,
and writing it out to a target dataset blob. 
Again, there is a final reduction node writing the target
dataset metadata. 
We typically use Dask’s Delayed interface for these computations.</p>

<p><img src="/assets/images/2020-03-16-dask-usage-at-by-graph.png" alt="Simple Dask graph with parallel nodes and final reduction" /></p>

<p>In between, we have intermediate steps for re-shuffling the data.
This works by reading the dataset as a <a href="https://docs.dask.org/en/latest/dataframe.html">Dask dataframe</a> 
repartitioning the dataframe using network shuffle, and writing it out again to a dataset.</p>

<p>The size of the data we work on varies strongly depending on the individual customer.
The largest ones currently amount to about one billion rows per day. 
This corresponds to 30 GiB of compressed Parquet data, which is roughly 500 GiB of uncompressed data in memory.</p>

<h2 id="dask-cluster-setup-at-blue-yonder">Dask cluster setup at Blue Yonder</h2>

<p>We run a somewhat unique setup of Dask clusters that is driven by the specific requirements
of our domain.
For reasons of data isolation between customers and environment isolation for SaaS applications 
we run separate Dask clusters per customer and per environment (production, staging, and development).</p>

<p>But it does not stop there. 
The service we provide to our customers is comprised of several products that build upon each other
and maintained by different teams. We typically perform daily batch runs with these products running sequentially
in separated environments.
For performance reasons, we install the Python packages holding the code needed for the computations on each worker. 
We do not want to synchronize the dependencies and release cycles of our different products, which
means we have to run a separate Dask cluster for each of the steps in the batch run.
This results in us operating more than
ten Dask clusters per customer and environment, with most of the time, only one of the clusters being active
and computing something. While this leads to overhead in terms of administration and hardware resouces,
(which we have to mitigate, as outlined below)
it also gives us a lot of flexibility. For instance, we can update the software on the cluster of one part of the compute pipeline
while another part of the pipeline is computing something on a different cluster.</p>

<h3 id="some-numbers">Some numbers</h3>

<p>The number and size of the workers varies from cluster to cluster depending on the degree of parallelism
of the computation being performed, its resource requirements, and the available timeframe for the computation.
At the time of writing, we are running more than 500 distinct clusters.
Our clusters have between one and 225 workers, with worker size varying between 1GiB and 64GiB of memory.
We typically configure one CPU for the smaller workers and two for the larger ones.
While our Python computations do not leverage thread-level parallelism, the Parquet serialization part,
which is implemented in C++, can benefit from the additional CPU.
Our total memory use (sum over all clusters) goes up to as much as 15TiB.
The total number of dask workers we run varies between 1000 and 2000.</p>

<p><img src="/assets/images/2020-03-16-dask-usage-at-by-n-workers.png" alt="Simple Dask graph with parallel nodes and final reduction" /></p>

<h2 id="cluster-scaling-and-resilience">Cluster scaling and resilience</h2>

<p>To improve manageability, resilience, and resource utilization, we run the Dask clusters on top
of <a href="http://mesos.apache.org/">Apache Mesos</a>/<a href="http://aurora.apache.org/">Aurora</a> 
and <a href="https://kubernetes.io/">Kubernetes</a>. This means every worker as well as the scheduler and client
each run in an isolated container. Communication happens via a simple service mesh 
implemented via  reverse proxies to make the communication
endpoints independent of the actual container instance.</p>

<p>Running on top of a system like Mesos or Kubernetes provides us with resilience since a failing worker 
(for instance, as result of a failing hardware node)
can simply be restarted on another node of the system. 
It also enables us to easily commission or decommission Dask clusters, making the amount of clusters we run
manageable in the first place.</p>

<p>Running 500 Dask clusters also requires a lot of hardware. We have put two measures in place to improve
the utilization of hardware resources: oversubscription and autoscaling.</p>

<h3 id="oversubscription">Oversubscription</h3>

<p><a href="http://mesos.apache.org/documentation/latest/oversubscription/">Oversubscription</a> is a feature of Mesos
that allows allocating more resources than physically present to services running on the system.
This is based on the assumption that not all services exhaust all of their allocated resources at the same time.
If the assumption is violated, we prioritize the resouces to the more important ones.
We use this to re-purpose the resources allocated for production clusters but not utilized the whole time
 and use them for development and staging systems.</p>

<h3 id="autoscaling">Autoscaling</h3>

<p>Autoscaling is a mechanism we implemented to dynamically adapt the number of workers in a Dask cluster
to the load on the cluster. This is possible since Mesos/Kubernetes . This
makes it really easy to add or remove worker instances from an existing Dask cluster.</p>

<p>To determine the optimum number of worker instances to run, we added the <code class="language-plaintext highlighter-rouge">desired_workers</code> metric to Distributed.
The metric exposes
the degree of parallelism that a computation has and thus allows us to infer how much workers a cluster should
ideally have. Based on this metric, as well as on the overall resources available and on fairness criteria
(remember, we run a lot of Dask/Distributed clusters), we add or remove workers to our clusters.
To resolve the problem of balancing the conflicting requirements for different resouces like RAM or CPUs
 using <a href="https://cs.stanford.edu/~matei/papers/2011/nsdi_drf.pdf">Dominant Resource Fairness</a>.</p>

<h2 id="dask-issues">Dask issues</h2>

<p>The particular way we use Dask, especially running it in containers connected by reverse proxies and the fact
that we dynamically add/remove workers from a cluster quite frequently for autoscaling has lead us to hit some
edge cases and instabilities and given us the chance to contribute some fixes and improvements to Dask.
For instance, we were able to 
<a href="https://github.com/dask/distributed/pull/3246">improve stability after connection failures</a> or when 
<a href="https://github.com/dask/distributed/pull/3366">workers are removed from the cluster</a>.</p>

<p>If you are interested in our contributions to Dask and our commitment to the dask community, please
also check out our blog post <a href="../dask-developer-workshop/">Karlsruhe to D.C. ― a Dask story</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Overall, we are very happy with Dask and the capabilities it offers. 
Having migrated to Dask from a proprietary compute framework that was developed within our company,
we noticed that we have had similar pain points with both solutions: running into edge cases and robustness
issues in daily operations. 
However, with Dask being an open source solution, we have the confidence that others can also profit from
the fixes that we contribute, and that there are problems we <strong>don’t</strong> run into because other people have
already experienced and fixed them.</p>

<p>For the future, we envision adding even more robustness to Dask: 
Topics like scheduler/worker resilience (that is, surviving the loss of a worker or even the scheduler without losing computation results)
and flexible scaling of the cluster are of great interest to us.</p>]]></content><author><name>Andreas Merkel and Kshitij Mathur</name></author><category term="technology" /><category term="python" /><category term="data-engineering" /><summary type="html"><![CDATA[Dask Usage at Blue Yonder]]></summary></entry><entry><title type="html">Karlsruhe to D.C. ― a Dask story</title><link href="https://tech.blueyonder.com/dask-developer-workshop/" rel="alternate" type="text/html" title="Karlsruhe to D.C. ― a Dask story" /><published>2020-05-28T19:00:00+00:00</published><updated>2020-05-28T19:00:00+00:00</updated><id>https://tech.blueyonder.com/dask-developer-workshop</id><content type="html" xml:base="https://tech.blueyonder.com/dask-developer-workshop/"><![CDATA[<h1 id="karlsruhe-to-dc--a-dask-story">Karlsruhe to D.C. ― a Dask story</h1>

<p>Back in February 2020, we (Florian Jetter, Nefta Kanilmaz and Lucas Rademaker) travelled to the Washington D.C. metropolitan area to attend the first ever Dask developer conference. Our primary goal was to discuss Blue Yonder’s issues related to Dask with the attending developers and users. When we returned back to Germany, not only had we connected with many of the core developers in the Dask community, but we had also (almost finished) an implementation of a distributed semaphore in our bags.</p>

<p>There is <a href="https://blog.dask.org/2020/04/28/dask-summit">a blog post</a> giving a general overview and summary of the workshop talks. This blog post is a summary of <em>our</em> experience of the workshop..</p>

<h2 id="setting">Setting</h2>

<p>The three-day long workshop included sessions with short talks, as well as time slots for working on Dask-related issues and discussions.
The talks covered a broad spectrum of topics including Dask-ML and the usage of Dask in general data analysis, Dask deployment and infrastructure, and many more. All talks focused on answering these key questions:</p>

<ul>
  <li>Who are Dask users and what are their use cases?</li>
  <li>What are current pain points and what needs to be fixed as soon as possible?</li>
  <li>What is on the Dask users’ wish list?</li>
</ul>

<p>In other words, this was an opportunity for presenters to officially “rant” [*] about Dask, with experts having the experience and knowledge to help in the same room.
This exchange between users and developers enabled immediate fixes of minor problems, identifying synergies between different projects, as well as shaping the roadmap for future development of Dask. We feel that this is a successful model for driving the Dask open-source community.</p>

<p>[*] quote Matthew Rocklin</p>

<h2 id="the-blue-yonder-way-of-using-dask">The Blue Yonder way of using Dask</h2>

<p>We at Blue Yonder are still finishing our migration from a proprietary job scheduler to Dask.distributed. This is why when <em>we</em> talk about <em>Dask</em> in this post, we are mostly referring to <em>Dask.distributed</em>, as this is our main use case.</p>

<p>We quickly realized at the workshop that Blue Yonder’s use case is almost unique in the entire Dask community.</p>

<p>Florian Jetter opened proceedings at the conference with a presentation on the typical data flow for our machine learning products, our usage of Dask and Dask.distributed within this flow and where we were currently facing issues.</p>

<p>At Blue Yonder, we provide Machine Learning driven solutions to our customers mainly in retail. The data - which our customers provide through an API - is inserted into a relational database. The Machine Learning and prediction steps need a denormalized data format, which we provide in the form of Parquet datasets in the Azure Blob Store. The resulting predictions are written back to Parquet files and offered to customers through an API.
The Data Engineering team leverages the distributed scheduler for parallel task execution during the data transformation steps between database and Parquet datasets. Most parts of these pipelines use Dask bags or delayed objects for map/reduce operations. Dask dataframes are especially useful when we reshuffle datasets with an existing partitioning to a differently partitioned dataset. We use <a href="https://github.com/JDASoftwareGroup/kartothek">Kartothek</a> for this purpose.</p>

<p>A lot of Dask users we met at the workshop also utilized Dask clusters for data-heavy calculations. However, the Blue Yonder use case remains special: it appears that most other users are working in an R&amp;D-like environment and their computations are not running in comparable production environments. Most importantly, these users have no service level agreements with customers: SLAs, for example, for delivery times for predictions. From our interactions with users, it became apparent that Blue Yonder puts higher requirements on the stability and robustness of the distributed scheduler than any other users present at the conference.</p>

<h2 id="issues-we-have-encountered-with-dask-and-potential-improvements">Issues we have encountered with Dask and potential improvements</h2>

<p>During discussions with conference attendees we touched upon some topics that have been (or still are) big problems for us and encountered other community members with similar experiences. Below are some of the “rants” we’d like to share.</p>

<h3 id="distributed-stability">Distributed stability</h3>
<p>We migrated some of our largest data pipelines to Dask in the last months of 2019. For these pipelines, we started to observe significant instability during the computation of the Dask graph. We hadn’t seen this issue in any of our previous pipelines running on distributed. Our team contributed several patches upstream in order to resolve these issues on our side.</p>

<p>During the working sessions, a number of conversations related to the stability of the distributed scheduler took place.
One idea which came up, to increase the overall robustness of distributed, was <em>replication of task results</em>. The idea is as follows: if a worker which is holding a task result is shut down, replicating the task result will allow certain parts of the graph to not need to be re-computed.
As of the time of this writing, we have not had a chance to actively work on something like this yet, but it is something that we keep in mind.</p>

<h3 id="performance-and-graph-optimization">Performance and graph optimization</h3>
<p>While monitoring the execution of one of our Dask production pipelines, we observed that the amount of memory consumed by the workers was significantly higher than one would expect in an ideal scenario where tasks are executed in a way which minimizes memory usage of the workers.
When investigating the Dask.optimization module, we saw that code to optimize memory usage was already there. In practice, however, the graphs are not executed in the optimal order with regards to memory usage because of other constraints during execution.</p>

<p>A Dask user at the workshop facing this issue told us that they worked around this by injecting dummy dependencies into Dask graphs. These dependencies acted as “choke-holds” for certain types of tasks, in order to improve the memory usage during execution.</p>

<p>This lack of optimization, also referred to as memory back-pressure, not only impacts memory usage but also increases resource consumption. We would greatly benefit from an implementation which addresses this issue.</p>

<h2 id="looking-back">Looking back</h2>

<h3 id="results-from-working-sessions">Results from working sessions</h3>
<p>Lucas had the pleasure to collaborate with John Lee on writing down a benchmark to enable <a href="https://github.com/Dask/distributed/pull/3069">work stealing for tasks with restrictions</a>. He also got valuable input from Tom Augspurger on dask internals while debugging a Dask.dataframe bug involving categoricals.</p>

<p>However, arguably the most useful work we did was the implementation of a semaphore in Dask.distributed.
Our workflows depend heavily on accessing resources like production databases. Migrating them to the distributed scheduler was therefore not possible without first being able to rate-limit the access of computation cluster to such resources. For this, we needed a semaphore implementation.
Coincidentally, we also talked with other attendees of the workshop whom were interested in such a functionality.
This is something we did not forget; despite the jetlag, we left D.C. with a good chunk of the implementation necessary for a semaphore. This finally got <a href="https://github.com/Dask/distributed/commit/2129b740c1e3f524e5ba40a0b6a77b239d4c1f94">released</a> with distributed 2.14.0.</p>

<h3 id="interaction-with-the-community">Interaction with the community</h3>
<p>Being able to share our issues with the Dask community and discuss potential ways of improvement with expert users and core developers was extremely valuable. Additionally, this interaction gave us a wider perspective of the current status of Dask, the ecosystem around it and what we can expect from it in the future.</p>

<p>We were also reminded once again of the value of open-source software. 
There was a clear synergy in terms of intended functionality between <a href="https://github.com/JDASoftwareGroup/kartothek">Kartothek</a> and other tools in the ecosystem such as Arrow/Parquet and Dask.dataframe I/O and partitioning.
Dask is driven by a wide community of skilled and passionate developers. 
By committing to this community-driven project, we get to collaboratively shape the best software out there.</p>

<h3 id="final-remarks">Final remarks</h3>

<p>We are grateful to have had the opportunity to attend this workshop.
We’d like to thank the organizers for their hospitality and the success of this workshop.</p>

<p>See you at the Dask developer workshop 2021 ;).</p>]]></content><author><name>Lucas Rademaker &amp; Nefta Kanilmaz</name></author><category term="technology" /><category term="python" /><category term="data-engineering" /><category term="Dask" /><category term="distributed" /><summary type="html"><![CDATA[Karlsruhe to D.C. ― a Dask story]]></summary></entry></feed>