It was the best of times, it was the worst of times…

June 30th, 2014

cubeLifeView.gif

It's been a heck of a month, and I've adopted Dickens' line from A Tale of Two Cities as it seems to fit my life amazingly well these days. I'm trying to adapt to the current environment at The Shop, but it's proving increasingly difficult the more I attempt to adapt, and in the end I'm left with the feeling that it's an abusive relationship where the more I attempt to adapt, the more I'm being exploited.

The work can be fun, and it's one of the two things I'd classify in the ... best of times... category. But there's very little real work, and far more people posturing and pretending and trying to get me to do their work for them. In most cases, I accept the work because it is, after all, the best of times, but then it becomes an abusive relationship when these people expect me to do their work for them, and get angry when I can't - or won't. I've enabled them to be poor producers in this environment, and by enabling that behavior, I've done them no favors.

Management is not supporting me because they are the ultimate benefactors of all the work I do, so the more I do, the more they like it. It's a very simple equation. I also have a manager that seems to be completely incapable of delivering any kind of bad news, so instead, he convinces himself that the right thing is to adjust his expectations, and have me to their work. If they then become happy, and all the work gets done (by me), then all the better. No more reason to deliver bad news.

But it's not healthy. It's not sustainable. And I'm getting to the point that I'm just done with this particular unhealthy relationship, and it's time to move on. I like the company, but not the people I'm working with. There are people here at The Shop that I'd like to work with, have in the past, and would enjoy doing so again. There are even people in this group that are decent workers. But the vast majority are not. And I've enabled their bad behavior for too long.

It's time to make them stand on their own two feet. That's the only way they will get better. And they need to know if they can get better, or if it's time to change jobs. That is what we all should have to do, but I've been keeping them from this by picking up all the group's work. Like I said, I wasn't doing them any real favors. I was doing myself a favor by keeping my mind active.

It's time to make some changes.

PHP on Heroku

May 1st, 2014

php.jpg

I'm looking at standing up an XML-RPC service written in PHP on the web, and I've been doing a lot of looking around, and found that Heroku really is about the best place for us at this point in time. It's got all the scalability we need, it's got Postgres, it's got redis, it's got all the things to mix-and-match all the work I need to do, and it's all in one place. I couldn't think of a better place.

But they haven't been doing PHP... until now.

This post talks about it, and it's really exceptionally simple... Start with a new directory and a simple index.php file:

  <?php
    echo "Hello World!";
  ?>

and in the same directory simply create a blank composer.json file:

  $ touch composer.json

Finally, create the git repo, commit the new files, create the heroku app, and deploy:

  $ git init
  Initialized empty Git repository in ~/hello_heroku_php/.git/
  $ git add .
  $ git commit -m "Initial import of Hello Heroku"
  [master (root-commit) 06ba0a7] Initial import of Hello Heroku
   2 files changed, 5 insertions(+)
   create mode 100644 composer.json
   create mode 100644 index.php
  $ heroku create 
  Creating safe-ridge-5356... done, stack is cedar
  http://safe-ridge-5356.herokuapp.com/ | git@heroku.com:safe-ridge-5356.git
  $ git push heroku master
  ...
  $ heroku open

This is amazingly simple! Just what I needed.

Secrets of a Great Bag

April 23rd, 2014

Chrome Bag

This morning I was looking in my bag for something and I noticed that there was a little fold in the side of the bag, and upon investigation, I saw that it was a completely separate pocket in the bag! It was thin - in that it wasn't in the same area as the laptop sleeve - it was parallel to it. This is a perfect place for papers and simple things - leaving the larger pocket for the laptop and thick items.

I was so jazzed I couldn't help but smile. Yes, this is the most amazing bag I've had. Wonderful piece of work. I just can't say enough about this bag.

Getting PostgreSQL 9.3 Support into PHP 5.4.24 on Mac OS X 10.9

April 21st, 2014

PostgreSQL.jpg

A while back I wrote about getting PostgreSQL support into PHP as it was packaged with Mac OS X 10.6. Since then, a lot has happened with Mac OS X, and PostgreSQL, and I find that I'm once again in need of developing PHP and PostgreSQL on my MacBook Pro. So I wanted to refresh this list and make it a little simpler at the same time.

Getting PostgreSQL 9.3

The first big difference in this post is that I'm switching to Homebrew and that's made the entire operation a lot smoother. There's no 32-bit/64-bit issue as Homebrew does both, and it builds in on your box, so again, a lovely customized solution with a simple command:

$ brew install postgresql

It even has a nice, simple informational blurb about how to start/stop and upgrade. Very nice. But now that it's on the box, and ready to roll, let's add in the PostgreSQL support to the PHP 5.4.24 that's installed with Mac OS X 10.9.

Activating PHP in Apache

The first thing to do is to activate PHP in the supplied Apache 2 with OS X 10.9. This is a single line in a single file - /etc/apache2/httpd.conf. There's a line and you need to uncomment it:

  LoadModule php5_module libexec/apache2/libphp5.so

and then add a new file called /etc/apache2/other/php5.conf and have it contain:

  <IfModule php5_module>
    AddType application/x-httpd-php .php
    AddType application/x-httpd-php-source .phps
 
    <IfModule dir_module>
      DirectoryIndex index.html index.php
    </IfModule>
  </IfModule>

which does all the other PHP configuration in a separate file to make upgrades easy.

Building in PostgreSQL extra for PHP

At this point we need to get the source code for the exact version of PHP that Apple ships. This is pretty easy by getting the PHP 5.4.24 source from a mirror and unpacking it into a directory. We then just run the following commands:

  $ cd php-5.4.24/ext/pgsql/
  $ phpize
  $ ./configure
  $ make

at the end of this process, you'll have a file: php-5.4.24/ext/pgsql/.libs/pgsql.so and that needs to be placed in the right directory and referenced in the php.ini file.

For Mac OS X 10.9.2, this is accomplished with:

  $ sudo cp .libs/pgsql.so /usr/lib/php/extensions/no-debug-non-zts-20100525/

and then edit the php.ini file to set the extension directory:

  extension_dir=/usr/lib/php/extensions/no-debug-non-zts-20100525/

and then add the line:

  extension=pgsql.so

Finishing Up

At this point, a simple restart of apache:

  $ sudo apachectl restart

and everything should be in order. Hit a URL that's a static file with the contents:

  <?php
    phpinfo();
  ?>

and you should see all the details about the PHP install - including the PostgreSQL section with the version of Postgres indicated. It's all up and running now.

Starting to Decorate the House

March 31st, 2014

The Chalkboard

This last week, I've taken the first real steps towards decorating the house - adding a slate chalkboard to what was the dining room, but will be known as The Pub when it's done. My designer came up with the idea of putting a pub table and two chairs as well as a pool table in the dining room, and I really like it. So this is the first step:

I know it doesn't look like much, but it's a really big deal to me. It's a big step in what I'm trying to do, and it's something I've wanted to have in my house since grad school. It took me almost 30 years, but I did it.

I pray good things come to those that wait... and keep the dream alive.

Bash Safety Tip: Check for Terminal Prompt

February 19th, 2014

Ubuntu Tux

I was having a pretty painful issue with SCP the other day and it took me a few hours to get to the bottom of it, but there's a lesson to be learned here. The problem was that I was able to SSH to a host, but was not able to SCP to that same host. The keys were good, the permissions on the key files were good - and SSH was just fine. It was just SCP.

Finally, I figure out it was my .bashrc file. I had added some functions in there, and they were doing fine with a terminal session, but the SCP "head-less" session was causing it to hang. Horribly. And that's the Safety Tip for the day:

Add this after the alias commands at the top of your .bashrc:

  # skip the rest if we're not interactive
  if [ -z "$PS1" ]; then
      return
  fi

and then you'll have the aliases if you need them, but you won't have the rest that could foul up the SCP session.

Very useful tip. Do it.

Turning a Corner – Getting a New Laptop Bag

February 19th, 2014

Chrome Bag

I've been thinking about a new laptop bag for quite a while. As in more than a year now, and the one I've wanted was a nice successor to my Spire Volt. It's several years old now, and I was thinking that if I got a new retina MacBook Pro, I'd need a new 'boot', as they call it, to hold the new machine. But sadly, Spire is in trouble getting things out, and their selection of bags has really shrunk. Also, the 'boot' for the rMBP is zippered, and I don't like that - I like the velcro that my Volt has. So I went looking.

I worked with a really great guy that had a Chrome messenger bag, and the tweet he sent out one day with a 40 lbs. bag of dog food along with his laptop, shirt, etc. all in his bag made me a believer. It was an impressive amount of stuff.

I find that there are a few times that I really want to carry a good bit of stuff to the office - candy bars for my favorite Clojure guru, work laptop, etc. and the space I have in my Volt is nice, but it was a touch small at times. So I wanted something big.

I also loved the Spire lifetime guarantee, and a solid construction where I felt that the guarantee wasn't going to be needed. I don't treat things badly, but I want to know that I don't have to protect them when their job is protecting everything I have of value. My bag is it. This is my life in a carry-on bag. There's this and my house, and that's my life. So when I have something in my bag, I want to know it's safe.

So I looked at the Chrome messenger bags and the Metropolis looked like what I wanted. Sized for a 17" laptop, but a sleeve for a 15" MacBook Pro... black on black - with a black buckle - just like my all-black Volt... and Chrome even had the phone caddy for my iPhone. It was sweet. But I didn't pull the trigger on it. I was waiting. For what, I don't know but I think it was today.

Right now, I'm carrying two laptops every day to work - mine, a 17" MacBook Pro, and a work 15" retina MacBook Pro. I have to say, the 15" rMBP has convinced me to downsize, as it were, and yet I have to carry two because I have my life on this laptop, but work is work, and there's a reason they are separate. What I needed was a machine at home that I could set-up as a work machine and just keep work stuff on it, but not carry it back and forth.

So if I got a new 15" rMBP for my laptop, and put my work account on my 17", then I could leave it at home for those times I got the wake-up call, and not have to carry my work 15" rMBP back and forth. Sure, it means buying a new laptop, but that's not such a horrible thing, now is it?

So this morning I decided to get the bag - sleeve, phone case and all. Ordered it and it's on it's way to me. I still need to order the laptop, but that's no big deal, the hard part was convincing myself that it was the right time to make the change. Switch up and go messenger bag and move to a new laptop. The rest is just timing.

I'm starting to feel like I have a little more control in my life. It's not as bleak as it used to me. It's a good feeling.

Looking Back at My Most Painful Year

February 3rd, 2014

On the weekend, I passed a sad anniversary - one year ago, my wife of 27 years told me to leave. Said she never really loved me - that I was just a better alternative than moving back in with her parents after college. I believed her. Still do. It's been a hard year, a few months in I decided not to describe it as "My Most Painful Year", and instead take a term from Neil Simon, and call it Chapter Two. It didn't help. Still doesn't.

My term was accurate, and it still is. I've tried several times to find that joy that made we want to post about all the interesting things I was doing with technology. All the fun I was having - and even ranting a bit here and there about the crazy things companies do when they think no one's looking. But it's really been hit and miss. I'm lucky to force myself to post something once a month.

Even this post - I should be able to rattle off a year's worth of stories looking back on this year, but I find I have no interest in doing that. What does it matter? It's not going to make me feel any better, I'm not through this, I'm still in the middle of it. That's not the time to look back and try to gather the lessons learned. It's the time to get out of it.

My friends say that time will make this all better. Maybe it will. I'd like to think that some day I'll wake up and not feel this way, but I have my doubts. I don't see why another year is going to make any difference. The last one really hasn't. I don't feel any better. I just feel a year older.

It's been a bad year. That's about the sum total of what I've learned.

Almost a Year Down

January 31st, 2014

Well... it's almost a year on my own. It's not been a particularly fun year, and I've been lucky to post a simple note a month, but I'm still here... still putting one foot in front of the other, and that's good.

Baby Steps.

Optimizing Redis Storage

January 19th, 2014

Redis Database

[Note: I created this post for The Shop, and while I'm not sure if it'll ever see the light of day, it's useful, and I wanted to see it posted. So here it is.]

Optimizing Redis Storage

The Optimize Group

One of the tasks of the Optimize Team where I work is to build a real-time analytics engine for our A/B testing framework which involves analyzing the consumers experiencing each of the variants on each experiment. Along with this, we need to look at each deal sold in the system and properly attribute each sale to the experiments the consumer visited on their way to that sale that might have influenced their buying decision. Based on this visiting and buying data, the different product teams can then determine which of the experiment variants they want to keep, and which they don’t.

In order to improve any consumer-facing Groupon product, experiments are done where a random sample of consumers will be placed into a testing group and shown one or more variants of the original, control, experience, and then their responses will be tallied. This A/B testing data will come to our cluster in the form of several separate messages. Some will indicate the consumer, browser, and device when an experiment variant is encountered, others will indicate when a consumer purchased a deal. It is then the job of this cluster to correlate the actions taken by that consumer to see if the variant is better than the control. Did the larger image lead to more purchases? Did the location of the button cause more people to click on it? All these experiments need to be classified and the consumer actions attributed.

Recently, several production systems started using Clojure and given that Storm is written primarily in Clojure, it seemed like a very good fit to the problem of real-time processing of messages. There are several topologies in our cluster - one that unifies the format of the incoming data, another enriches it with quasi-static data, and then a simple topology that counts these events based on the contents of the messages. Currently, we’re processing more than 50,000 messages a second, but with Storm we have the ability to easily scale that up as the load increases. What proved to be a challenge was maintaining the shared state as it could not be stored in any one of the bolts as there are 30 instances of it spread out across five machines in the cluster. So we had to have an external shared state.

All of our boxes are located in our datacenter, and because we’re processing real-time data streams, we’re running on bare metal boxes - not VMs. Our tests showed that if we used the traditional Redis persistence option of the time/update limits, a Redis box in our datacenter with 24 cores and 96 GB of RAM was more than capable of handling the load we had from these 30 bolts. In fact, the CPU usage was hovering around a consistent 15% - of one of the 24 cores. Plenty of headroom.

Redis is primarily a key/value store, with the addition of primitive data types including HASH, LIST, and SET to allow a slightly nested structure and operations to the cache. And while it’s ability to recover after a crash with it’s data intact is a valuable step up over Memcached, it really makes you think about how to store data a useful and efficient layout. The initial structure we chose for Redis was pretty simple. We needed to have a Redis SET of all the experiment names that were active. It turns out that there can be many experiments in the codebase, but only some are active. Others may have completed and just haven’t been removed from the code. To support this active list, we had a single key:

	finch|all-experiments => SET (names)

and then for each active experiment name, we had a series of counts: How many consumer interactions have there been with this experiment? How many errors were there on the page when dealing with an experiment? and even a count for the basic errors encountered in the stream itself - each updated with Redis’ atomic INCR function:

	finch|<name>|counts|experiment => INT
	finch|<name>|counts|errors => INT
	finch|<name>|counts|null-b-cookies => INT

The next step was to keep track of all the experiments seen by all the consumers. As mentioned previously, this includes the browser they were using (Chrome 29.0, IE 9.0, etc.), the channel (a.k.a. line of business) the deal is from (Goods, Getaways, etc.), and the name of the variant they experienced. The consumer is represented by their browser ID:

	finch|<name>|tuples => SET of [<browser>|<channel>|<variant>]
	finch|<name>|variant|<browser>|<channel>|<variant> => SET of browserId

The Redis SET of tuples containing the browser name and version, the channel, and the name of the variant they saw was important so that we didn’t have to scan the key set looking for the SETs of browser IDs. This is very important as Redis is very efficient at selecting a value from the key/value set - but it is horribly inefficient if it has to scan all the keys. While this function exists in the Redis command set, it’s also very clearly indicated as not to be used in a production system because of the performance implications.

Finally, we needed to attribute the sales and who bought them, again based on these tuples:

	finch|<name>|orders|<browser>|<channel>|<variant>|orders => INT
	finch|<name>|orders|<browser>|<channel>|<variant>|qty => INT
	finch|<name>|orders|<browser>|<channel>|<variant>|revenue => FLOAT
	finch|<name>|orders|<browser>|<channel>|<variant>|consumers => SET of uuid

As you can see, the lack of nested structures in Redis means a lot needs to be accomplished by how you name your keys, which makes this all appear to be far more complicated than it really is. And at the same time, we have purposefully chosen to use the atomic Redis operations for incrementing values to keep the performance up. Consequently, this may seem like a lot of data to hold in Redis, but it lead to very fast access to the shared state and Redis’ atomic operations meant that we could have all 30 instances of the bolt hitting the same Redis instance and updating the data concurrently. Performance was high, the analytics derived from this data were able to be generated in roughly 5 sec, so the solution seemed to be working perfectly.

Until we had been collecting data for a few days.

The memory usage on our Redis machine seemed to be constantly climbing. First it passed 20 GB, then 40 GB, and then it crashed the 96 GB machine. The problem stemmed from the fact that while an experiment was active we were be accumulating data for it. While the integers weren’t the problem, this one particular SETs was:

	finch|<name>|variant|<browser>|<channel>|<variant> => SET of browserId

There would, over time, be millions of unique visitors, and with more than a hundred active experiments at any one time, and even multiple browserIDs per consumer. Add it all up, and the Redis SET would have hundreds of millions of entries. This would continue to grow as more visitors came to the site and experience the experiments. What we needed was a much more efficient way to store this data.

Wondering what Redis users do when wanting to optimize storage we did some research and found a blog post by the Engineering group at Instagram. We also found a post on the Redis site that reinforces this point and gives tuning parameters for storing efficiently in a HASH. Armed with this knowledge, we set about refactoring our data structures to see what gains we could get.

Our first change was to pull the ‘counts’ into a HASH. Rather than using:

	INCR finch|<name>|counts|experiment
	INCR finch|<name>|counts|errors
	INCR finch|<name>|counts|null-b-cookies

we switched to:

	HINCR finch|<expr-name>|counts experiment
	HINCR finch|<expr-name>|counts errors
	HINCR finch|<expr-name>|counts null-b-cookies

Clearly, we were not the first to go this route as Redis had the equivalent atomic increment commands for HASH entries. It was a very simple task of breaking up the original key and adding the ‘H’ to the command.

Placing the sales in a HASH (except the SET of consumerIDs as they can’t fit within a HASH), was also just a simple breaking up of the key and using HINCR and HINCRBY. Continuing along these lines we saw we could do a similar refactor and we switched from a SET of browserIDs to a HASH where the keys are the browserIDs - just as unique, and we can use the Redis command HKEYS to get the complete list. Going further, we figured we could that values of the new HASH could contain some of the data that was in other structures:

	finch|<browserID> => app-chan => <browser>|<channel>
	finch|<browserID> => trips|<expr-name>|<name_of_variant> => 0

where that zero was just a dummy value for the HASH key.

With this new structure, we can count the unique browserIDs in an experiment by using the Redis EXIST function to see if we have seen this browserID in the form of the above HASH, and if not, then we can increment the number of unique entries as:

	finch|<expr-name>|tuples => <browser>|<channel>|<name_of_variant> => INT

At the same time we get control over the ever-growing set of browserIDs that was filling up Redis in the first place by not keeping the full history of browserIDs, just the count. We realized we could have the browserID expire on a time period and let it get added back in as consumers return to use Groupon. Therefore, we can use the Redis EXPIRE function on the:

	finch|<browserID>

HASH, and then after some pre-defined period of inactivity, the browserID data would just disappear from Redis. This last set of changes - moving away from a SET to a HASH, counting the visits as opposed to counting the members of a SET, and then EXPIRE-ing the data after a time really made the most significant changes to the storage requirements.

So what have we really done? We had a workable solution to our shared state problem using Redis, but the space required was very large and the cost of keeping it working was going to be a lot more hardware. So we researched a bit, read a bit, and learned about the internals of Redis storage. We then did a significant data refactoring of the information in Redis - careful to keep every feature we needed, and whenever possible, reduce the data retained.

The end effect? The Redis CPU usage doubled, which was still very reasonable - about 33% of one core. The Redis storage dropped to 9 GB - less than 1/10th of the original storage. The latency in loading a complete experiment data set rose slightly - about 10% on average, based on the size and duration of the experiment. Everything we liked about Redis: fast, simple, robust, and persistent, we were able to keep. Our new-found understanding of the internals of Redis has enabled us to make it far more efficient. As with any tool, the more you know about it - including its internal workings, the more you will be able to do with it.