Archive for January, 2019

The Big Chill is on the Way

Tuesday, January 29th, 2019

The forecast is saying that with the 6"+ of snow on the ground, we are going to be getting a once in 25 year cold spell. It's about 1 deg now, and heading down to something like -23 deg tonight. A good friend in Alabama sent me a picture of his driveway with the comment "Not [sure] I can handle all the snow" - and it looked like a September day.

So I took a picture out my front door, and sent it to him:

Before The Big Chill

and we giggled about the difference.

He's from Northern Indiana, so he remembers this all too well... and I don't mind it, so we can share a laugh about the fact that he's there, I'm here, the cold is coming, but we have both had the wonderful experience of walking in the Chicago Winter with the wind chill in excess of -30 deg - and lived to tell the tale.

This is why we live in the Midwest. Not because it's easy, but because it is hard.

UPDATE: went out a few hours later, and shoveled off the snow that fell last night. It was chilly... I had forgotten the bite of the cold wind. It was something I always want to remember... always.

Mega Update Morning

Wednesday, January 23rd, 2019

Software Update

This morning, actually, last night, Apple released macOS 10.14.3, iOS 12.1.3, and tvOS 12.1.2 - and so I had a good morning updating my devices. It always puts me in a good mood to update machines... it's a fresh start... a new beginning, and it makes me smile.

I am glad to have updated to the latest Apple TV because the TV app is really a great thing for keeping track of the shows that I'm watching on Netflix and Hulu - all in one place. And it goes to my iPhone just in case I'd like to watch on the train home.

All updated and ready to take on the day!

Really Enjoying BeatsX and AirPods

Tuesday, January 22nd, 2019

Apple Computers

I got a pair of the AirPods from the first week they were launched. Amazing devices. But the fit wasn't as good as I'd hoped, so I kept using my Apple wired in-ear headphones at the office because they fit better and blocked out a lot more noise. So a few months ago, I got the BeatsX headphones from the Apple Store. Wow... very nice.

The charge is about like my AirPods, and now that I need a Lightening cable around to charge headsets, the AirPods are easier for me to charge as well. Bonus!

They are amazingly easy to use, can be controlled from my iPhone, or Mac - as long as it's logged into iCloud, and the mic/control fob is just as easy to use as the one on my wired headphones. Super simple... super easy... amazingly convenient to ditch the cord.

I have moved into the cordless world, and I have to say, I don't mind it at all. Up next: wireless charging of my iPhone...

Watching Timeless on Hulu

Sunday, January 20th, 2019

TV.jpg

I've recently started watching Timeless on Hulu, as I know it has only two seasons, and I saw an interview one of the stars of the show did, and she was surprisingly impressive when talking about the show. Now, this could be like Bill & Ted's Excellent Adventure - which was a pretty funny movie, and treated time travel in a very good, funny way, but there have been tons of times when the science gets in the way, and things really don't end up working out so well.

I've been really amazed at the writing in Timeless. First, it doesn't try to explain too much - which is good, and it's got just enough rules - born out, they say, by experimental evidence that didn't go well. In all, I give the science a passing grade. Nicely done.

And then there's the premise... kinda like an evil Carmen SanDiego... which means there is a lot of hidden learning in the episodes. Also, as with the Back to the Future movies, changes in the past, effect the timeline they return to, but don't effect them. It's really a well written show.

It's the team that makes me smile the most... both the bad guys, and the good guys. There are a lot of things happening in the show, and while I'm only up to the sixth episode, they way in which secrets and motivations are handled is a very good story. I really like that.

It'll be interesting to see how the two seasons play out. I'm hoping they keep it up.

Creating a Demo Movie

Thursday, January 17th, 2019

iMovie.jpg

This week has been a Hackathon at The Shop, and I was asked to do one on a cross-domain, secure, component-based Web App Framework based on the work done at PayPal in the kraken.js team. It was a steep learning curve for me and the others on the Hackathon team, as none of us had any real experience with Node.js or React, but we had only this week to get something going.

The good news is that we got everything we needed to get running late yesterday, and today I started work on the Demo presentation which happens tomorrow, but it's videos, each group submitting one video. The only limitation is that the video has to be less than 5 min in length - and that's a hard limit I'm told.

OK... so I was looking at all the screen capture tools on the App Store, and some of them looked pretty decent, but the good one was $250, and while I might go that high - I wanted it to be amazing for $250... like OmniGraffle good. And I saw a lot of really iffy reviews. So that didn't seem like the way to go.

Due to the fact that I needed to be able to add in slides and screen grabs, I knew I needed more than a simple "start recording" that Google Hangouts does, and with nothing really obvious in the App Store or in Google searches... well... I hit up a few of my friends in production of stuff like this. Funny thing... they said "QuickTime Player and iMovie"

This really blew me away... I mean I knew about iMovie, but I didn't know that QuickTime Player did screen recordings - and with a selectable region on the screen. And that it also did auto recordings - again, I'm going to need to be able to do voice-overs on the slides and things happening on the screen in the demo.

So I started recording clips. Keynote was nice in that I could make all the slides there, and export them as JPEG files, and they imported perfectly into iMovie. Then I could put them in the timeline for exactly how long I needed them, and do any transitions I needed to make it look nice.

Then I went into a little phone booth we have at The Shop, and recorded the audio with very little background noise. I could then re-record the audio clips as needed to make it all fit in the 5 min hard limit. In the end, I could export the final movie, and upload it to the Google Drive for the submissions.

Don't get me wrong... there was a steep learning curve for iMovie for about an hour. How to move, select, add things, remove things... not obvious, but with a little searching and experimenting, I got the hang of the essentials. And honestly, that's all I needed to finish this Demo video.

I was totally blown away in the end. I was able to really put something nice together with a minimum of fuss, and now that I have the essentials in-hand, it'll be far easier next time. Just amazingly powerful tools from Apple - all installed on each new Mac. If only more people knew...

When is a Free Lunch not Free?

Tuesday, January 15th, 2019

cow.jpg

I'm supportive of places that provide perks like free lunch, shuttle trips to mass transit, things that can really nickel-and-dime employees, and aren't that horrible, but are nice perks. Just nice. Today at The Shop the lunch was... well... I know several folks that liked it, and thought it was really good. But it was way too cheesy for me.

I've been told that I'd never last in Wisconsin, and I have to agree... I probably would draw more than a few odd looks for my love of milk and ice cream, but not cheese. Just no thanks.

So today's free lunch wasn't free to me. But they did have the Honey Nut Cheerios that I grabbed a fist-flu of to cleanse my palette after the cheese.

Simple Immutable Data in Postgres

Monday, January 14th, 2019

PostgreSQL.jpg

A friend asked me today for the trick I've used in the past to make sure that the data in a database is immutable and versioned. This is nice because it matches what Clojure acts like, internally, and it makes it easy to see who's doing what, when. Assuming we start with a table - for the sake of argument, let's say it's customers, and looks something like:

  CREATE TABLE IF NOT EXISTS customer (
    id             uuid NOT NULL,
    version        INTEGER NOT NULL,
    as_of          TIMESTAMP WITH TIME zone NOT NULL,
    last_name      VARCHAR,
    first_name     VARCHAR,
    PRIMARY KEY (id, version, as_of)
  );

where the first three fields are really the key ones for any table to work with this scheme. You need to have a unique key, and in this case, the easiest I've found is a UUID, and then you need a version and a timestamp when that change was made. What's left in the table is really not important here, but it's something.

You then need to make an Audit table that has the same table structure, but has the string _audit added to the name:

  CREATE TABLE IF NOT EXISTS customer_audit LIKE customer including ALL;

What we then need to create is the following trigger that intercepts the INSERT and UPDATE commands on the customers table, and places the historical data into the Audit table, and the most recent version of the customer data is always kept in the customer table.

The trigger looks like this:

  --
  -- create the trigger on INSERT and UPDATE to update the version if it's
  -- not provided, and to maintain all versions in the audit table, but have
  -- the current version in the non-audit table. Importantly, NOTHING is
  -- deleted.
  --
  CREATE OR REPLACE FUNCTION audit_customer()
  RETURNS TRIGGER AS $body$
  DECLARE
    ver INTEGER;
  BEGIN
    -- get the advisory lock on this id
    perform pg_advisory_xact_lock(('x' || translate(LEFT(NEW.id::text, 18),
                                  '-', ''))::bit(64)::BIGINT);
 
    -- get the max of the existing version for the data now
    SELECT MAX(version) INTO ver
      FROM customer_audit
     WHERE id = NEW.id;
    -- and bump it up one and use that
    IF ver IS NULL THEN
      NEW.version := 1;
    ELSE
      NEW.version := ver + 1;
    END IF;
 
    -- if an update, then we need to insert the new
    IF tg_op = 'UPDATE' THEN
      -- now let's insert the old row into the audit table
      INSERT INTO customer_audit
        VALUES (NEW.*);
    elsif tg_op = 'INSERT' THEN
      -- now let's insert the new row into the audit table
      INSERT INTO customer_audit
        VALUES (NEW.*);
      -- and delete the old one in the customer table
      DELETE FROM customer
        WHERE id = NEW.id
          AND version <= ver;
    END IF;
 
    -- finally, return the row to be inserted to customer
    RETURN NEW;
  END
  $body$ LANGUAGE plpgsql;
 
  CREATE TRIGGER set_version BEFORE INSERT OR UPDATE ON customer
    FOR each ROW EXECUTE PROCEDURE audit_customer();

At this point, we can INSERT or UPDATE on customers and the previous version of that customer will be mmoved to the Audit table, and the the most recent version will be held in the customers table.

I have found this very useful, and I've put it in a gist for easy access.

The point of:

    -- get the advisory lock on this id
    perform pg_advisory_xact_lock(('x' || translate(LEFT(NEW.id::text, 18),
                                  '-', ''))::bit(64)::BIGINT);

is to get an exclusive lock on the data for a given id. This is necessary to make sure that updates from multiple services get serialized on the same data. This scheme can't ensure that there are merged changes - only that there is a sequence of changes to the table, and each one is entirely correct for the time it was entered.

So... what happens if you have a string as the primary key, and not a UUID? Well, use can use the MD5 checksum of the string as the lock indicator:

    -- get the advisory lock on a general string
    perform pg_advisory_xact_lock(('x' || md5(NEW.wiggle::VARCHAR))::bit(64)::BIGINT);

where the field wiggle is a varchar, and here, we are just computing the MD5, and using that as the basis of the lock. Yes, there could be some hash collisions, but that's likely not a huge performance problem, and it's conservative in that we'll over-lock, and not under-lock.

UPDATE: a friend asked about using an int as the primary key, and in that case, the line would be:

    -- get the advisory lock on a general string
    perform pg_advisory_xact_lock(NEW.id::BIGINT);

where the column id is an int. Again, we just need to get it to the bigint for the advisory lock call. After that, Postgres does the rest.