tithonium: (Default)
Per Denise's request...

So, I have this Service. One of the endpoints loads basically /all the data/ in the database, constructs a big json object, and send it over the wire.
As of Thursday morning, it was taking 179 seconds to generate that json on my laptop.
As of Thursday evening, it was taking between 4 and 5 seconds.

In /theory/, if you just /look/ at the code, and look at how things were being loaded, you might think it should have been that fast to start with. I was doing everything right, right? Here's what we were doing (massively simplified and slightly redacted)...

class Plan
  scope :with_components, ->{
                              includes(
                                :calendars,
                                :calculators,
                                {
                                  schedules: :tiers,
                                  statements: [
                                    :approvals,
                                    :releases,
                                  ],
                                },
                              )
                            }
end

class ReportController
  def index                          
    plans = Plan.not_deleted.with_components.order(:id)
    render json: PlanReportDecorator.decorate_list(plans).as_json
  end
end


Simple enough. Load all the plans, include all the associations that we're going to use to generate the json, and generate the json. Boom.

Thing is, when you actually generate the json, inside those decorators (and there are several levels of decorators here, but only two matter), it was loading more data from the database. And it would do that, loading counts or sums or individual records, about a dozen queries each, for every statement (dozens), for every plan (hundreds).

All those individual queries, taking fractions of a second each, add up quickly.

But WHY is it making all these queries? We eager-loaded EVERYTHING WE NEED.


Ok, so. Yes, we did. And ActiveRecord is really pretty clever as far as it goes, but sometimes it doesn't go far enough.

Let's take a specific example. Statements can be approved? or not. They are approved if there exists an Approval that has not been revoked. Simple enough accessor:

def approved?
  approvals.not_unapproved.any?
end


And since we eager-loaded the approvals for all statements, this should be crazy fast, right?

Nope. Because we're accessing the not_unapproved scope on our approvals association, it makes a new db query. In theory, when the scope is known to be a simple where(), there's no /real/ reason that AR couldn't translate that into a .select{} on the loaded data, but it doesn't. So, new query. How do we fix that? If we /have/ all the approvals, don't load them again. Just look at what we have. If we do NOT have them, then do this scoped query, as it's the fastest option.

def approved?
  if @approved.nil?
    @approved = approvals.loaded? ? !!approvals.find(&:not_unapproved?) : approvals.not_unapproved.exists?
  end
  @approved
end


Memoize the result in @approved. If we haven't done so, it'll be nil. We can't use ||=, because that will still run when the value is false, we only want to set it when it's undetermined. Check if we have the association loaded. If so, use .find/.detect, using an accessor on the Approvals that implements the same logic as the scope. If we don't have them loaded, use the scoped query, and use .exists? instead of .any?, because it might possibly be ever so faintly faster. (on pg, it's SELECT 1 AS one FROM instead of SELECT count(*) FROM)

Changing things like that cut my load time from three minutes to about 50 seconds. The remainder was calculated sums on other associated records, displaying payment totals and such. It was causing three database queries per statement. Rather than loading those models each time, I added a json column to the database and stored the computed values at the time the statement is processed, and wrote a little caching wrapper I can tie to any method on the model to turn it into a write-once accessor.

def self.cached_detail_methods ; @cached_detail_methods ||= [] ; end
def self.cache_detail(*methods)
  methods = methods.flatten.map(&:to_s)
  methods.each do |method|
    class_eval <<-"EOF"
      def #{method}_with_detail_cache
        return self.detail_cache[#{method.inspect}] if self.detail_cache.has_key? #{method.inspect}
        self.detail_cache[#{method.inspect}] = #{method}_without_detail_cache
      end
    EOF
    alias_method_chain method, :detail_cache
  end
  @cached_detail_methods ||= []
  @cached_detail_methods |= methods
end



Then added a before_save callback that will clear and repopulate the cache anytime we modify the statement (generally when it's being reprocessed).

And that change got rid of the last 45 seconds. At this point, there are less than a dozen database queries, all at the very start, and the json generation itself takes hardly any time at all.
tithonium: (Martian Sunset)
So, let's catch up a bit. Not everything comes thru via twitter.

1) Hives.
Every several years or so (at a guess I'd say 5-7, but I haven't kept track) I break out in hives for 2-4 months. This started in early tweens (10 or 11, I think). We've never been able to figure out a cause. Every time, it's required stronger and stronger antihistamines. After moving up here, I went a long time without any, and thought maybe, whatever the cause, I'd left it behind.

Well, three years ago, I started breaking out again. It turned out to be a reaction to Niaspan, which I was taking in an effort to increase my HDL and drop my triglycerides. Niaspan is basically vitamin B, and high doses of B can cause rashes, hives, etc. I'd had other reactions to it too, including ZOMG MY SKIN IS BURNING, and the hives showed up later. Eventually we figured out the cause, but not before I was taking both Ranitidine and Doxepin. So, ok, good, it wasn't the previous problems, it was a specific known cause, yay.

Well, not yay. About a month and a half ago now, I started breaking out in them again. It started on the hands, as it usually does. Fingers and palms swollen, eventually enough that typing was quite painful. Then moving up the arms, legs, torso, and eventually showing up on the bottom of the feet and the eyelids, which I just /love/, let me tell you.

This time, ranitidine didn't seem to be having any effect. Claratin didn't either. Benedryl didn't. Various combinations of those, at very high doses, had /some/ effect, but not enough. So, back on the Doxepin. Mixing that with Claratin helped, but the doses were still creeping upwards and were higher than we'd like. When I hit 100mg dox plus 60mb claratin per day, the doc decided I might need to go on Prednisone. By the time he decided to go forward with that and they sent in the scrip, I was at 100/120. Now I'm at 100mg dox, 120mg claratin, and 6mg of prednisone per day. Well, no, sorry, 5mg today. Started at 3mg twice a day, reducing the night dose by 1mg/2days until 0, then the morning dose 1mg/2d until 0. So, a 12 day regimen starting at the max dosage. I've taken 15mg so far, and will be at 17 by the time I go to bed.

* Claratin, at worst, just makes me sleepy. At 120mg/day, it makes me VERY sleepy. So I'm tired all the time, and have trouble getting up.
* Doxepin makes me sleepy, to an extent. Not that bad by itself. But the dose I'm taking is high enough that the effects on my neurochemistry are noticable. My dreams have been COMPLETELY FUCKED UP (I'm still annoyed with [livejournal.com profile] loree about the pie machine). I've developed a strong tendency towards talking rapidly and at length. More so than normal, that is. At the same time, I'm even more distractable and unfocused than normal.
* Prednisone makes me irritable and angry. I spent most of thursday actively wanting to hit people for little or no reason. Tiny little things made me want to spin around and throttle the construction guys on the 2nd floor, because whatever it was I just mis-heard him say HAD TO HAVE BEEN ABOUT ME, YOU BASTARD, I'LL KILL YOU!!!!1!!eleventy. It was a little better today.
* Prednisone also fucks up blood sugar. I grabbed a new meter (slightly nicer than the one I had, and $15) and new strips (the ones I had were 2 years expired, and $100!!) and have /actually/ been checking like I'm supposed to. Mostly. Forgot to set a timer after dinner tonight and so didn't get a reading for that. I'm not sure, at this point, whether it's actually making the blood sugar high or just making the blood sugar /readings/ high, because I've been having mild low-sugar reactions, while readings were coming out anywhere from 123 to 186. Also, this is so very much the WRONG TIME OF YEAR for this problem.
* Prednisone ALSO supresses the immune system, so I caught [livejournal.com profile] loree's cold or whatever she had last week. Last night my throat was starting to be itchy. Today it hurt. Tonight I'm starting to get seriously phlegmy. This is a much faster progression than normal, on something I probably wouldn't have even felt if it weren't for the pred.

On the plus side, at the current dose, my hands aren't swollen at all, which I haven't been able to say for about two weeks, and I'm about 95% hive-free. Two or three per limb, roughly. That's worth it, right? Right?



2) Work.
Just moved offices. Am now sharing an office on 8 with the new guy, Todd, and it looks like we're going to be writing services together. I think he's going to be a good influence on the team. I hope, anyway. He's driving pretty hard, tho, and in my fuzz-brained state I'm occasionally feeling a bit overwhelmed. I'm sure it'll get better once I finish getting set up in the new office and can focus on setting up a new dev environment.

My dev server won't boot after the move. "ata1: SRST failed (errno=-16)" Yay. Moday I'm going to pull the drive and plug it into a usb interface and see if I can read it. Maybe pull my scripts and such so I don't have to recreate them, and move everything to a new machine. I rescued the old newsletter server from the recycling pile, for personal use, but using it at work is easier than taking it home.

I also spent some time digging thru the IT recycling pile for server rails. They had lots, but the matched sets were no longer matched. So it took a while to find enough that I could make some hopefully-useful pairs. Now I just need to see if any of them will actually work with the servers I have.


3) Other work
Had a much-delayed burst of productivity on the registration system I'm working on. Finally managed to brow-beat paypal into cooperating with me. Need to go back thru all the optional fields and put back in the ones I had to take out to get it to work, and figure out why it wasn't working. But, it /works/, and that will do for now. Now I need to finish the setup pages, and figure out what the registrars need in their interface. Oh, and printing. But that's fairly easy. I just wish I could /focus/ on it more often.

I want to rewrite invitotron. And sixmore. Refusing to touch those while the registration system isn't completely finished. Nor any of the other billion things I want to work on.
I need some minions.
tithonium: (Default)
Well, I've just successfully created a widget for the chumby using OpenLaszlo.
All I did was hack down their World Clock demo to produce a single clock, compiled to swf7, and got it to run on the chumby. Now I just need to write my /own/ clock code - theirs has a copyright notice attached with a reference to a license which I can't be bothered to go read, and I can have a little Martian clock on my desk.
tithonium: (Default)
At lunch today, [livejournal.com profile] scott_t_s suggested that I should post a poll and let people fund what I should actually work on. I don't expect this to be a terribly useful exercise, but I expect it will be amusing, so I'm going to go ahead and do so. Thus:

[Poll #994908]
tithonium: (Default)
Another evening spent doing LJ styles. Decided I wanted my friends page to look more like my recent entries page, so I dupe'd Disjoint and applied the differences from my RE page, then had to figure out a good way to get %%friendpic%% in there. Not sure I'm completely happy with it, but it works for now. Might change how entries are done; discrete tables results in more variance than I like.
tithonium: (Default)
It took roughly two and a half years for my job to start affecting my dreams. It took roughly four days for my diet to start affecting my dreams. Tonight, the heat affected my dreams, and I got all three at once. I was working on some project wherein amazon was trying to communication, perhaps it was advertise, over the case or power supply fans of users. I was responsible for the PI layer (amazonians will know what this means). I was quite concerned about supporting all four types of fan, plus the two types for macs, enough so that I got up in the middle of the night when I realized I had missed a couple. I was also VERY concerned about not causing systems (or me) to overheat. I no longer remember how hunger came into it... I got up at 314am to write this, decided to search for a relevant entry in my journal, and ended up implementing the fulltext search people've been nagging me for. So now, and hour and a third later, I'm going to bed.


http://www.martian.cx/Words/Life/Ramblings/blog.cgi?entry=300

Profile

tithonium: (Default)
tithonium

February 2019

S M T W T F S
     12
3456789
10111213141516
17181920212223
2425262728  

Syndicate

RSS Atom

Expand Cut Tags

No cut tags