<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.3">Jekyll</generator><link href="https://www.ryandaigle.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.ryandaigle.com/" rel="alternate" type="text/html" /><updated>2018-08-16T13:49:36+00:00</updated><id>https://www.ryandaigle.com/</id><title type="html">Ryan Daigle</title><subtitle>A smattering of engineering, technology, and related stuffs</subtitle><entry><title type="html">iPad Pro Publishing: From Ulysses to Jekyll with One Tap</title><link href="https://www.ryandaigle.com/a/ipad-pro-publishing-from-ulysses-to-jekyll-with-one-tap/" rel="alternate" type="text/html" title="iPad Pro Publishing: From Ulysses to Jekyll with One Tap" /><published>2018-05-04T00:00:00+00:00</published><updated>2018-05-04T00:00:00+00:00</updated><id>https://www.ryandaigle.com/a/ipad-pro-publishing-from-ulysses-to-jekyll-with-one-tap</id><content type="html" xml:base="https://www.ryandaigle.com/a/ipad-pro-publishing-from-ulysses-to-jekyll-with-one-tap/">&lt;p&gt;With iOS 11, the iPad Pro made a tremendous leap in capability. The hardware is fantastic, multi-tasking is no longer a bolted on step-child, and keyboard-driven workflows are becoming more common.&lt;/p&gt;

&lt;p&gt;As I mainly &lt;a href=&quot;https://www.ryandaigle.com/a/my-ipad-pro-experiment/&quot;&gt;use my iPad&lt;/a&gt; for writing-based workflows, it was pretty important for me to be able to efficiently write, and publish, from my iPad. My blog is based on Github Pages, so it’s backed by a git repo and uses the Jekyll static site framework. On a laptop that’s easy to setup, but isn’t quite as seamless on the iPad.&lt;/p&gt;

&lt;p&gt;Thankfully, Workflow (and the excellent &lt;a href=&quot;https://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=&amp;amp;esrc=s&amp;amp;source=web&amp;amp;cd=1&amp;amp;cad=rja&amp;amp;uact=8&amp;amp;ved=0ahUKEwj6zcDyhevaAhXS21MKHVlRBPAQFggnMAA&amp;amp;url=https%3A%2F%2Fworkingcopyapp.com%2F&amp;amp;usg=AOvVaw1ZYFxW33bLylxCaJA-GM6L&quot; title=&quot;Working Copy, Git on iOS&quot;&gt;Working Copy&lt;/a&gt;) come to the rescue. From my writing app (Ulysses) I can publish with one button. Rather than describe the results, I’ll just post a video of me publishing this very post:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/IMG_0054.gif&quot; alt=&quot;&quot; title=&quot;Publish from Ulysses&quot; /&gt;&lt;/p&gt;

&lt;p&gt;💥💥&lt;/p&gt;

&lt;p&gt;I’ve published this &lt;a href=&quot;https://workflow.is/workflows/9dc238e6a4644b749380b96bf6779183&quot;&gt;workflow in the Workflow Gallery&lt;/a&gt; if you want to give it a try yourself. I’ve done my best to extract out all the Ryan-specific details but, let’s be honest, that’s a stink that you can never really escape.&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;p&gt;Here are the basic steps to get this workflow installed on your iPad. This assumes you have an existing &lt;a href=&quot;https://pages.github.com&quot;&gt;Github Pages&lt;/a&gt; site already setup in Github using Jekyll, and &lt;a href=&quot;https://itunes.apple.com/us/app/workflow/id915249334?mt=8&quot;&gt;Workflow&lt;/a&gt; installed on your iPad.&lt;/p&gt;

&lt;h3 id=&quot;working-copy&quot;&gt;Working Copy&lt;/h3&gt;

&lt;p&gt;Install the git client &lt;a href=&quot;https://itunes.apple.com/us/app/working-copy/id896694807?mt=8&quot;&gt;Working Copy&lt;/a&gt; on your iPad and be sure to enable URL callbacks from the application settings (in the app).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/Photo-2018-03-30-12-06-Qunzd1mhgRv.jpg&quot; alt=&quot;&quot; title=&quot;Enable x-callbacks in Working Copy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Write down/copy the URL key, which you will need when setting up the workflow.&lt;/p&gt;

&lt;p&gt;Also be sure to clone the Github Repo of your blog (e.g., &lt;code class=&quot;highlighter-rouge&quot;&gt;yourname.github.io&lt;/code&gt;) in Working Copy so you have the source on your iPad.&lt;/p&gt;

&lt;h3 id=&quot;workflow&quot;&gt;Workflow&lt;/h3&gt;

&lt;p&gt;Download my &lt;a href=&quot;https://workflow.is/workflows/9dc238e6a4644b749380b96bf6779183&quot;&gt;“Publish to Jekyll” workflow from the gallery&lt;/a&gt;. It will ask you a few questions to configure the integration, in this order…&lt;/p&gt;

&lt;p&gt;First is your Working Copy x-callback URL key (see above for getting that):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/Photo-2018-03-30-13-16-b31BLyaEYE9.jpg&quot; alt=&quot;&quot; title=&quot;Set Working Copy x-callback URL key&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next is the name of your blog’s github repo:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/Photo-2018-03-30-13-17-ubn697nXGoG.jpg&quot; alt=&quot;&quot; title=&quot;Set github repo name&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next is the URL to your blog’s site. If you have any extra path info to get to your blog posts, be sure to include it (but not the trailing &lt;code class=&quot;highlighter-rouge&quot;&gt;/&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/Photo-2018-03-30-13-18-pub9lhOF8JY.jpg&quot; alt=&quot;&quot; title=&quot;Set blog URL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, it will ask you for the author name you use when publishing your posts (this gets added to the “front matter” for each post in Jekyll):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/Photo-2018-03-30-13-19-u8SbrvzfjnJ.jpg&quot; alt=&quot;&quot; title=&quot;Set author handle/name&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once the workflow is downloaded and configured, it will appear in the share extension for any text document.&lt;/p&gt;

&lt;h3 id=&quot;publishing&quot;&gt;Publishing&lt;/h3&gt;

&lt;p&gt;I use Ulysses for all my long-form writing, and it’s a great place to publish from. Once you have an article you’re ready to publish, you can invoke the workflow right from the action button. The only nuance is you have to be sure you’re in Text/Markdown preview mode (vs. just sharing from the standard writing view). Here’s what that looks like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/IMG_0052.gif&quot; alt=&quot;&quot; title=&quot;Set Ulysses in text/markdown preview mode&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Once you’re viewing the text form of your article in Ulysses, you can trigger the “Publish to Jekyll” workflow and you’ll be off to the races!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/IMG_0054.gif&quot; alt=&quot;&quot; title=&quot;Publish from Ulysses&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The only thing the workflow will ask for during publishing is a list of tags for your post (used by my Jekyll setup - yours may not). Enter a comma-delimited list of tags if you use them, or leave the field blank if you don’t (or just remove that step from the workflow if you’re comfortable doing so).&lt;/p&gt;

&lt;p&gt;If successful, the workflow should complete and open your new post URL (which might not be live yet since it takes Github anywhere from a few seconds to minutes to actually publish your post).&lt;/p&gt;

&lt;p&gt;If it fails, the workflow will abort with (hopefully) an error message. Most of the time the error will have occurred in Working Copy (e.g., there’s a merge conflict, you don’t have the latest version on your iPad etc…).&lt;/p&gt;

&lt;p&gt;Publishing on an iPad may never be as seamless as a laptop - but it’s pretty impressive what you can accomplish by chaining a few well-implemented apps together.&lt;/p&gt;

&lt;p&gt;Happy publishing!&lt;/p&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="iPad Pro" /><category term="working copy" /><category term="ulysses" /><category term="jekyll" /><category term="github pages" /><summary type="html">With iOS 11, the iPad Pro made a tremendous leap in capability. The hardware is fantastic, multi-tasking is no longer a bolted on step-child, and keyboard-driven workflows are becoming more common.</summary></entry><entry><title type="html">My iPad Pro Experiment</title><link href="https://www.ryandaigle.com/a/my-ipad-pro-experiment/" rel="alternate" type="text/html" title="My iPad Pro Experiment" /><published>2018-02-09T00:00:00+00:00</published><updated>2018-02-09T00:00:00+00:00</updated><id>https://www.ryandaigle.com/a/my-ipad-pro-experiment</id><content type="html" xml:base="https://www.ryandaigle.com/a/my-ipad-pro-experiment/">&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/IMG_0688.jpg&quot; alt=&quot;&quot; title=&quot;Portrait mode, so you know it's fancy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve been intrigued with the iPad Pro as a laptop replacement for awhile now. The battery life, the constant connectivity, the rock solid and sand-boxed OS, the portability - these are things that really appeal to me. After reading one too many “&lt;a href=&quot;https://www.foraker.com/blog/using-the-ipad-pro&quot;&gt;how&lt;/a&gt; I &lt;a href=&quot;https://micropreneur.life/working-on-an-ipad-pro-for-developers/&quot;&gt;switched&lt;/a&gt; to the &lt;a href=&quot;https://jann.is/ipad-pro-for-programming/&quot;&gt;iPad Pro&lt;/a&gt;” blog posts from fellow tech-types I was convinced. It was time for me to take the plunge and give the iPad Pro a run as my laptop replacement.&lt;/p&gt;

&lt;p&gt;I scurried off to the Apple Store like a giddy school girl and walked out with a 12.9” iPad Pro and a Smart Keyboard. The world was my oyster. The sky was bright blue, the flowers were in bloom. It smelled like optimism and Panera (there was a Panera right by the mall exit).&lt;/p&gt;

&lt;p&gt;But that experiment turned out to be a failure.&lt;/p&gt;

&lt;p&gt;Not one week in, it was clear the iPad Pro could not be my laptop replacement. What happened?&lt;/p&gt;

&lt;h2 id=&quot;initial-expectations&quot;&gt;Initial expectations&lt;/h2&gt;

&lt;p&gt;When I first got the iPad Pro, I envisioned it being the only computer I would need, a true laptop replacement. I would live a life of complete freedom (I have a child), roaming an exotic European city (I live in the middle of North Carolina), settling into a quaint café (I’m not really a coffee person), confidently tying up my man bun (I’ve never had long hair) and working away on my creative endeavors (I have a first-grade level of creativity, at most) with the iPad.&lt;/p&gt;

&lt;p&gt;Ahhhh… perfection.&lt;/p&gt;

&lt;p&gt;In reality, even my rather conventional needs were left unrequited. And all because of one weak link.&lt;/p&gt;

&lt;h3 id=&quot;hardware&quot;&gt;Hardware&lt;/h3&gt;

&lt;p&gt;The iPad Pro hardware is fantastic. Amazing screen, great sound, high build quality, great form factor. 😚 Mmmwaaaa (or whatever sound one makes when holding their fingers to their mouth and kissing).&lt;/p&gt;

&lt;p&gt;The Smart Keyboard is actually quite good too. I had no issues writing for long periods of time on it. Just enough key-travel to satisfy my stubby nubs and the surface material is lightweight in feel, but durable with just the right texture. And the way it conveniently folds up as a cover for ultimate portability - superb.&lt;/p&gt;

&lt;p&gt;Could it have some extra quick-function keys like a home key, Siri key? Probably. But overall it’s a great little package and I can definitely recommend the Smart Keyboard.&lt;/p&gt;

&lt;h3 id=&quot;operating-system&quot;&gt;Operating system&lt;/h3&gt;

&lt;p&gt;iOS 11 (the current release as of this writing) is pretty great. It’s a really pragmatic combination of iOS’s traditional sandboxed app paradigm paired with some convenient multi-tasking workflows (think split-view, drag-n-drop, persistent menubar, etc…). It’s rock solid stable and you find yourself pulling out your phone less because all those mobile-centric workflows (Authy, iMessages, Twitter, etc…) are right at your fingertips now.&lt;/p&gt;

&lt;p&gt;Is control center a bit of a mess on the iPad? Yeah, it’s definitely not as convenient as on the iPhone. Same goes for Notification Center and whatever you call that screen over there to the left where widgets go to die. But as an overall experience, iOS on the iPad is pretty swell.&lt;/p&gt;

&lt;h3 id=&quot;the-apps&quot;&gt;The apps&lt;/h3&gt;

&lt;p&gt;The apps. Oh, the apps. This is where it starts to be a mixed bag. Are there some standout apps on the iPad? Absolutely! iOS is still the best app ecosystem by a long shot. However, the iPad-specific app ecosystem within isn’t nearly as robust.&lt;/p&gt;

&lt;p&gt;Most iPad apps, by now, have been upgraded to comfortably live on the larger dimensions of the iPad. But most still assume a touch-first interaction model. I believe is a mistake for the iPad Pro.&lt;/p&gt;

&lt;p&gt;For most folks the Pro will pretty much live in landscape mode paired with a keyboard. In that configuration, having to always reach up to find a touch-target, or execute the finger choreography required for some gestures, is a very jarring experience. It breaks the hands-on-keyboard posture and feels excessive given the frequency some apps make you tap on them.&lt;/p&gt;

&lt;p&gt;I’ve read enough peoples’ write-ups to know this isn’t a universal opinion. Some people don’t mind typing, reaching up to tap or swipe, then back down to continue typing. For me, though, this type of break is supremely frustrating. I’m a diva, deal with it 💁🏻‍♂️&lt;/p&gt;

&lt;p&gt;And even if you’re not a fragile snowflake like me, you will probably find several apps you rely on that are frustratingly unoptimized for a landscape-dominant setup. Broken web views, weird rendering quirks, all-around bad app citizens - these are common occurrences on the iPad.&lt;/p&gt;

&lt;p&gt;At the end of the day the reality is iPad apps are rarely any company’s first priority. They’re probably more inline with desktop apps on the development pecking order of: Mobile (iOS &amp;amp; Android) &amp;gt; Web app &amp;gt; Desktop/iPad. With these priorities, is it any wonder the app experience on the iPad is what betrays what is otherwise a compelling experience?&lt;/p&gt;

&lt;p&gt;And so … here is where our story ends. Me, frustrated by one too many poor app experiences (I’m looking at you Dropbox Paper) and disillusioned from my utopian vision.&lt;/p&gt;

&lt;p&gt;The iPad Pro ecosystem just isn’t ready yet.&lt;/p&gt;

&lt;h2 id=&quot;round-two&quot;&gt;Round Two&lt;/h2&gt;

&lt;p&gt;AND YET! And yet.&lt;/p&gt;

&lt;p&gt;And yet. Here I am, typing this on an iPad (10.5”, this time). What happened, what changed? Basically, my expectations did. I realized I was trying to shoehorn the iPad into my ideal use-case vs. letting the strengths of the device inform how I would use it.&lt;/p&gt;

&lt;p&gt;After returning the glorious piece of glass that is theiPad Pro 12.9”, I began to recognize the times where the freedom and focus provided by an iPad would be beneficial. Those half days where I co-work somewhere outside the home office. Those times when I need focus. The times when I’m on call and need a portable communications device. Does a laptop do 100% of what I need? Yes. But could an iPad do the most valuable 80% of what I need? My current working theory is that it can.&lt;/p&gt;

&lt;p&gt;Here is how I think about my iPad Pro now:&lt;/p&gt;

&lt;h3 id=&quot;writing&quot;&gt;Writing&lt;/h3&gt;

&lt;p&gt;A lot of what I do at work is communicating via writing. Long-form writing, Slack collaboration, blog posts, some emails, etc… The iPad is a great writing machine. I’ve installed &lt;a href=&quot;https://itunes.apple.com/us/app/ulysses/id1225571038?mt=8&quot; title=&quot;Ulysses for the iPad&quot;&gt;Ulysses&lt;/a&gt; and now use the iPad as my primary long-form writing device.&lt;/p&gt;

&lt;p&gt;Since I know Ulysses is one of those well-supported apps with excellent keyboard and landscape support (it really is, highly recommended), I can skirt the iPad app ecosystem issue I ran into during my initial experiment.&lt;/p&gt;

&lt;h3 id=&quot;focus&quot;&gt;Focus&lt;/h3&gt;

&lt;p&gt;I am a big fan of creating space in your day to focus on what Cal Newport calls “Deep Work”. Focused time, free of distractions, for you to make progress on some strategic, big picture, work. Too much of our day is consumed by this alert and that notification and this ping from a coworker.&lt;/p&gt;

&lt;p&gt;The iPad is my safe place.&lt;/p&gt;

&lt;p&gt;I have all notifications turned off. The iPad is where big chunks of work happen and notifications are death to focus.&lt;/p&gt;

&lt;p&gt;I almost went as far as not installing Slack, email and other work-related collaboration apps, but decided against it (for now, I am always re-evaluating)!&lt;/p&gt;

&lt;h3 id=&quot;portability&quot;&gt;Portability&lt;/h3&gt;

&lt;p&gt;The reason I installed Slack and some other real-time collaboration tools is that those are necessary for when I’m on call. Having them on my iPad lets me use the iPad as my sole on-call device. I can grab this always-on, thin and lite device and not have to worry about the power source or any other accessories. It’s all bundled into this convenient package.&lt;/p&gt;

&lt;p&gt;That little bit of convenience is incredibly valuable for high-frequency, high-inconvenience, use-cases like being on call.&lt;/p&gt;

&lt;p&gt;The balance I made between these distracting apps and my desire for focus is to move them all off the main home screen. My main view is simply a dock with writing-centric apps and nothing else.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://share.ryandaigle.com/IMG_0011.jpg&quot; alt=&quot;My home screen&quot; title=&quot;Peace and quiet, no distractions&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ahhh… focus.&lt;/p&gt;

&lt;h3 id=&quot;future&quot;&gt;Future&lt;/h3&gt;

&lt;p&gt;Finally, this experiment is a little bit of a bet on my part on what the future of computing will be. I believe we are seeing the beginning of an evolution that will bring tablet devices (led by iPad) into the mainstream of computing.&lt;/p&gt;

&lt;p&gt;I want to experience that progression first hand. Or at least enough of it to know why it doesn’t work for me and what that gaps remain.&lt;/p&gt;

&lt;p&gt;If you see more posts from me, the experiment is working.&lt;/p&gt;

&lt;p&gt;If not, well, get ready for round three 😱&lt;/p&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="iPad Pro" /><category term="ulysses" /><summary type="html"></summary></entry><entry><title type="html">Recursively List Files in Elixir</title><link href="https://www.ryandaigle.com/a/recursively-list-files-in-elixir/" rel="alternate" type="text/html" title="Recursively List Files in Elixir" /><published>2016-09-05T00:00:00+00:00</published><updated>2016-09-05T00:00:00+00:00</updated><id>https://www.ryandaigle.com/a/recursively-list-files-in-elixir</id><content type="html" xml:base="https://www.ryandaigle.com/a/recursively-list-files-in-elixir/">&lt;p&gt;Quick Elixir ditty to recursively list the files at a given path (relative or absolute):&lt;/p&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;defmodule&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FileExt&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ls_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;\\&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;cond&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regular?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dir?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ls!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Enum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&amp;amp;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Enum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ls_r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Enum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concat&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-elixir highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;FileExt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ls_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen/.git/COMMIT_EDITMSG&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen/.git/config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen/.git/description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen/.git/HEAD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;../exgen/.git/hooks/applypatch-msg.sample&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

 &lt;span class=&quot;no&quot;&gt;FileExt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ls_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen/.git/COMMIT_EDITMSG&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen/.git/config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen/.git/description&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen/.git/HEAD&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/Users/ryan/projects/exgen/.git/hooks/applypatch-msg.sample&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;sd&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="elixir" /><summary type="html">Quick Elixir ditty to recursively list the files at a given path (relative or absolute):</summary></entry><entry><title type="html">Generate an Apple Pay CSR with OpenSSL</title><link href="https://www.ryandaigle.com/a/openssl-generate-apple-pay-csr/" rel="alternate" type="text/html" title="Generate an Apple Pay CSR with OpenSSL" /><published>2014-11-06T18:07:56+00:00</published><updated>2014-11-06T18:07:56+00:00</updated><id>https://www.ryandaigle.com/a/openssl-generate-apple-pay-csr</id><content type="html" xml:base="https://www.ryandaigle.com/a/openssl-generate-apple-pay-csr/">&lt;p&gt;When creating an &lt;a href=&quot;https://www.apple.com/apple-pay/&quot;&gt;Apple Pay&lt;/a&gt; certificate signing request, Apple specifies that you need to use a 256 bit elliptic curve key pair. To generate both the private key and the CSR using the &lt;code class=&quot;highlighter-rouge&quot;&gt;openssl&lt;/code&gt; command line utility, do the following:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;openssl ecparam &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; private.key &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; prime256v1 &lt;span class=&quot;nt&quot;&gt;-genkey&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;openssl req &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-sha256&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; private.key &lt;span class=&quot;nt&quot;&gt;-nodes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; request.csr &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/O=Your Name or Company/C=US'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result will be a private key file at &lt;code class=&quot;highlighter-rouge&quot;&gt;private.key&lt;/code&gt; and the CSR in &lt;code class=&quot;highlighter-rouge&quot;&gt;request.csr&lt;/code&gt;. They should look something like this.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEID0Y+YLOz3ed+dMlh047WSwgxl3a0WVI4en3tjntAdwooAcGBSuBBAAK
oUQDQgAEwHGnT+kCI+oqFK8ALEZzBcqHC+QNwmCLQHx51zCT51TpZEIufTFpac3a
E5sNqznV2Dp39N0wVCAV7QPGI6SXvg==
-----END EC PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-----BEGIN CERTIFICATE REQUEST-----
MIH3MIGgAgEAMEExGTAXBgNVBAMTEHd3dy5zcHJlZWRseS5jb20xFzAVBgNVBAoT
DlNwcmVlZGx5LCBJbmMuMQswCQYDVQQGEwJVUzBWMBAGByqGSM49AgEGBSuBBAAK
A0IABMBxp0/pAiPqKhSvACxGcwXKhwvkDcJgi0B8edcwk+dU6WRCLn0xaWnN2hOb
Das51dg6d/TdMFQgFe0DxiOkl76gADAJBgcqhkjOPQQBA0cAMEQCIBGy+OBbsjey
lQhqezpSRt+IKfMMLdA78Pnck3fWIVxcAiBOYX1hmOREEysFQq0eX309iY0uZ3dm
MRDa/83lW8GcZQ==
-----END CERTIFICATE REQUEST-----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will need the private key to decrypt the Apple Pay cryptogram so keep it in a secure place. The CSR you will then upload to Apple, which will sign the certificate for you to use in your iOS app.&lt;/p&gt;

&lt;p&gt;Normally, your payment provider will generate the private key (which they will retain) and CSR for you, so doing this yourself won’t be necessary. However, if you’re a payment provider like &lt;a href=&quot;https://spreedly.com&quot;&gt;Spreedly&lt;/a&gt; or are interested in managing more of the payment flow, this may be useful.&lt;/p&gt;

&lt;p&gt;A few notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use the &lt;code class=&quot;highlighter-rouge&quot;&gt;prime256v1&lt;/code&gt; elliptic curve when generating your private key as this is what’s used by Apple’s Keychain app when generating an EC key.&lt;/li&gt;
  &lt;li&gt;Apple will add/overwrite the UID, CN (common name) and OU (organizational unit) certificate fields with the associated merchant identifier so your CSR doesn’t need to specify them.&lt;/li&gt;
&lt;/ul&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="apple pay" /><category term="openssl" /><summary type="html">When creating an Apple Pay certificate signing request, Apple specifies that you need to use a 256 bit elliptic curve key pair. To generate both the private key and the CSR using the openssl command line utility, do the following:</summary></entry><entry><title type="html">Exposing a Javascript API in a Web Page with Browserify</title><link href="https://www.ryandaigle.com/a/expose-javascript-api-with-browserify/" rel="alternate" type="text/html" title="Exposing a Javascript API in a Web Page with Browserify" /><published>2014-10-09T18:07:56+00:00</published><updated>2014-10-09T18:07:56+00:00</updated><id>https://www.ryandaigle.com/a/expose-javascript-api-with-browserify</id><content type="html" xml:base="https://www.ryandaigle.com/a/expose-javascript-api-with-browserify/">&lt;p&gt;When using Browserify to build and resolve your javascript library dependencies, it’s very easy to get a resulting &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle.js&lt;/code&gt; file that you can include in a browser with a simple script tag.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/assets/bundle.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Including the bundle in this way will execute the code in your entrypoint (often &lt;code class=&quot;highlighter-rouge&quot;&gt;main.js&lt;/code&gt;) which is where most online tutorials end and which might be all you need. However, how do you create a bundle that will expose an API to the including page? So you can do something like:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/assets/bundle.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lib&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You need to specify the &lt;code class=&quot;highlighter-rouge&quot;&gt;standalone&lt;/code&gt; option to the browserify command (or API), which will export the API you expose in your entrypoint file in the given namespace. Here’s an example.&lt;/p&gt;

&lt;p&gt;Given a library file where your main library functionality is defined:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'underscore'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;aSetting&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;MyLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;doWork&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;aSetting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And your browserify entrypoint which exposes your library API:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'./lib'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, if you were to build a bundle with browserify and include it in a web page, you wouldn’t be able to access &lt;code class=&quot;highlighter-rouge&quot;&gt;MyLibrary&lt;/code&gt;. Since browserify makes sure everything is local scoped, and the web page doesn’t know how to deal with your top level export, it’s effectively hidden. The solution is to tell browserify to expose your exports with the &lt;code class=&quot;highlighter-rouge&quot;&gt;standalone&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;As a command it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;browserify main.js &lt;span class=&quot;nt&quot;&gt;--standalone&lt;/span&gt; MyLibrary &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; bundle.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And if you’re using Gulp or similar the API options looks like:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;browserify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'./main.js'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;standalone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'MyLibrary'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this resulting bundle you can now reference &lt;code class=&quot;highlighter-rouge&quot;&gt;MyLibrary&lt;/code&gt; from the including html page.&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/assets/bundle.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lib&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;MyLibrary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;doWork&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="browserify" /><summary type="html">When using Browserify to build and resolve your javascript library dependencies, it’s very easy to get a resulting bundle.js file that you can include in a browser with a simple script tag.</summary></entry><entry><title type="html">Time-Series Database Design with InfluxDB</title><link href="https://www.ryandaigle.com/a/time-series-db-design-with-influx/" rel="alternate" type="text/html" title="Time-Series Database Design with InfluxDB" /><published>2013-08-13T00:00:00+00:00</published><updated>2013-08-13T00:00:00+00:00</updated><id>https://www.ryandaigle.com/a/time-series-db-design-with-influx</id><content type="html" xml:base="https://www.ryandaigle.com/a/time-series-db-design-with-influx/">&lt;p&gt;Here at &lt;a href=&quot;https://spreedly.com/&quot;&gt;Spreedly&lt;/a&gt; we’ve recently started using the time series database &lt;a href=&quot;http://influxdb.com/&quot;&gt;InfluxDB&lt;/a&gt; to store a variety of customer activity metrics. As with any special purpose database, using and designing for a time-series database is quite different than what you may be used to with structured (SQL) databases. I’d like to describe our experience designing our InfluxDB schema, the mistakes we made, and the conclusions we’ve come to based on those experiences.&lt;/p&gt;

&lt;h2 id=&quot;the-mark&quot;&gt;The mark&lt;/h2&gt;

&lt;p&gt;Consider the following scenario, closely resembling Spreedly’s: You run a service that lets your customers transact against a variety of payment gateways. You charge for this service on two axes – by the number of gateways provisioned and the number of credit cards stored. For any point in time you want to know how many of both each of your customers has for their account.&lt;/p&gt;

&lt;p&gt;Initially we setup two series (InfluxDB’s term for a collection of measurements, organizationally similar to a SQL database table) to store the total number of each item per account:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;gateway.account.sample&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;payment-method.account.sample&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On some regular interval we’d collect the number of gateways and payment methods (credit cards) for each account and store it in the respective series. Each measurement looked like:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1400803300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;account_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;abc123&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;time&lt;/code&gt; is required by InfluxDB and is the &lt;a href=&quot;http://en.wikipedia.org/wiki/Unix_time&quot;&gt;epoch time&lt;/a&gt; of the measurement. The &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt; is our value of the measurement at that time, and &lt;code class=&quot;highlighter-rouge&quot;&gt;account_key&lt;/code&gt; is an additional property of that measurement.&lt;/p&gt;

&lt;p&gt;Simple enough. This approach felt good and we went to production with this schema. That’s when we learned our first lesson…&lt;/p&gt;

&lt;h2 id=&quot;time-scoped-queries&quot;&gt;Time-scoped queries&lt;/h2&gt;

&lt;p&gt;The first app that used the data in InfluxDB was our customer Dashboard product. It displays all your transactions and a simple view of your current billing counts (number of gateways and number of stored payment methods). Dashboard simply queried for the most recent measurement from each series for the current account:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'abc123'&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Since results in InfluxDB are ordered by default most recent first, the &lt;code class=&quot;highlighter-rouge&quot;&gt;limit 1&lt;/code&gt; clause ensures only the most recent measurement is returned for that customer (account).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All was fine initially, but as our dataset grew into the hundreds of thousands entries for each series we noticed our queries were taking quite some time to complete - about a constant 5s for every account. It turns out that these queries were incurring a full table scan, hence the constant (poor) performance.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Avoid a full table scan by always time-scoping your queries&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In InfluxDB, the non-time fields aren’t indexed, meaning any queries that filter based on them require a full table scan (even if you’re only fetching a single result). The way to avoid a full table scan is to always time-scope your queries. Knowing this we modified our queries to only query against the previous 2 days worth of data (enough time to capture the most recent input):&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'abc123'&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Adding the &lt;code class=&quot;highlighter-rouge&quot;&gt;where time &amp;gt; now() - 2d&lt;/code&gt; clause ensures that the query operates against a manageable set of data and avoids a full table scan. This dropped our query times from 5s (and growing) down to a steady 100ms - 200ms. (Keep in mind this is a remote instance of InfluxDB, meaning the bulk of that is in connection setup and network latency.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/2b2A2T1k1P381v04170V/Image%202014-08-07%20at%205.17.05%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;InfluxDB response time reduction using time-scoped queries. Y-axis truncated for maximum obfuscation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Obviously your use-case may differ wildly from ours. If your data is collected at unknown intervals, or in real-time, you don’t have the luxury of limiting your queries to a known window of time. In these situations it is wise to think about how to segment your data into series for optimal performance.&lt;/p&gt;

&lt;h2 id=&quot;series-granularity&quot;&gt;Series granularity&lt;/h2&gt;

&lt;p&gt;How many series should you have? How much data should you store in each series? When should you break out queries into their own series? These are all common questions when designing your time-series schema and, unfortunately, there is no concrete right or wrong answer. However, there are some good rules of thumb to keep in mind when structuring your data.&lt;/p&gt;

&lt;p&gt;Continuing from our previous example: We were now using time-scoped queries to get the total number of gateways and cards for each account. While we were seeing good performance, each query was operating against a single series that contained data for all accounts. The query’s &lt;code class=&quot;highlighter-rouge&quot;&gt;account_key&lt;/code&gt; condition was responsible for filtering the data by account:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'abc123'&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As even this already time-scoped set of data grows, querying against a non-indexed field will start to become an issue. Queries whose conditions eliminate a large percentage of the data within the series should be extracted out into their own series. E.g., in our case we have a query that gets a single account’s count of stored gateways to the exclusion of all the other accounts. This is an example of a query that filters out the majority of the data in a series and should be extracted so each account has its own series.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Series are cheap. Use them liberally to isolate highly conditional data access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re coming from a SQL-based mindset, the thought of creating one series per account might seem egregious. However, it’s perfectly acceptable in time-series land. So that’s what we did - we starting writing data from each account into its own series (with each series’ name including the account key). Now, when querying for an account’s total number of stored gateways we do:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;abc1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since you have to know the key in question to access the right series, this type of design is most common with primary (or other well-known keys). But… not only can series be segmented by key, segmenting by time period is also possible. While not useful in our specific situation, &lt;a href=&quot;http://en.wikiquote.org/wiki/Billy_Madison#Dialogue&quot;&gt;you can imagine&lt;/a&gt; segmenting data into monthly series, e.g., &lt;code class=&quot;highlighter-rouge&quot;&gt;201407.gateway.sample&lt;/code&gt; or some other period, depending on your access pattern.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/3v2r0h123I2p2v0Q2q0R/chris-farley-you-could-imagine-o.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can imagine…&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;multi-purpose-data&quot;&gt;Multi-purpose data&lt;/h2&gt;

&lt;p&gt;At this point your series are lean and efficient, well-suited for accessing a single type of query and data. However, sometimes life isn’t that clean and you have one set of data that needs to be accessed in many different ways.&lt;/p&gt;

&lt;p&gt;For instance, at Spreedly, we’d like to have a business-level set of metrics available that shows the total number of gateways and payment-methods &lt;em&gt;across all customers&lt;/em&gt;. We could just dump summary-level data into a new series (not a terrible idea), but we’re already collecting this data on a customer-level. It’d be nice not to have to do two writes per measurement.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Use continuous queries to re-purpose broad series by access pattern&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fortunately, InfluxDB has a feature called &lt;a href=&quot;http://influxdb.com/docs/v0.8/api/continuous_queries.html&quot;&gt;continuous queries&lt;/a&gt; that lets you modify and isolate data from one series into one or more other dependent series. Continuous queries are useful when you want to “rollup” time-series data by time period (e.g., get the 99th percentile service times across 5, 10 and 15 minute periods) and also to isolate a subset data for more efficient access. This latter application is perfect for our use-case.&lt;/p&gt;

&lt;p&gt;To use continuous queries to support both summary and account-specific stats we need to create the parent series that contain measurements for each account.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1400803300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;account_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;abc123&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1400803300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;account_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;def456&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can access this series directly to obtain the business-level stats we need across all customers:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With continuous queries we can also use this parent series to spawn several “fanout” queries that isolate the data by account (replicating the account-specific series naming scheme from earlier):&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;account_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gateway&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice the &lt;code class=&quot;highlighter-rouge&quot;&gt;[account_key]&lt;/code&gt; interpolation syntax? This creates one series per account and stores the value field from each measurement into the new account-specific series (retaining the original measurement’s time):&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1400803300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1400803300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this structure we:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Only write the data one time into the parent series &lt;code class=&quot;highlighter-rouge&quot;&gt;gateway.account.sample&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Can perform summary level queries against this parent series&lt;/li&gt;
  &lt;li&gt;Have access to the highly efficient, constantly updated, account-specific, data series &lt;code class=&quot;highlighter-rouge&quot;&gt;account-def456.gateway.sample&lt;/code&gt; etc…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a great use of fanout continuous queries. Also available are regular continuous queries which operate by precomputing expensive &lt;code class=&quot;highlighter-rouge&quot;&gt;group by&lt;/code&gt; queries. I’ll skip over them for now since we’re not yet using them at Spreedly, but I encourage you to look at them for your use cases.&lt;/p&gt;

&lt;h2 id=&quot;naming-and-structure&quot;&gt;Naming and structure&lt;/h2&gt;

&lt;p&gt;Series naming and packet structure is a tough topic due to personal preferences, differences in client languages and highly varied access patterns. I’m not going to label the following as best-practices, instead I’ll present what we’ve found at Spreedly, our motivations, and let you decide whether it makes sense for you to apply.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Come up with a naming structure that conveys both the purpose of the series and the type of data contained within. At Spreedly it’s something like (and still evolving): &lt;code class=&quot;highlighter-rouge&quot;&gt;[key].measured-item.[grouping].measurement-type&lt;/code&gt;. For instance, the series that contains the count of all gateways stored by account is &lt;code class=&quot;highlighter-rouge&quot;&gt;gateway.account.sample&lt;/code&gt;. The account-specific version is: &lt;code class=&quot;highlighter-rouge&quot;&gt;account-abc123.gateway.sample&lt;/code&gt;. The &lt;code class=&quot;highlighter-rouge&quot;&gt;measurement-type&lt;/code&gt; component is highly influenced by the &lt;a href=&quot;https://github.com/ryandotsmith/l2met/wiki/Usage#logging-convention&quot;&gt;l2met logging conventions&lt;/a&gt; and deserves further discussion.
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; series record a specific number of times something happened in a specific period of time as an integer. Counts can be summed with other counts in the same series to perform time-based aggregations (rollups). Measured number of requests or transactions per minute are an example of a &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; series.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;sample&lt;/code&gt; series take a point in time measurement of some metric that supercedes all previous samples of the same series. Sum totals are a good example of this type of series, e.g., total revenue to date, or total number of payment methods. With each measurement in the series, previous measurements are no longer relevant, though they may still be used to track trends over time.&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;measure&lt;/code&gt; series are similar to &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; series except that instead of being a simple representation of the number of times something happen, they can represent any unit of measure such as ms, Mb etc…  Measurements are be mathmatically operable and can be summed, percentiled, averaged etc… CPU load and response times are examples of &lt;code class=&quot;highlighter-rouge&quot;&gt;measure&lt;/code&gt; series.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Often there is a single value that represents the thing being measured, with the rest of the fields being meta-data or conditions. To facilitate re-usable client parsing we’ve found it nice to use the same field name across all series to represent the value of the measurement. Unsurprisingly, we chose &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt;. All our series data contains a &lt;code class=&quot;highlighter-rouge&quot;&gt;value&lt;/code&gt; field that contains the measurement value. This makes it easy to retrieve, which is especially useful in queries that select across multiple series or even &lt;a href=&quot;http://influxdb.com/docs/v0.8/api/query_language.html#merging-series&quot;&gt;merge results from multiple series&lt;/a&gt; into a single result set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a lot of subjectivity that goes into database design, independent of the storage &lt;a href=&quot;http://adland.tv/commercials/verizon-boxymoron-2002-030-usa&quot;&gt;paradigm&lt;/a&gt;. While SQL has been around for awhile and has well-known patterns, alternative databases, including time-series databases, are a bit more of a wild west. I’m hoping that by sharing our experiences we can prevent some common mistakes, freeing you up to create all new ones of your own!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Many thanks to &lt;a href=&quot;https://twitter.com/pauldix&quot;&gt;Paul&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/toddpersen&quot;&gt;Todd&lt;/a&gt; and the rest of InfluxDB for their tireless guidance on the subject&lt;/em&gt;&lt;/p&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="time series" /><category term="influxdb" /><summary type="html">Here at Spreedly we’ve recently started using the time series database InfluxDB to store a variety of customer activity metrics. As with any special purpose database, using and designing for a time-series database is quite different than what you may be used to with structured (SQL) databases. I’d like to describe our experience designing our InfluxDB schema, the mistakes we made, and the conclusions we’ve come to based on those experiences.</summary></entry><entry><title type="html">The New Gist: What It Is and What It Could Be</title><link href="https://www.ryandaigle.com/a/the-new-github-gists/" rel="alternate" type="text/html" title="The New Gist: What It Is and What It Could Be" /><published>2012-12-13T00:00:00+00:00</published><updated>2012-12-13T00:00:00+00:00</updated><id>https://www.ryandaigle.com/a/the-new-github-gists</id><content type="html" xml:base="https://www.ryandaigle.com/a/the-new-github-gists/">&lt;p&gt;&lt;a href=&quot;http://gist.github.com&quot;&gt;Gist&lt;/a&gt; is an incredible tool by Github for quickly sharing code, text and files. It has syntax highlighting and rendering for a huge number of programming languages including Markdown for text. For many techies, including myself, Gist is an indispensable tool for quickly sharing code and content with coworkers.&lt;/p&gt;

&lt;p&gt;Gist has been around for several years now and, when compared with the pace of development on the main Github.com property, has been relatively neglected. Thankfully, Github &lt;a href=&quot;https://github.com/blog/1276-welcome-to-a-new-gist&quot;&gt;recently updated Gist&lt;/a&gt; with a fresh new codebase and UI. As a heavy user of Gist I have some thoughts on this update, where it hits the mark and where it’s still lacking.&lt;/p&gt;

&lt;h2 id=&quot;search&quot;&gt;Search&lt;/h2&gt;

&lt;p&gt;Search has long been sorely needed in Gist. It is not uncommon for power users to have several hundred to thousands of gists and the previous linear list-view based on creation date was inadequate. Immediate recall of a gist based on a search query was the primary use-case I had in mind when creating &lt;a href=&quot;https://gistedapp.herokuapp.com&quot;&gt;Gisted - a tool to quickly search and access all your gists&lt;/a&gt;. So, seeing a native Gist search feature was very welcome for me.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/1J0V2s471k2n0n0M2V3Y/Image%202012-12-12%20at%205.05.36%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, it leaves a bit to be desired. Firstly the search is case &lt;em&gt;sensitive&lt;/em&gt;. So searching for &lt;code class=&quot;highlighter-rouge&quot;&gt;proposal&lt;/code&gt; is not the same as &lt;code class=&quot;highlighter-rouge&quot;&gt;Proposal&lt;/code&gt;. When searching my gists I never care about the case and want to just quickly find the most relevant gist containing that term. Fortunately, I imagine this to be a very easy fix on Github’s end and expect it will be remedied shortly (based on nothing but my intuition).&lt;/p&gt;

&lt;h3 id=&quot;indexing&quot;&gt;Indexing&lt;/h3&gt;

&lt;p&gt;However, more fundamentally, search seems to only apply to the description of your gist (gists don’t have titles - the closest thing is what is labeled as the description). While I try to be very conscious of creating meaningful descriptions, when I search for them I often use some distinct term from within the gist content itself. Searching only based on description is like searching Google only based on the title of the web page.&lt;/p&gt;

&lt;p&gt;Consider the results from the new gist search for a term I know exists: &lt;code class=&quot;highlighter-rouge&quot;&gt;dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/1s2n3z0i0u0D3k1a2a06/Image%202012-12-12%20at%205.13.40%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Only one result? I think not. Now against descriptions and file contents:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/1D0R3e023C0s1x3G1L3R/Image%202012-12-12%20at%205.15.22%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are lots of relevant results missed by Gist’s search mostly due to the lack of content indexing. Search can be a powerful utility for Gists but it needs some indexing refinement yet.&lt;/p&gt;

&lt;h3 id=&quot;advanced-operators&quot;&gt;Advanced operators&lt;/h3&gt;

&lt;p&gt;Relevant but basic search is a must-have for most users. Search with filtering and other operators is a must-have for power users. For instance, filtering by owner is a great way to quickly list the gists you’ve starred by others. I like this implemented with the &lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt; prefix notation and use it frequently:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/081v1N2W0n2C2i3r1t2g/Image%202012-12-12%20at%208.18.31%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The new Gist doesn’t seem to have filtering or any advanced features like phrases (&lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;exactly this&quot;&lt;/code&gt;) or operators (&lt;code class=&quot;highlighter-rouge&quot;&gt;AND&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;-&lt;/code&gt;). These tend to be built-in features of any search index so I imagine Github is easing into search and will turn these on once they feel comfortable with the infrastructure.&lt;/p&gt;

&lt;h2 id=&quot;lists&quot;&gt;Lists&lt;/h2&gt;

&lt;p&gt;In the old Gist you really only had one way to view your gists. In a list of your created gists or your starred gists, ordered by when they were created. This was incredibly limiting. The new gist ushers in several refinements including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The ability to sort gists by their updated date. However, this value is not sticky so I find myself always having to select it when I just want it as my default.&lt;/li&gt;
  &lt;li&gt;A much better partial rendering of each gist in the list, allowing you see more of the gist to know which one is the one you’re looking for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are just a few of the things that make using Gist a little more enjoyable than the old interface.&lt;/p&gt;

&lt;h2 id=&quot;revisions&quot;&gt;Revisions&lt;/h2&gt;

&lt;p&gt;Although gists have always been backed by a full git repo you didn’t see much of that benefit in the web UI. You had to clone the repo locally to see version diffs and manually fetch other remotes to compare and merge forked versions.&lt;/p&gt;

&lt;p&gt;The new Gist takes a small step to solving this puzzle, allowing you to view diffs between your gist’s revisions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/1W2O1A3D3W1b322x2R1J/Image%202012-12-12%20at%208.47.55%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, it still doesn’t provide an easy way to view diffs or perform merges across forks. These are key collaboration features that would remove significant friction from using gists now. I can only hope Github is seriously thinking about enhancing this aspect of the product.&lt;/p&gt;

&lt;h2 id=&quot;content-focus&quot;&gt;Content focus&lt;/h2&gt;

&lt;p&gt;The new Gist has a somewhat awkward focus on the gist files rather than the descriptions. I say awkward because, while I appreciate the direction of putting the content front and center, there are some artifacts that betray the intent.&lt;/p&gt;

&lt;p&gt;For instance, when viewing a gist in a list it’s the filename of the gist that gets top billing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://f.cl.ly/items/242h231D36362b0C2J2c/Image%202012-12-12%20at%208.57.37%20PM.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, gists can have multiple files making it an odd decision to choose only one (the first) to key off of.&lt;/p&gt;

&lt;p&gt;Additionally, if files/content are the focus, they should be a first-class citizen in search but are instead ignored (as previously discussed).&lt;/p&gt;

&lt;h2 id=&quot;still-missing&quot;&gt;Still missing&lt;/h2&gt;

&lt;p&gt;Some miscellaneous features I was hoping would be added in the new Gist include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A resurrection of comment notifications! &lt;a href=&quot;https://gistedapp.herokuapp.com&quot;&gt;Gisted&lt;/a&gt; will do this for you, but it really should be a natively supported feature.&lt;/li&gt;
  &lt;li&gt;Markdown editing and rendering parity with Github proper. If you look at your &lt;a href=&quot;https://github.com/rwdaigle/miyagi#miyagi&quot;&gt;standard project README&lt;/a&gt; on Github you’ll notice you can edit the Markdown inline with decent highlighting, preview your content and, when rendered, sections are automatically anchored. Markdown is such a core feature of text-based collaboration that parity here is essential.&lt;/li&gt;
  &lt;li&gt;A de-emphasis of the public gist-stream (now called “Discover Gists”). I just don’t see the value of randomly browsing new gists and think that real estate could be better used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The new Gist is definitely an improvement over the old one. However I find it mostly just polishes existing features and doesn’t directly address some of larger issues.&lt;/p&gt;

&lt;p&gt;I would encourage Github to focus of the main uses of Gist. From my perspective, gists are used mainly as a collaboration tool. While they’re backed by a full git repo, that is mostly an implementation detail. Commenting, managing collaborator modifications, and finding gists across several sources should be well supported use-cases.&lt;/p&gt;

&lt;p&gt;I suspect we’ll see the pace of development on Gist quicken now that a new codebase is in place. Removing technical debt often removes roadblocks that may have prevented a product from evolving. I can only hope if I revisit this post several months from now I’ll have to significantly edit some of my more critical points.&lt;/p&gt;

&lt;p&gt;Github has been incredibly supportive in my use of the &lt;a href=&quot;http://developer.github.com/v3/gists/&quot;&gt;Gist API&lt;/a&gt; and my work with them on this front only reinforces their developer-focused reputation. They’ll get Gist right, it’s just a matter of time.&lt;/p&gt;

&lt;p class=&quot;note&quot;&gt;
Given my dependence on Gist for work I have a vested interest in its success. Any critical points made here were done so only in hopes of seeing it evolve into a better product.
&lt;/p&gt;</content><author><name>ryan</name></author><category term="blog" /><category term="github" /><summary type="html">Gist is an incredible tool by Github for quickly sharing code, text and files. It has syntax highlighting and rendering for a huge number of programming languages including Markdown for text. For many techies, including myself, Gist is an indispensable tool for quickly sharing code and content with coworkers.</summary></entry></feed>