Archive for December, 2012

Timezones, Clojure, and PostgreSQL – What a Combination

Monday, December 17th, 2012

Clojure.jpg

I have spent far more time with this problem today than it deserves. One of the real annoyances with clojure is also a touted strength - the JVM. But that's where I'm having the problems today - in the ugly world of the java.util.Date and it's ilk. When I receive a JSON snippet like the following:

  {
    "division": "cleveland",
    "start_date": "2012-12-07",
    "end_date": "2013-01-07",
    "demand" : []
  }

and need to place it into a simple PostgreSQL table:

  (:require [clojure.java.jdbc :as sql])
 
  (sql/create_table "demand_sets"
                    [:id :uuid "PRIMARY KEY DEFAULT uuid_generate_v4()"]
                    [:division_id :uuid "references divisions (id)"]
                    [:source_id :uuid "references sources (id)"]
                    [:valid_from :timestamp]
                    [:valid_to :timestamp]
                    [:loaded_at :timestamp "DEFAULT now()"])

One would expect that we'd convert the string into a valid Java Timestamp with something like clj-time:

  (defn get-demand-set
    [division]
    (let [demand-set (client/extract division)
          start-date (:start_date demand_set)
          end-date (:end_date demand_set)
          demands (:demand demand_set)]
      {:valid_from (to-date-time start-date)
       :valid_to (to-date-time end-date)
       :demand (map transform demands)}))

and, in fact, we get nice Java Date objects that are the correct time in the UTC (Zulu) time zone. This all makes sense.

Where it gets squirrley is the saving and reloading from a PostgreSQL database. The database table that was created in the above statement has the timezone in the fields, and there's a defined timezone for each instance of PostgreSQL - right there in the config file. I had my local database set to my local box - US/Central, and that was part of the problem. I wanted to see the dates in my database as I was seeing them in the JSON, and that's not realistic. I needed to understand that I'd really be looking at "2012-12-06 18:00:00" for the start-date, as that's the US/Central time of 2012-12-07 00:00:00 UTC.

Then I had to hassle with getting them out and comparing them correctly. This is really where this whole problem started - comparing dates where they formatted correctly, but were just off by a timezone or offset of some kind.

To do that, it turns out I needed to have something that would get me a single demand_set record:

  (defn find-demand-set
    [demand-set-id]
    (select-1 demand-sets
              (where {:id [= demand-set-id]})))

and here, we're using korma for the entity mapping and selecting. With this we can then get a demand set and extract out the Timestamp fields, but I need to make sure that I convert them properly:

  (defn fetch-demand-set
    [demand-set-id]
    (let [demand-set-rec (db/find-demand-set demand-set-id)
          ;; …more here
         ]
      {:valid_from (from-date (:valid_from demand-set-red))
       :valid_to (from-date (:valid_to demand-set-rec))
       ;; …more here
      }))

And then we can safely do something like this:

  (defn demand-sets-equal?
    [ds1 ds2]
    (and (= (:valid_from ds1) (:valid_from ds2))
         (= (:valid_to ds1) (:valid_to ds2))
         ;; …more stuff here
         ))

and the timestamps will properly compare.

But don't expect them to look like they did in the JSON unless you have the PostgreSQL databases set up on UTC time. Thankfully, that's what we have in the datacenter, so I don't have to worry about that. The only place I have to worry is on my laptop, and that's something I can live with for now.

There is one alternative I could have chosen. That's to have used the TIMESTAMP WITHOUT TIME ZONE field in the PostgreSQL table. I think that would have worked as well, and it would have shown the same value in all databases, regardless of the individual time zone setting. But then we would loose an important point in the time, and I wasn't ready to do that yet.

Still, to have spent several hours on this was exhausting

Back in the SQL Saddle Again

Monday, December 17th, 2012

Dark Magic Demand

Dark Magic has a PostgreSQL back-end, and I have to say that it's very nice to be back in the land of SQL after having been fighting with CouchDB and it's problems recently. If Couch just didn't fall over on us and refuse connections, then I'd probably be a little more inclined to use it, but this is getting a little out of hand.

Anyway… I am back in PostgreSQL, and it's great. This morning I just whipped up a little view that didn't take 24 hours to generate, and gave me the latest uploads by division and source that have gone into the database:

  CREATE OR REPLACE VIEW latest_uploads AS
    SELECT d.permalink, s.name, ds.loaded_at
      FROM demand_sets ds, divisions d, SOURCE s,
           ( SELECT source_id, division_id, MAX(loaded_at) AS loaded_at
               FROM demand_sets
             GROUP BY source_id, demand_id ) latest
     WHERE ds.loaded_at = latest.loaded_at
       AND ds.source_id = latest.source_id
       AND ds.division_id = latest.division_id
       AND d.id = ds.division_id
       AND s.id = ds.source_id

After this, I can simply query this view and get the latest of each of the divisions and sources and the time it was loaded. Very nice debugging tool.

More importantly, it's SQL, and that's so much easier to deal with for stable schemas.

Corrected Major Dark Magic Problem – Valid Dates

Sunday, December 16th, 2012

bug.gif

As I was trying to test the demand from Dark Magic in the Quantum Lead app, I noticed that I was getting a nasty parsing error - the dreaded UTF-8 encoding. Argh! I looked up what I might be able to do, and the solution seemed pretty simple. In the clojure code where we make the response from Dark Magic, we needed to make sure to set the charset in the headers:

  (defn return-json
    [ob]
    {:status 200
     :headers {"Content-Type" "application/json; charset=UTF-8"}
     :body (json/generate-string ob)})

But still, when I ran this code, I didn't get the JSON decoding error in Ruby, but I also didn't get any data. There must be something wrong! And sure, enough, there was.

No dates.

The pipeline code expected to see a start_date and end_date in the demand set to know if this data is valid for the current running date/time. If not, then it doesn't use it. I looked at the code in Dark Magic, and sure enough - we were throwing away the dates we were getting from the original source. I guess my co-worker who wrote this didn't hear me say those were important. (Hint: he did, he's just not disciplined)

So today I figured out enough clojure to change the code and get all these things into the system. That means new fields in the database, new insert methods, extract methods, comparison changes. Lots of things had to change, but it was worth it, because when I was done, it was working with the encoding and all the dates.

It started out very frustrating, but ended up being a great learning experience.

Feeling the Pain of Developing a System in Clojure

Sunday, December 16th, 2012

Clojure.jpg

I want to like clojure - I really do. Erlang was interesting, and it taught me a lot about functional programming - not that I'm really all that good now, but in Erlang, there were a lot of things that I found very hard. One was state - that was all in the arguments. The other was error messages. Man, those error dumps could go on for pages, and finding the first one that really was the source of the problem was a trick. But once you knew what to look for, and found it, then you had a fighting chance to figure out what you did wrong.

I'm learning that with clojure, it's not always that easy. After all, clojure is just java byte code, right? And with all the functional components written in classes in the compiler, it's easy to understand why they have classes that make no sense to a human reader.

OK, maybe to some human readers, but not to beginning clojure coders.

I came across this today and it's really pretty shocking.

Stack traces 50, 60, 100 lines long and not a clue in the lot of it where the problem is. Now I'm not saying it was hard to find… I've looked in more Java stack traces than I can count. I'm saying that every level in that stack trace was nothing in my code. Not a single thing. Certainly not by the unknown name munging that clojure uses.

So I'm left just commenting out sections of code hoping to find the ones that are causing the problem. In functional code, that's not easy as it's all nested within itself in the source file, and commenting out a section is really quite a little adventure to make sure that you get all the parens in the right places.

This is a serious problem in my mind. How long do you have to work with clojure to understand the name munging? Six months? A year? If that's the case, then there's no way a new developer on a team can do any fire-fighting, it's too stressful, and the only people to do it are the senior debs on the team.

That might not be bad, but our senior clojure dev is not a workaholic, so having him fire-fight anything before 9:00 am and after 5:00 pm is a dicey proposition. It makes me very nervous.

It makes me want to drop it and use something else.

Got Dark Magic v0 Deployed to Prod

Saturday, December 15th, 2012

Dark Magic Demand

Today I was able to get Dark Magic, our new Demand Machine (DM) that will be the place where our system goes to get the varied demands for the services that we'll mate up with the merchant info data from Salesforce. It's not a lot, right now. It's really only cacheing the data from our current only source of Demand, but it's now in our control. We don't have to worry about their system going down, or not having data, or having bad dates, etc. It's all in our hands.

True, it's only production now. I need to make sure my co-worker is done with the UAT machines, and then I can spin it up there as well. But hey… it's a big step! I'm glad it's running.

More Work on the Demand Service

Saturday, December 15th, 2012

Building Great Code

Today I did more work with the Demand Service - trying to get it to the point that it's reading in data from the external source and vending it out. Most of that time has been spent with the database schema trying to get it all worked out. For the most part, we're using korma, and that defines the tables and other DDE. So there was a lot of running the migration, checking the tables, making sure it'll hold what we need, talking about the effects, and repeating.

Not a bad day, and it needed to be done, but not exactly something you write a lot about, either.

Updated Dash/Hashie Properties – Sore Subject

Friday, December 14th, 2012

Code Monkeys

I'm pretty sure I've ranted about this already, but once again today, it has been thrust in my face by the Code Monkeys that I work with. We start out the application design with a Hash. It's a wonderfully simple, flexible, data storage tool, and since we don't know all that we need, it makes far more sense to use it than enumerated ivars. So far, so good.

Then we pull in Hashie. This gives us "dotted methods" for all the keys in the hash. This includes the key? methods - one per key, that we can use as opposed to saying: m['key'].nil?. It's a win, and I love it. It does this without any restrictions or set-up on our part. I love it. So far, even better.

Then some in the group decide that we need to switch to a formatting/declarative version of the Hashie. Now if you want to use a value in the hash, you have to enumerate it in the class with a property keyword. All of a sudden, I'm not happy, and I say why I think this is a bad idea.

Up comes the ugly spectra of weak management and strong willed co-workers and Agile. They decide to re-write it even though we didn't all agree to it. Kinda bites, in my book. But I guess someday that'll work for me when I make unilateral decisions, and since I'm willing to work more hours, I'll get more of those opportunities. If I were a jerk.

So now, we have error messages and the code doesn't work because the incoming data source added a field and the code didn't. Had we left it alone, it'd be fine and we'd be working just as you expect. But because of this decision, we have production issues.

Where's the win, Boys?!

So I had to add some properties to the class just to make it not error. Yeah, this is good work guys.

Added Direct Deployment of Couch Design Docs

Thursday, December 13th, 2012

CouchDB

One of the problems with Couch is that when you change or create a view in Couch, it has to rebuild/reindex the entire view by running the map function on all the documents in the database. This sounds very reasonable because like any database, it needs to maintain it's indexes, and this is how it does this.

The problem is that when you're doing this, the view is completely unavailable unless you want stale data. Not really ideal, but again, you can see why it's implemented this way. It's possible to see the old view, but that's stale, or you can wait for the new one. Your pick.

In order to make this easier on our environments, one of my co-workers came up with the idea that if you deploy the new view in a different document, and then after it's done being built, you rename it to the one you want, there's no second rebuild. The rename is nearly instant, and everything is OK. He built something so that when we deploy to the UAT and Production Couch DBs, we deploy in these "temp" spaces, and then there's a crontab job that sees if the rebuilds are done, and moves things in.

Well… that's great for UAT and Prod, but for dev, I don't want the cron job - I just want to have a direct-deploy scheme where I can wait the two minutes to rebuild my (much smaller) database. So I added that into the rake task, and was then able to deploy my changes to dev first, and see that they were working just fine, and then to deploy them to UAT and Prod and let them wait.

The reason for all this was that the views in the Pinnings design document were out of date - people had changed the code and not updated the views, so that they weren't picking up the right documents as they were supposed to. Just not disciplined about what they were doing, I suppose.

Simplified Test and Fixed Bug at the Same Time

Wednesday, December 12th, 2012

bug.gif

One of my co-workers brought up a bug that I hadn't noticed up to now - one of the test methods - that is, a method that returns a boolean about the Merchant argument, was not working, and there was a far simpler way to implement it. Basically, when we were dealing with Hashes in the data structures, it was as efficient a way to handle the problem - modulo the bug, as we could have. But now that we have the Merchant and within it, the Opportunity objects, with their own boolean methods, this implementation really became - ask the Merchant to ask it's Opportunity if it's a live deal.

Far simpler in the code.

Plus, at the same time, we're doing the right test because when I coded that one up, I did it right. Go figure.

In any case, a simple fix, and we reduced the lines of code. Not bad.

Fixed More Production Problems

Wednesday, December 12th, 2012

bug.gif

For the second morning in a row, I had issues with production. Thankfully, this bug didn't effect any of the production data - only that data that was supposed to be written to Couch. So in a sense, it was bad, but not worthy of a re-run. The bug was pretty simple, and for once, I'm glad it wasn't me. The original code was:

  def to_augmented_hash
    to_hash.delete('accounts').merge({
      :category_counts        => category_counts,
      :number_of_new_accounts => number_of_new_accounts
    })
  end

where we were getting a NilClass error on the merge call. I had remembered reading that the delete() method would return a nil under some cases, so I looked it up again, and sure enough - it only returns the value of the key removed. What needed to happen was:

  def to_augmented_hash
    to_hash.delete_if { |k,v| k == 'accounts' }.merge({
      :category_counts        => category_counts,
      :number_of_new_accounts => number_of_new_accounts
    })
  end

With this in, it all worked just fine. When I went to check it in, I saw that a co-worker also fixed this, but split it out on several lines, and mutated the value with the delete(). I decided to leave mine in as it was cleaner, and more to the way it would have been re-written by someone else anyway.