ossl_ssl.c:125:5: error: use of undeclared identifier 'TLSv1_2_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_2),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_2_method
^
ossl_ssl.c:126:5: error: use of undeclared identifier 'TLSv1_2_server_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_2_server),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_2_server_method
^
ossl_ssl.c:127:5: error: use of undeclared identifier 'TLSv1_2_client_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_2_client),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_2_client_method
^
ossl_ssl.c:131:5: error: use of undeclared identifier 'TLSv1_1_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_1),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_1_method
^
ossl_ssl.c:132:5: error: use of undeclared identifier 'TLSv1_1_server_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_1_server),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_1_server_method
^
ossl_ssl.c:133:5: error: use of undeclared identifier 'TLSv1_1_client_method'
OSSL_SSL_METHOD_ENTRY(TLSv1_1_client),
^
ossl_ssl.c:119:69: note: expanded from macro 'OSSL_SSL_METHOD_ENTRY'
#define OSSL_SSL_METHOD_ENTRY(name) { #name, (SSL_METHOD *(*)(void))name##_method }
^
<scratch space>:148:1: note: expanded from here
TLSv1_1_client_method
^
ossl_ssl.c:210:21: error: invalid application of 'sizeof' to an incomplete type 'const struct <anonymous struct at ossl_ssl.c:115:14> []'
for (i = 0; i < numberof(ossl_ssl_method_tab); i++) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ossl_ssl.c:19:35: note: expanded from macro 'numberof'
#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0]))
^~~~~
ossl_ssl.c:1127:13: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
if (rc = SSL_shutdown(ssl))
~~~^~~~~~~~~~~~~~~~~~~
ossl_ssl.c:1127:13: note: place parentheses around the assignment to silence this warning
if (rc = SSL_shutdown(ssl))
^
( )
ossl_ssl.c:1127:13: note: use '==' to turn this assignment into an equality comparison
if (rc = SSL_shutdown(ssl))
^
==
ossl_ssl.c:2194:23: error: invalid application of 'sizeof' to an incomplete type 'const struct <anonymous struct at ossl_ssl.c:115:14> []'
ary = rb_ary_new2(numberof(ossl_ssl_method_tab));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ossl_ssl.c:19:35: note: expanded from macro 'numberof'
#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0]))
^~~~~
ossl_ssl.c:2195:21: error: invalid application of 'sizeof' to an incomplete type 'const struct <anonymous struct at ossl_ssl.c:115:14> []'
for (i = 0; i < numberof(ossl_ssl_method_tab); i++) {
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ossl_ssl.c:19:35: note: expanded from macro 'numberof'
#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0]))
^~~~~
1 warning and 9 errors generated.
make[2]: *** [ossl_ssl.o] Error 1
make[2]: *** Waiting for unfinished jobs....
compiling raddrinfo.c
compiling ifaddr.c
make[1]: *** [ext/openssl/all] Error 2
make[1]: *** Waiting for unfinished jobs....
linking shared-object zlib.bundle
linking shared-object socket.bundle
linking shared-object date_core.bundle
linking shared-object ripper.bundle
make: *** [build-ext] Error 2
The weird thing here is that I did not get the usual ‘Missing the OpenSSL lib?’ warning. The lib was found but somehow the headers were fucked up. It also did not happen with older rbenv Rubies.
That’s no good. Let’s fix our PATH. I’m using zsh, so for me it’s set in ~/.zshrc. Your particular file depends on the shell you’re using (for bash it would be ~/.bashrc or ~/.bash_profile, but see the caveat here).
1234
➜ ~ vim ~/.zshrc
# Change the line that sets PATH so that /usr/local/bin
# comes BEFORE /usr/bin. For me, it looks like this:
# export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
Open up a new terminal window and check that the PATH is correct:
1234
➜ ~ echo $PATH
/usr/local/heroku/bin:/Users/jarkko/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
➜ ~ which openssl
/usr/local/bin/openssl
Better. Now, let’s make sure that homebrew libs symlink to the newer openssl.
12345678910
➜ ~ brew unlink openssl
➜ ~ brew link --overwrite --force openssl
➜ ~ openssl version -a
OpenSSL 1.0.1j 15 Oct 2014
built on: Sun Dec 7 02:14:31 GMT 2014
platform: darwin64-x86_64-cc
options: bn(64,64) rc4(ptr,char) des(idx,cisc,16,int) idea(int) blowfish(idx)
compiler: clang -fPIC -fno-common -DOPENSSL_PIC -DZLIB_SHARED -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -arch x86_64 -O3 -DL_ENDIAN -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/local/etc/openssl"
Splendid.
After that, Ruby 2.2.0 installed cleanly without any specific parameters needed:
[UPDATE 1, Jan 7] The original version of this post told you to rm /usr/bin/openssl, based on the link above. As James Tucker pointed out, this is a horrible idea. I fixed the article so that we now fix the $PATH instead.
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!
In a previous post in the Rails Performance series we stated that the default garbage collection settings for Ruby on Rails applications are not optimal. In this post we’ll explore the basics of object age in RGenGC, Ruby 2.1’s new restricted generational garbage collector.
As a prerequisite of this and subsequent posts, basic understanding of a mark and sweep1 collector is assumed.
A somewhat simplified mark and sweep cycle goes like this:
A mark and sweep collector traverses the object graph.
It checks which objects are in use (referenced) and which ones are not.
This is called object marking, aka. the MARK PHASE.
All unused objects are freed, making their memory available.
This is called sweeping, aka. the SWEEP PHASE.
Nothing changes for used objects.
A GC cycle prior to Ruby 2.1 works like that. A typical Rails app boots with 300 000 live objects of which all need to be scanned during the MARK phase. That usually yields a smaller set to SWEEP.
A large percentage of the graph is going to be traversed over and over again but will never be reclaimed. This is not only CPU intensive during GC cycles, but also incurs memory overhead for accounting and anticipation for future growth.
Old and young objects
What generally makes an object old?
All new objects are considered to be young.
Old objects survived at least one GC cycle (major or minor) The collector thus reasons that the object will stick around and not become garbage quickly.
The idea behind the new generational garbage collector is this:
MOST OBJECTS DIE YOUNG.
To take advantage of this fact, the new GC classifies objects on the Ruby heap as either OLD or YOUNG. This segregation now allows the garbage collector to work with two distinct generations, with the OLD generation much less likely to yield much improvement towards recovering memory.
For a typical Rails request, some examples of old and new objects would be:
Old: compiled routes, templates, ActiveRecord connections, cached DB column info, classes, modules etc.
New: short lived strings within a partial, a string column value from an ActiveRecord result, a coerced DateTime instance etc.
Young objects are more likely to reference old objects than old objects referencing young objects. Old objects also frequently reference other old objects.
Age segregation is also just a classification – old and young objects aren’t stored in distinct memory spaces – they’re just conceptional buckets. The generation of an object refers to the amount of GC cycles it survived:
Minor GC (or “partial marking”): This cycle only traverses the young generation and is very fast. Based on the hypothesis that most objects die young, this GC cycle is thus the most effective at reclaiming back a large ratio of memory in proportion to objects traversed.
It runs quite often - 26 times for the GC dump of a booted Rails app above.
Major GC: Triggered by out-of-memory conditions - Ruby heap space needs to be expanded (not OOM killer! :-)) Both old and young objects are traversed and it’s thus significantly slower than a minor GC round. Generally when there’s a significant increase in old objects, a major GC would be triggered. Every major GC cycle that an object survived bumps its current generation.
It runs much less frequently - six times for the stats dump above.
The following diagram represents a minor GC cycle (MARK phase completed, SWEEP still pending) that identifies and promotes some objects to old.
A subsequent minor GC cycle (MARK phase completed, SWEEP still pending) ignores old objects during the mark phase.
Most of the reclaiming efforts are thus focussed on the young generation (new objects). Generally 95% of objects are dead by the first GC. The current generation of an object is the number of major GC cycles it has survived.
RGenGC
At a very high level C Ruby 2.1’s collector has the following properties:
High throughput - it can sustain a high rate of allocations / collections due to faster minor GC cycles and very rare major GC cycles.
GC pauses are still long (“stop the world”) for major GC cycles.
Generational collectors have much shorter mark cycles as they traverse only the young generation, most of the time.
This is a marked improvement to the C Ruby GC and serves as a base for implementing other advanced features moving forward. Ruby 2.2 supports incremental GC and object ages beyond just old and new definitions. A major GC cycle in 2.1 still runs in a “stop the world” manner, whereas a more involved incremental implementation (Ruby 2.2) interleaves short steps of mark and sweep cycles between other VM operations.
Object references
In this simple example below we create a String array with three elements.
123456
irb(main):001:0>require'objspace'=>trueirb(main):002:0>ObjectSpace.trace_object_allocations_start=>nilirb(main):003:0>ary=%w(a b c)=>["a","b","c"]
Very much like a river flowing downstream, the array has knowledge of (a reference to) each of its String elements. On the contrary, the strings don’t have an awareness of (or references back to) the array container.
Young objects are more likely to reference old objects, than old objects referencing young objects. Old objects also frequently reference other old objects.
However it’s possible for old objects to reference new objects. What happens when old objects reference new ones?
Old objects with references to new objects are stored in a “remembered set”. The remembered set is a container of references from old objects to new objects and is a shortcut for preventing heap scans for finding such references.
Implications for Rails
As our friend Ezra used to say, “no code is faster than no code.” The same applies to automatic memory management. Every object allocation also has a variable recycle cost. Allocation generally is low overhead as it happens once, except for the use case where there are no free object slots on the Ruby heap and a major GC is triggered as a result.
A major drawback of this limited segregation of OLD vs YOUNG is that many transient objects are in fact promoted to old during large contexts such as a Rails request. These long lived objects eventually become unexpected “memory leaks”. These transient objects can be conceptually classified as of “medium lifetime” as they need to stick around for the duration of a request. There’s however a large probability that a minor GC would run during request lifetime, promoting young objects to old, effectively increasing their lifetime to well beyond the end of a request. This situation can only be revisited during a major GC which runs infrequently and sweeps both old and young objects.
Each generation can be specifically tweaked, with the older generation being particularly important for balancing total process memory use with maintaining a minimal transient object set (young ones) per request. And subsequent too fast promotion from young to old generation.
In our next post we will explore how you’d approach tuning the Ruby GC for Rails applications, balancing tradeoffs of speed and memory. Leave your email address below and we’ll let you know as soon as it’s posted.
See the Ruby Hacking Guide’s GC chapter for further context and nitty gritty details. I’d recommended scanning the content below the first few headings, until turned off by C.↩
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!
Back when Rails was still not mainstream, a common dismissal by developers using other – more established – technologies was that Rails is cool and stuff, but it will never scale1. While the question isn’t (compared to Rails’ success) as common these days, it still appears in one form or another every once in a while.
Last week on the Ruby on Rails Facebook group, someone asked this question:
Can Rails stand up to making a social platform like FB with millions of users using it at the same time?
If so what are the pro’s and the cons?
So in other words, can Rails scale a lot?
Just as is customary for a Facebook group, the question got a lot of clueless answers. There were a couple of gems like this:
Tony if you want to build some thing like FB, you need to learn deeper mountable engine and SOLID anti pattern.
The worst however are answers from people who don’t know they don’t know shit but insist on giving advice that is only bound to either confuse the original poster or lead them astray – and probably both:
Twitter is not a good example. They stopped using Rails because it couldn’t handle millions of request per second. They began using Scala.
This is of course mostly BS with a hint of truth in it, but we’ll get back to that in a bit.
The issue with the question itself, is that it’s the wrong question to ask, and this has nothing to do with Ruby or Rails per se.
Why is it the wrong question? Let’s have a look.
Sure, Ruby is slow in raw performance. It has gotten a lot faster during the past decade, but it is still a highly dynamic interpreted scripting language. Its main shtick has always been programmer happiness, and its primary way to attain that goal has definitely not been to return from that test run as fast as possible. The same goes for Rails.
That said, there are two reasons bare Ruby performance doesn’t matter that much. First, it’s only a tiny part of the perceived app performance for the user. Rails has gone out of its way to automatically make the frontend of your web app performant. This includes frontend caching, asset pipeline, and more opinionated things like Turbolinks. You can of course screw all that up, but you would be amazed how much actual end-user performance you’d miss if you’d write the same app from scratch – not to mention the time you’d waste building it.
Second, and most important for this discussion: scaling is not the same thing as performance. Rails has always been built on the shared nothing architecture, where in theory the only thing you need to do to scale out your app is to throw more hardware at it – the app should scale linearly. Of course there are limits to this, but they are by no means specific to Rails or Ruby.
Scaling and performance are two separate things. They are related as terms, but not strictly connected. Your app can be very fast for a couple users but awful for a thousand (didn’t scale). Or it can scale at O(1) to a million users but loading a page for even a single concurrent user can take 10 seconds (scales but doesn’t perform).
Like stated above, a traditional crud-style app on Rails can be made to scale very well by just adding app server instances, cores, and finally physical app servers serving the app. This is what is meant by scaling out2. Most often the limiting factor here is not Rails but the datastore, which is still often the one shared component in the equation. Scaling the database out is still harder than the appservers, but nevertheless possible. That is way outside the scope of this article, however.
Is this the right tool for the job?
It’s clear in hindsight that a Rails app wasn’t the right tool for what Twitter became – a juggernaut where millions of people were basically realtime chatting with the whole world.
That doesn’t mean that Rails wasn’t a valid choice for the original app. Maybe it wasn’t the best option even then from the technical perspective, but it for sure made developing Twitter a whole lot faster in its initial stages. You know, twitter wasn’t the only contender in the microblogging stage in the late naughties. We finns fondly remember Jaiku. Then there was that other San Fransisco startup using Django that I can’t even name anymore.
Anyway, the point is that reaching a scale where you have to think harder about scalability is a very, very nice problem to have. Either you built a real business and are making money hand over fist, or you are playing – and winning – the eyeball lotto and have VCs knocking on your door (or, more realistically, have taken on several millions already). The vast majority of businesses never reach this stage.
More likely you just fail in the hockeystick game (the VC option), or perhaps build a sustainable business (the old-fashioned people pay me for helping them kick ass kind). In any case, you won’t have to worry about scaling to millions of concurrent users.
Even at the very profitable, high scale SaaS market there are hoards of examples of apps running on Rails. Kissmetrics runs its frontend on Rails, as does GitHub, not to mention Groupon, Livingsocial3, and many others.
However, at certain scale you have to go for a more modular architecture, SOA if I may. You can use a message queue for message passing, a noSQL db for non-relational and ephemeral data, node.js for realtime apps, and so on. A good tool for every particular sub-task of your app.
That said, you need to keep in mind what I said above. It is pretty unlikely you will ever reach a state where you really need to scale. Thus, thinking about the architecture at the initial stage too much is a form of premature optimization. As long as you don’t do anything extra stupid, you can probably get away with a simple Rails app. Because splitting up your app to lots of components early on makes several things harder and more expensive:
Complexity of development.
Operating and deploying a bunch of different apps.
Keeping track that all apps are up and running.
Hunting bugs.
Making changes in a lean development environment where things change rapidly
Cognitive cost of understanding and learning how the app works. This is especially true when you’re expanding your team.
This doesn’t mean that at some point you shouldn’t do the split. There might be a time where the scale for the points above tips, and a monorail app becomes a burden. But then again, there might not. So do what makes sense now, not what makes sense in your imaginary future.
Of course Rails alone won’t scale to a gazillion users for an app it wasn’t really meant for to begin with. Neither is it supposed to. However, it is amazing how far you can get with it, just the same way that the old boring PostgreSQL still beats the shit out of its more “modern” competitors in most common usecases4.
Questions you should be asking
When making a technology decision, instead of “Does is scale?”, here’s what you should be asking instead:
What is the right tool for the jobs of my app?
How far can I likely get away with a single Rails app?
Will we ever really reach the scale we claim in our investor prospectus? No need to lie to yourself here.
What is more important: getting the app up and running and in front of real users fast, or making it scalable in an imaginary future that may never come?
Only after answering those are you equipped to make a decision.
P.S. Reached the point where optimizing Rails and Ruby performance does make a difference? We’re writing a series of articles about just that. Pop your details in the form ☟ down there and we’ll keep you posted.
Another good one I heard in EuroOSCON 2005 was that the only thing good about Rails is its marketing.↩
Versus scaling up, which means making the single core or thread faster.↩
OK, the last two might not pass the profitable bit.↩
There are obviously special cases where a single Rails app doesn’t cut it even from the beginning. E.g. computationally intensive apps such as Kissmetrics or Skylight.io obviously won’t run their stats aggregation processes on Rails.↩
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!
Photo by martin, used under the Creative Commons license.
The vast majority of Ruby on Rails applications deploy to production with the vanilla Ruby GC configuration. A conservative combination of growth factors and accounting that “works” for a demographic from IRB sessions (still my preferred calculator) to massive monolithic Rails apps (the fate of most successful ones). In practice this doesn’t work very well, however. It produces:
Too aggressive growth of Ruby heap slots and pages when thresholds are reached.
A large ratio of short and medium lived objects in relation to long lived ones for Rails applications.
Too many intermittent major GC cycles during the request / response cycle.
Heap fragmentation.
Let’s use a metaphor most of us can better relate to: dreaded household chores. Your ability and frequency of hosting dinners at home are limited by four things (takeaways and paper plates aside):
How many seats and tables you have
How many sets of clean cutlery, plates and glasses are available
Overhead preparing a particular choice of cuisine
Willingness to clean up and do dishes after
This is what you have to work with at home:
4 chairs and a table
12 plates and equivalent utensils
83 friends (60 from Facebook, 20 at work, your 2 brothers and then there’s Jim)
You’ve invited and subsequently prepared dinner and the table—seats, plates and cutlery sets—for four, popped open your bottle of wine and fired up the grill. However, only one friend arrives, quite late. You’re grilling steak number three, yet he’s the vegetarian…and only drinks beer. And even then doesn’t talk very much.
In the end, you down the whole bottle of wine and the three steaks. Life’s good again. There’s plenty to clean up and pack away, still.
Rails scenario
17 guests show up at your door. Half of them are heavily intoxicated because Dylan invited the rest of his wine tasting group, too. Only one eats any of your food, yet breaks four plates. Beer disappeared in three minutes. The group members reveal seven new bottles of wine, make your dog drink one and he kernel panics as a result.
You were not f*cking prepared. At all. Marinated steak’s now ruined, there’s less inventory and 30+ bottles to recycle. You’re hungry and now there are no plates left!
In both of these scenarios, from the perspective of your friends it mostly worked out just fine. It wasn’t optimal for you or your environment, though. What’s important is that you learned a few things:
Next time it’s easier to execute optimally, but there may still be a party and some broken plates.
A barbeque for 17 in your one bedroom flat with a George Foreman grill doesn’t scale well.
Cooking with Ruby
In the same manner, different use cases for the Ruby runtime require different preparations. Let’s tie the dinner metaphor back to Ruby land and its memory model.
Home environment
The Ruby runtime, with everything else inside. Pages, objects and auxilary object data.
Guest count
The number of major features and facets you need to support. Gems and engines are good candidates along with individual models, controllers, views etc. These “seats” are also connected - different guests mingle together.
Guest distribution
Rails provides a framework for building applications, thus should be considered as part of the guest list too. Like some family members that make their way to gettogethers. First and second tier cousins you may hear of once a year and never talk with - they’re present (consume memory), yet don’t always add much value to the ambient.
Food and drink
The amount and distribution of objects required to make a feature or facet work. A mix bag of small entrees (embedded objects like 2-char strings), main dishes (a Rails request and all its context) to cocktails and tequila shots (threads!).
Plates and glasses
An object slot on the Ruby heap. One String, Array, Hash or any other object. Keep in mind that they can overflow and be recycled too - a wine glass is good for multiple servings. For buffets, a single plate can go very far too :-)
Tables
Ruby pages - containers for objects. All of the plates and glasses on a given table. They’re mostly prepared in advance, but you can “construct” and improvise as needed to.
Type of cuisine
Some dishes incur a lot of work to prepare and to clean up. Cooked basmati rice will leave a very very different footprint in your kitchen than a paella or salmon option would.
The GC defaults for most Rails applications assume a reasonable sized home environment, a well defined guest list and just enough food and drinks for each. Everyone can sit at the same table, wine and dine on fine dishes, all with a minimal cleanup burden.
In reality, it’s a frat party. Gone seriously wrong.
In the next part of this series, we’re going to take a look at how the Ruby runtime can better host Rails applications. And what you can optimize for.
This is the transcript of the talk I gave in Reaktor Dev Day in Helsinki, September 26, 2014.
Thanks, and hi, everyone! It’s a real honor to be here. I’ve been a big fan of Reaktor for a long time, that is, UNTIL ALL MY GEEK FRIENDS DEFECTED THERE. There’s been lots of talk about Rosatom building a new nuclear plant here in Finland. I say fuck that, we already have enough nuclear knowledge locally. But I digress.
I’d like to be one of the cool kids and Start with Why just like Simon Sinek told us. However, before that it’s worth defining the term metaprogramming in the context of this talk.
What do we mean by metaprogramming? In its simplest form, metaprogramming means code that writes code.
A-ha! So, code generators are metaprogramming, too? Not really. I go with the definition where the code is generated on the fly in the runtime. We could perhaps call it dynamic metaprogramming. This means that most of what I’m going to talk about is not possible in a static language.
So a more appropriate definition might be, to quote Paolo Perrotta,
Writing code that manipulates language constructs at runtime.
Why?
But why, I hear you ask. What’s in it for me? Well, first of all, because metaprogramming is…
…magic. And magic is good, right? Right? RIGHT? Well, it can be. At least it’s cool.
It’s also a fun topic for a conference like this, because it’s frankly, quite often, mind-boggling. Think of it like this. You take your brain out of your head. You put it in your backpocket. Then you sit on it. Does it bend? If it does, you’re talking about metaprogramming.
I also like things that make me scratch my head. I mean, scratching your head is a form of exercise. Just try it yourself. Scratch your head vigorously and your Fitbit will tell you you worked out like crazy today. That’s healthy.
But all joking aside, we don’t use metaprogramming to be clever, we use it to be flexible. And with Ruby – and any other sufficiently dynamic language – in the end of the day, metaprogramming is just a fancy word for normal, advanced programming.
Why Ruby?
So why Ruby? First of all, Ruby is the language I know by far the best. Second, Ruby combines Lisp-like dynamism and flexibility to a syntax that humans can actually decipher.
Like said, in Ruby there’s really no distinction between metaprogramming and advanced OO programming in general. Thus, before we go to things that are more literally metaprogramming, let’s have a look at Ruby’s object model and constructs that lay the groundwork for metaprogramming.
Thus, in a way, this talk can be reduced to advanced OO concepts in Ruby.
How?
Before we delve more deeply into the Ruby object model, let’s take a step back and have a look at what we mean by object-orientation.
Generally, there are two main approaches to object-oriented programming. By far the most popular is class-based OO, used in languages such as C++ and Java. The other one is prototype-based OO, which is most commonly seen in Javascript. So which of the two does Ruby use?
How’s that for prototype-oriented OO in Ruby? But, noone does anything like this with Ruby. No? Just ask the DCI guys. Or, well, ask Gary about DCI (and Snuggies).
But I get your point, mainly when you do Ruby programming, you use something that resembles more the good ole class-based OO model. However, in Ruby it comes with a twist – or a dozen.
Everything is executable
1234567891011
classConferenceputs"Hello world (open)"defvenueend# …end# Hello world (open)# => nil
In Ruby, everything is executable, even the class definitions. But it doesn’t end there. What does the following produce?
In Ruby, you can open any class, even the built-in classes, to modify it. This is something that is called monkey-patching, or duck punching for extra giggles.
Even methods. Thus you can even do functional style programming with Ruby. Think about it, you can use your favorite language to cook a delicious meal of callback spaghetti. Believe me, I’ve tried.
But, classes are different, I hear you say. They have class methods, and stuff.
I’ll let you into a secret. In Ruby, class methods are just like Ukraine in docent Bäckman’s rethoric: they don’t really exist. Wanna proof?
12345
classConferencedefself.in_finland# return conferences in Finlandendend
Here’s an example of a class method in Ruby. Self is the current object, which in the case of a class definition is the class itself. Does this look familiar?
It should.
Singleton Methods
123
defspeaker.talk_length@talk_length||=30end
Singleton methods are methods that are defined for a single object, not for the whole object class.
Ruby method lookup
Above is a simple (and pretty, huh?) diagram of Ruby method lookup. Methods reside in the object’s class, right of the object in the image. But where do singleton methods live? They can’t sit in the class, since then they’d be shared by all the objects of the same class. Neither can they be in the Object class, for the same reason.
Turns out they live in something called a singleton class.
Singleton class
Singleton class, a.k.a ghost class, metaclass, or eigenclass, is a special case of a class. It’s a regular class except for a couple of details:
It’s hidden from the generic class hierarchy. Thus e.g. the #ancestors method for a class never lists singleton classes.
It cannot be directly inherited.
It only ever has a single instance.
So, what are class methods? They’re simply singleton methods for the class object itself. And like all singleton methods, they live in the singleton class of the object in question – in this case, the class object. Because classes are just objects themselves.
This has an interesting corollary. Singleton classes are classes, and classes are objects, so…
…wait for it…
…a singleton class must have its own singleton class as well.
That’s right, it’s turtles…errr…singleton classes all the way down. Is it starting to feel like metaprogramming already? We have barely started.
Generating Code Dynamically in Ruby
We’re going to have a look at four different ways to generate code dynamically in Ruby:
eval
instance_eval & class_eval
define_method
method_missing
eval
123456
meth="my_method"eval<<-END def #{meth} "foo" end END
Eval is the simplest and barest way to dynamically execute code in Ruby. It takes a string of code and then executes it in the current scope. You can also give eval an explicit scope using a binding object as the second argument.
1234
defget_binding(a)bindingendeval('a+1',get_binding(3))# => 4, because 'a' in the context of get_binding is 3
Eval is super powerful, but has a few huge drawbacks:
It messes up syntax highlighting and autocompletion since the code is just a string as far as the editor goes.
It is a giant attack vector for code injection, unless you carefully make sure that no user-submitted data is passed to eval.
For these reasons eval has slowly fallen out of favor, but there are still some cases where you have to drop down to bear metal (excuse the pun) means. As a rule of thumb however, you should as a first option resort to one of the following constructs.
instance_eval
Put simply, instance_eval takes a block of code and executes it in the context of the receiving object. It can – just like eval – take a string, but also a real code block:
For the reasons above, you should probably use a code block with instance_eval instead of a string of code, unless you know what you’re doing and have a good reason for your choice.
A very common usecase for instance_eval is to build domain-specific languages.
class_eval is the sister method for instance_eval. It changes the scope to inside the class definition of the used class. Thus, unlike instance_eval, it can only be called for classes and modules.
Because of this, a bit counterintuitively methods defined inside class_eval will become instance methods for that class’s objects, while methods defined inside ClassName.instance_eval will become its class methods.
define_method is the most straightforward and highest-level way to dynamically create new methods. It is just the same as using the normal def syntax, except:
With define_method you can set the method name dynamically.
You pass a block to define_method as the method body.
It is worth noting that you often use both *_eval and define_method together, e.g. when defining class methods.
123456789
classCat<Animalinstance_evaldo[:total_number,:sum_of_legs].eachdo|calc|define_method(calc)do# creates a class method, such as Cat.total_numberendendendend
method_missing
method_missing is a special case of dynamic code in Ruby in that it doesn’t just by itself generate any dynamic code. However, you can use it to catch method calls that otherwise would go unanswered.
method_missing is called for an object when the called method is not found in either the object’s class or any of its ancestors. By default method_missing raises a NoMethodError, but you can redefine it for any class to work as you need it to.
1234567891011121314
classSpeakerdefmethod_missing(met,*args)ifmet.to_s=="speak""I might as well say something: #{args[0]}"elsesuperendendendgary=Speaker.newgary.talk("Destroy it")# => NoMethodErrorgary.speak("Just destroy it!")# => "I might as well say something: Just destroy it!"
method_missing is an example of a hook method in Ruby. Hook methods are similar to event handlers in Javascript in that they are called whenever a certain event (such as an unanswered method call above) happens during runtime. There are a bunch of hook methods in Ruby, but we don’t have time to dive deeper into them during this talk.
method_missing differs from the previous concepts in this talk in that it doesn’t by itself generate new methods. This has two implications:
You don’t need to know the name of potentially called methods in advance. This can be very powerful in e.g. libraries that talk to external APIs.
You can’t introspect the methods caught by method_missing. This means that e.g. #instance_methods won’t return the “ghost methods” that only method_missing catches. Likewise, #respond_to? will return false regardless of whether method_missing would have caught the call or not, unless you also overwrite the respond_to_missing? method to be aware of the ghost method.
Example: attr_accessor Rewritten in Ruby
To top off this talk, we’re going to combine the topics we have learned so far to do a simple exercise. Namely, we’re going to rewrite a simple Ruby language construct ourself, in pure Ruby.
Ruby has a simple construct called attr_accessor that creates getter and setter methods for named instance variables of the class’s object.
While attr_accessor above looks like some kind of keyword, it is actually just a call to a class method1. Remember, the whole class definition is executable code and self inside the class definition is set to the class itself. Thus, the line is the same as:
In the code above we define a new class method, nattr_accessor2. Then we iterate over all the method names the method is called with3. For each method, we use define_method twice, to generate both the getter and setter methods. Inside them, we use the instance_variable_get and instance_variable_get methods to dynamically get and set the variable value. Using these methods we can again avoid having to evaluate a string of code, the way as with using define_method.
classAnimalincludeNattrnattr_accessor:legs,:headsend# => NoMethodError: undefined method `nattr_accessor' for Animal:Classfrom(pry):63:in`<class:Animal>'
Oops. What happened?
We used include to get the Nattr module into Animal. However, include will take the methods in the module and make them instance methods of the including class. However, we need the method as a class method. What to do?
Fortunately, Ruby has a similar method called extend. It works the same way as include, except that it makes the methods from the module class methods4 of our Animal class.
Lemme tell you a story. About a dozen or so years ago I was living in Zürich, as an exchange student. I hadn’t yet found a permanent apartment so I was living at some friends’ place while they were abroad. A permanent internet connection wasn’t an ubiquitous thing back then, and the Swiss aren’t big into tv’s, so I had to figure out things to do at nights. I was living alone, and as a somewhat geeky guy I wasn’t that much into social life. Thus, I mostly read at nights. I had just found this Joel guy and a shitload of his writings, so I used the printers at the university to print on the thin brownish paper (hey, it was free!) his somewhat ranting articles and then spent nights reading about camels and rubber duckies, the Joel test, – and leaky abstractions. And that is what metaprogramming in many cases is: an abstraction.
Now, there is nothing inherently wrong with abstractions – otherwise we’d all be programming in Assembler – but we’ll have to keep in mind that they always come at a cost. So keep in mind that metaprogramming is a super powerful tool to reduce duplication and to add power to your code, but you do have to pay a price for it.
Using too much metaprogramming, your code can become harder to:
read,
debug, and
search for.
So use it as any powerful but potentially dangerous tool: start simply but when the complexity gets out of hand, sprinkle some metaprogramming magic dust to get back on the driver’s seat. Never use metaprogramming just for the sake of metaprogramming.
As Dave Thomas once said:
“The only thing worth worrying about when looking at code is ‘is it easy to change?’”
Keep this in mind. Will metaprogramming make your code easier to change in this particular case? If yes, go for it. If not, don’t bother.
Where now?
We’ve only had time to scratch the surface of Ruby object model and metaprogramming. It’s a fractal of sometimes mind-boggling stuff, which also makes it so interesting. If you want to take the next steps in you advanced Ruby object model and metaprogramming knowledge, I’d recommend checking out the following:
Dave Thomas’s screencasts at Prag Prog. They’re a bit dated as in they cover Ruby 1.8. However, not that much has changed since then. Watching them also makes you feel good because you can see the great Prag Dave use Textmate, make mistakes, and delete characters in the code one by one.
Paolo Perrotta’s Metaprogramming Ruby was just updated to cover the latest Ruby and Rails versions. It’s a very down-to-earth and easy read of a sometimes intimidating subject.
If you already think you know everything about the subject, I’d recommend checking out Pat Shaughnessy’s Ruby Under a Microscope. It goes down to the level of how the Ruby object model is implemented in C (yeah, really), while still being an entertaining read.
Last but not least, read the source, Luke. Any non-trivial Ruby application is bound to have more than its share of metaprogramming sprinkled into it. Because, in Ruby, metaprogramming is just programming.
Yeah, I know, singleton method of the class itself.↩
Let’s name it something other than the built-in method just to avoid name collisions and nasty surprises.↩
The asterisk before the parameter name means that we can have a number of arguments, each of which will be passed to the method in an array called meths.↩
Technically, it opens up the singleton class of the Animal class and throws the methods in there. Thus they’ll become singleton methods for the Animal class, just like we want them to.↩
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!
We all know the story. Your company was going to get this big new shiny ERP software. It was going to replace a third of the workforce in the company, cut the costs in half and make everyone happy. In reality the project went two years over schedule, cost three times as much as envisioned, and the end result was a steaming pile of shit.
Photo by Quinn Dombrowski, used under the Creative Commons license.
At this point started the blame-throwing. The provider duped the client with waterfall and exorbitant change fees. The buyer didn’t know how to act as a client in an information system project. The specs weren’t good/detailed/strict/loose enough. The consultants just weren’t that good in the first place. On and on and on.
While one or more of the above invariably are true in failed software projects, there’s one issue that almost each and every failed enterprise software project has in common: the buyers were not (going to be) the users of the software.
This simple fact has huge implications. Ever heard that “the client didn’t really know what they wanted”? Well, that’s because they didn’t. Thus, most such software projects are built with something completely different than the end user in mind. Be it the ego of the CTO, his debt to his mason brothers who happen to be in the software business1, or just the cheapest initial bid2. In any case, it’s in the software provider’s best interest to appeal to the decisionmaker, not the people actually using the system.
Of course, not every software buyer is as bad as described above. Many truly care about the success of the system and even its users. If for no other reason, at least because it has a direct effect on the company’s bottom line. But even then, they just don’t have the first-hand experience of working in the daily churn. They simply can’t know what’s best for the users. Of course, this gets even worse in the design-by-committee, big-spec-upfront projects.
Since it’s not very likely that we could change the process of making large software project purchases any time soon, what can we as software vendors do? One word: empathy. If you just take a spec and implement it with no questions asked, shame on you. You deserve all the blame. Your job is not to implement what the spec says. Heck, your job isn’t even to create what the client wants. Your job is to build what the client – no, the end users – need. For this – no matter how blasphemous it might sound to an engineer – you have to actually talk to the people that will be using your software.
This is why it’s so important to put the software developers to actually do what the end-users would. If you’re building call-center software, make the developers work in the call center a day or a week. If you’re building web apps, make the developers and designers work the support queue, don’t just outsource it to India.
There is no better way to understand the needs for software you’re building than to talk directly to its users or use it yourself for real, in a real-life situation. While there aren’t that many opportunities to dog-fooding when building (perhaps internal) enterprise software for a client, there’s nothing preventing you from sending your people to the actual cost center. Nothing will give as much insight to the needs and pains of the actual users. No spec will ever give you as broad a picture. No technical brilliance will ever make up for lacking domain knowledge. And no client will ever love you as much as the one in the project where you threw yourself (even without being asked) on the line of fire. That’s what we here at Bear Metal insist on doing at the start of every project. I think you should, too.
We at Bear Metal have some availability open for short and mid-term projects. If you’re looking for help building, running, scaling or marketing your web app, get in touch.
It’s surprising how often the same people actually represent both the buyer and the seller. This happens all the time e.g. in the patient care systems projects.↩
Nevermind that the cheapest initial bid almost always balloons to something completely different in the end.↩
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!
This is a talk I gave at Monitorama.eu in Berlin, September 19, 2013.
Did you know that bear is Bär in German? Which, on the other hand, is berry in Swedish, and bears obviously eat berries as breakfast. Meanwhile, a berry is Beer in German, which does sound very German when you think about it. But I’m already digressing.
Germans, and the Berliner especially, are of course very fond of bears, which is the only explanation I could come up with for why I was chosen1 to give this talk here. In particular, they like polar bears here – Eisbären in the local lingo. But it wasn’t always like that.
In 1930 in Stuttgart, an innkeeper threw a large feast serving smoked polar bear ham. The result: 100 falling ill and 13 dead because of trichinosis caused by Trichinella spiralis, a little fella that looks like this:
The moral of the story: always cook your bear meat well done. And now, after hearing this tale, I’ll guarantee you, you will remember it every time you’re cooking polar bear meat. And that is the power of a story.
We’ll get back to the topic of storytelling in a little bit, but let’s first have a quick look at what we know about the human brain and mind.
Duality of the mind – a farce in 2 acts
Act 1: Brain pixels and the human RAM
In 1998, psychologists Daniel Simons and Daniel Levin carried out an experiment. They hired a professional actor to approach people walking on the street and ask them to give them route instructions on a map. While the targets were looking at the map intensely, something weird happened. Two workmen carrying a door walked between the helper and the actor. The door, of course, was smoke and mirrors. Behind it, the person who had asked for help, was swapped to another person. Most of the targets did not notice. The actor was swapped to another with different hair color, then different clothes, and finally from a man to a woman. And yet, more than half of the subjects failed to notice that they were talking to a completely different person.
What this tells us is that our attention is very, very limited. This comes mostly down to two things.
Our focus of vision is tiny
The human vision is a bit like a digital camera. Light is directed through a lens to a “sensor”, the retina. However, this human CMOS is nothing like the one made of silicon. While a digital camera sensor has an even grid of pixels, the brain pixels are anything but. In the center of our vision, called as fovea, we can resolve as much as 100 pixels in the area of a needle pin, at arm’s length. This is more or less where the so-called retina screen resolution comes from.
However, the fovea, at that same length, is only about the size of a thumbnail. Outside that, the “pixel density” goes down really fast. In the periphery of our vision, we can’t really detect any details at all.
The obvious question here is, how then can we process a more or less sharp image of our surroundings? The answer is: we don’t. But we cheat. We move our eyes rapidly to scan our vision, which creates an illusion of a sharper image than it really is.
But this isn’t such an issue, is it? I mean, we can just memorize what we just saw to create a more comprehensive picture of what we just saw. Right? Well, yes and no.
Our working memory is very small
We can, indeed, store items in what is called short-term or working memory. To stay in computer metaphors2, it is a bit like RAM. It is fast, but limited, and when something new goes into it and it gets full, something else must be thrown out. However, unlike its tech counterpart, working memory in us humans has not grown during the last years or even centuries. It is still ridiculously small: somewhere around 3-4. No, I don’t mean 3-4 gigs, or even megs. Hell, not even kilobytes or bytes. 3-4, period.
Let’s look at a short demo video of this. Please don’t continue reading this article further before you have watched it. It takes less than two minutes.
Did you notice the gorilla (or one of the other changes if you had seen the original gorilla video beforehand)? About 50% of people don’t, even though they are looking several times (this was proven with eye tracking equipment) right at the beast, which is quite an amazing demonstration of the limits of our attention.
So what does this lack of attention mean to us as graphic and visualization designers? To put it short, it means the world. As an example, you can’t put two things the viewer should be comparing against each other very far from each other, because the viewer just can’t keep the other one in her memory long enough to make the comparison. Thus the first rule of thumb is: make comparisons viewable with as few eye fixations as possible, preferably one.
The second rule is: maximize the data-ink ratio. The ratio, coined by the visualization guru Edward Tufte, means the amount of data conveyed by the visualization divided by the amount of “ink” used. To put it in another way, the less stuff you have that is only there for visual looks and doesn’t have any particular meaning, the better. Good examples of this are needless and redundant color coding, the infamous PowerPoint pattern backgrounds3, and 3D effects now running amok in the Keynote world. Each of these makes the cognitive load of the viewer higher by fighting for her attention, which then leaves fewer resources in her brain left to actually make sense of the real information in the graph.
The whole field of human attention and cognitive science is huge both in general and applied to visuals in particular. We don’t have the opportunity to delve into it deeper here, but here are some pointers for you to learn more:
In The Magazine, one the several things Marco Arment has sold during the past year, pediatrician Saul Hymes recently wrote an article called Give It Your Best Shot. In the article, Hymes writes about one of his patients, a three-week-old girl who went dead because of bacterial meningitis, an illness passed to him by her unvaccinated older brother.
It was all of course preventable. There has been a vaccine against the bacteria in question, Haemophilus influenzae type b since 1984. So afterwards Hymes asked the mother of the two whether she’d now “give her children the benefit of modern medicine’s vaccinations.”. The answer was no.
What’s going on here?
In his best-selling book, Thinking, fast and slow, the Nobel laureate psychologist Daniel Kahneman lays out his theory of human thinking, splitting it into two systems, which he calls quite unimagitatively systems 1 and 2. System 1 is fast, intuitive, automatic and direct. System 2 is slow, analytical, and not activated in many day-to-day tasks at all. It is also lazy, trusting the intuition of system 1 much more than it should. It wouldn’t be such a problem if system 1 wasn’t as prone to many errors and biases as it is. It draws conclusions long before the conscious mind does. What makes matters worse, we almost always think we made these intuitive, erroneous decisions knowingly.
And this, in many ways, is what is going on in the heads of the people in the anti-vaccination community. Let’s look at some of the biases potentially at play here.
Because of information readily available to us, we often make totally erroneous assumptions of how common or proven it actually is. If our grandfather smoked a lot but still lived to 100-years-old, we easily think that smoking can’t be that bad for you. Or if a celebrity in the TV claims that her son got autism from vaccinations, hey, why not? We use statements like these to prove something, but they don’t of course prove anything. The plural form of ‘anecdote’ is not ‘data’.
Because of availability bias, we systematically overestimate the risk of catastrophes we see often in the media, such as terrorist attacks or natural disasters, and underestimate the boring, but much more likely causes of death, such as diabetes and cancers. We attach much more likelihood to spectacular outcomes. And what could be more spectacular than a centerfold model and her son with an illness obviously caused by greedy pharma companies and their conspiracies with public health organizations?
Conjunction Fallacy
Conjunction fallacy means that the more vividly something is presented, the more likely it is for us to believe it is the truth. At intuitive level, we have a soft spot for plausible stories.
So when Jenny McCarthy goes to Oprah and tells about her son that “My science is Evan, and he’s at home. That’s my science”, no matter that…
the single study combining vaccines to autism has long since been disproven, its author has lost his doctor’s license for fraud, and Lancet has finally published a retraction, and that…
based on some evidence, her son’s (who supposedly was cured from autism in 2010 through diet and other means) symptoms point to Landau-Kleffner Sydrome or, in layman’s terms, delayed development, not autism,…
…people still cry and clap their hands. As Hymes writes,
“To paraphrase George Lucas: So this is how science dies — to thunderous applause? In the court of public opinion, data, and statements, and science are no match for an emotional parent and her child.”
Story Fallacy
We want our lives to follow a tight-nit story that is easy to follow. We talk about understanding surprising events, but that’s not really true. We simply build the meaning into them afterwards.
Media is a champion at this. Just think about the rampant “Apple is doomed, just like with PCs in the 1980’s” narrative. No matter what the facts say, the tech journalists who subscribe to the above notion will distort and retrofit them to their preferred narrative. Hollywood is of course another master at it and this obviously gives an edge to McCarthy over her opponents, the science community who try to convince the public with hard data and statistics.
Unfortunately in this case, stories attract us (and you’ll soon learn why) while the abstract makes us bored out of our minds. Thus, entertaining but irrelevant issues are often prioritized over relevant facts.
Confirmation Bias
Confirmation bias means that we systematically ignore and dismerit facts and opinions that disagree with our own beliefs and worldviews. If we really like BMW’s, we very easily just disregard test articles that give them bad grades and eagerly read through every word in pieces that adore them. The more strongly held a belief is, the stronger the bias is as well.
When we combine these four biases, it’s not so hard to understand why the science community has a hard time convincing the McCarthys of the world. As a result, there have recently been several outbreaks of measles in the US, something that already was completely eliminated from the country. The cases have almost without exception happened – like recently in North Texas – in vaccine-skeptical communities.
The anti-vaccination community is an extreme example, of course. I mean, we’re mostly talking about religious whackos, right? We, who are pro-science, would never succumb to such fallacies, right? Let me tell you about another cognitive bias.
The Overconfidence Effect
As proven over and over again, we systematically overestimate our knowledge, talent and our ability to predict. And not just by a little bit but on a giant scale. The effect doesn’t deal with whether we’re correct or wrong in single estimates. Rather, it measures the difference between what we know and what we think we know. The most surprising thing about the effect is that experts are no less susceptible to it than normal people – on the contrary. As Dobelli writes:
If asked to forecast oil prices in five years time, an economics professor will be as wide of the mark as a zookeeper will. However, the professor will offer his forecast with certitude.
The positive side of stories
But let’s not be negative here. The flipside of all this is that stories are a very powerful way to get your point across and people to remember what you’re trying to teach them. Why is this?
Quite simply, our brains are evolutionarily wired to respond strongly to stories. When we listen to a presentation with mostly boring bullet points, it hits the language processing areas of the brain, where we simply decode words into meaning. And then what? Nothing.
On the other hand, when we’re told stories, the aforementioned parts are not the only ones that fire. Any other areas in our brain that we’d use when experiencing the events of the story are as well. So if we hear a story about a delicious dish, our sensory cortex gets fired up. If the story is about action sports, our motor cortex is activated. Thus, a good story can put our whole brains to work.
Because of this, in a way we’re synchronizing our brains with our listeners. As Uri Hasson from Princeton says:
“When the woman spoke English, the volunteers understood her story, and their brains synchronized. When she had activity in her insula, an emotional brain region, the listeners did too. When her frontal cortex lit up, so did theirs. By simply telling a story, the woman could plant ideas, thoughts and emotions into the listeners’ brains.”
Granted, telling stories visually is much harder than verbally. It should not be treated as impossible, though. After all, movies and cartoons are to a large degree visual. So while the above five points are mostly meant for verbal storytelling, keeping them in mind even when weaving narrative with visualization can be of huge help.
It is important to build continuum, a narrative to your visualizations. The information presented needs to be integrated, rather than a bunch of unrelated pieces. You also want to create relevant emotions and affect to your presentation, and here it helps to link it to the viewers existing knowledge. However you do it, try to make your message more memorable and thus likely to impact behavior.
And whatever you do, keep in mind both a story and a visualization has to make sense.
So, how did Saul Hymes solve the problem of fighting a convincing, storytelling opponent? By telling stories himself. So while he still quoted the relevant stats and facts about the risks of taking vs not taking vaccines, he also started telling vibrant, vivid stories of individual kids dying or going deaf in his hands. After all, he didn’t have to convince people that taking vaccines is not dangerous. He had to convince them that not taking them is. And that is, of course, easy with a meaty story.
In closing
I want you to remember two things from this article.
Our vision and short-term memory – and thus our attention – capacity are very limited. To present successful visualizations, we have to keep this in mind, plan for it and help the cognition of the viewers with cues.
Storytelling is not important (just) because it is entertaining. It is important because it works – it makes people understand and remember our lessons better. This power might be based on fallacies but it is still very much a real effect that you can and should use to do good.
And wait, there’s more. I’ll just leave this thought here for you to ponder:
If you’re into data visualization, you’re not in the data business – you’re in the human communications business.
Visualization is just a tool to attain goals. Keep that in mind.
A few months of work during a sabbatical yielded a product that nailed a problem in the preventative healthcare space. After a freemium window, the product gained good market traction and you spawn a new company with three coworkers. Customers are raving, sales trends are on the up, the engineering team is growing and there are conceptual products in the pipeline.
Three months down the line, there are 4 production applications, hordes of paying customers, a few big contracts with strict SLAs (service-level agreements) and enough resources to spin off a presence in Europe. A new feature that combines these products into a suite is slated for release. Engineering hauled ass for 2 months and sales is super stoked to be able to pitch it to customers.
Shipping
A few days before the feature release a set of new servers is provisioned to buffer against the upcoming marketing push. Due diligence on various fronts was completed, mostly through static analysis of the current production stack by various individuals. Saturday morning at 1am PST they deploy during a window with a historically low transaction volume. Representatives of a few departments sign off on the release, although admittedly there are still dark corners and the OK is mostly based off a few QA passes. Champagne pops, drinks are being had and everyone calls it a day. But then…
When things go south
At 9am PST various alerts flood the European operations team - only 25% of the platform’s available, support is overwhelmed and stress levels go up across the board. Some public facing pages load intermittently, MySQL read load is sky high and application log streams are blank. This deployment, as with most naive releases, was flying blind. A snapshot of a working system prior to release isn’t of much value if it can’t be easily reproduced after rollout for comparison.
Based on assumptions about time, space and other variables there was a total lack of situation awareness and thus no visibility into expected impact of these changes. Running software that pays the bills is today more important than a flashy new feature. However, one must move forward and there are processes and tools available for mitigating risk.
What is situation awareness?
Situation awareness can be defined as an engineering team’s knowledge of both the internal and external states of their production systems, as well as the environment in which it is operating. Internal states refer to health checks, statistics and other monitoring info. The external environment refers to things we generally can’t directly control: Humans and their reactions; hosting providers and their networks; acts of god and other environmental issues.
It’s thus a snapshot in time of system status that provides the primary basis for decision making and operation of complex systems. Experience with a given system gives team members the ability to remain aware of everything that is happening concurrently and to integrate that sense of awareness into what they’re doing at any moment.
How situation awareness could have helped?
The new feature created a dependency tree between 4 existing applications, a lightweight data synchronization service (Redis) and the new nodes that were spun up. Initial investigation and root cause analysis revealed that the following went wrong:
The Redis server was configured for only 1024 connections and it tanked over when backends warmed up as the client connection was lazily initialized.
Initial data synchronization (cache warmup) put excessive load on MySQL and other data stores also used for customer facing reporting.
The data payloads used for synchronization were often very large for outlier customers, effectively blocking the Redis server’s event loop, also causing memory pressure.
The new nodes were spun up with a wrong Ruby major version and also missed critical packages required for normal operations.
A new feature that rolls the “logger” utility into some core init scripts piggybacked on this release. A syntax error fubar’ed output redirection and thus there weren’t any log streams.
Without much runtime introspection in place, it was very difficult to predict what the release impact would be. Although not everything could be covered ahead of time for this release, even with basic runtime analysis, monitoring and good logging it would have been possible to spot trends and avoid issues bubbling up systematically many hours later.
Another core issue here is the “low traffic” release window. It’s often considered good practice to release during such times to minimize fallout for the worst case, however it’s sort of akin to commercial Boeing pilots only training on Cessnas. Any residual and overlooked issues tend to also only surface hours later when traffic ramps up again. This divide between cause and effect complicates root cause analysis immensely. You’d want to be able to infer errors from the system state, worst case QA or an employee and most definitely not customers interacting with your product at 9am.
One also cannot overlook the fact that suddenly each team now had a direct link with at least 3 other applications, new (misconfigured) backends and Redis at this point in time. Each team however only still mostly had a mental model of a single isolated application.
Why situation awareness is so important?
We at Bear Metal have been through a few technology stacks in thriving businesses and noticed a recurring theme and problem. Three boxes become fifty, ad-hoc nodes are spun up for testing, special slaves are provisioned for data analysis, applications are careless with resources and a new service quickly becomes a platform-wide single point of failure. Moving parts increase exponentially and so do potential points of failure.
Engineering, operations and support teams often have no clue what runs where, or what the dependencies are between them. This is especially true for fast growing businesses that reach a critical mass - teams tend to become more specialized, information silos are common and thus total system visibility is also quite narrow. Having good knowledge of your runtime (or even just a perception) is instrumental in making informed decisions for releases, maintenance, capacity planning and discovering potential problems ahead of time. Prediction only makes sense once there’s a good perception of “current state” in place to minimize the rendering of fail whales.
Web operations and awareness
Operations isn’t about individuals, but teams. The goal is to have information exchange between team members and other teams being as passive as possible. Monitoring, alerting and other push based systems help a lot with passive learning about deployments. It’s mostly effortless and easy for individuals to build up knowledge and trends over time.
However, when we actively need to search for information, we can only search for what we already know exists. It’s impossible to find anything we’re not aware of. Given the primary goal of an operations team is platform stability in the face of changes, time to resolution (TTR) is always critical and actively seeking out information when under pressure is a luxury.
Historically a systemwide view has always been the territory of the CTO, operations team and perhaps a handful of platform or integration engineers. Inline with devops culture, we need to acknowledge this disconnect and explore solutions for raising situation awareness of critical systems for all concerned.
And now
Take a minute and ponder the following :
How well do you think you know your systems?
Are developers able to infer potential release risks themselves?
When things go south, how well informed is your support team and what information can they give customers?
Are you comfortable releasing at any time?
In our next post, we’ll explore some common components, variables and events required for being “on top” of your stack. In the meantime, what causes you the most pain when trying to keep up with your production systems? What would you write a blank cheque for? :-)
One of the saddest things to happen online was in 2007 when my all-time favorite author and presenter, Kathy Sierra, received death threaths and thus retreated from the public web. It also meant that she stopped writing her Creating Passionate Users weblog, which had been a great inspiration for me for quite some time. Thank god she didn’t pull a _why on it.
While it’s more than six years since Kathy’s last blog post (is it really that long?), there is no reason we shouldn’t apply her lessons even in today’s online world.
Maybe the most famous mantra of Sierra was that in order to create passionate users you should make them kick ass. Sure, it’s nice if your UI boasts übercool 3D CSS transformations but if it doesn’t help your users shine, no one (well, except for some web geeks) will give a flying fuck.
She demonstrated this with the fact that very often companies spend a huge amount of effort and money to hone the living daylights off their marketing materials but don’t really put that much time into what actually helps their users: tutorials and user manuals. Of course this had helped her immensively by creating a market for the visual Head First book series on O’Reilly that she curated.
Apple has for a long time been a good example of helping its users kick ass. The user manual of the old Final Cut Pro 7 was also a great introduction to the art of video editing1. Likewise, most of Apple ads show things you can do and create with their products, not just random people dancing around the pool.
People care about how they can kick ass themselves and they need to be able to learn it to capitalize on it. Nowadays it seems that companies are much more interested in giving people free apps and then using psychological tricks to milk money out of them than helping them shine. Which, coincidentally, brings us back to Kathy Sierra.
To my pleasant surprise, I last week learned that Kathy is back with the pseudonym Serious Pony, and a new blog of the same name. The first article, Your app makes me fat, is of the same awesome quality as her old pieces. In it, she tackles head-on the aforementioned gamification trend and the ego depletion tax it puts on us as app users.
To honor Kathy, we wanted to start this blog off by not talking about us ourselves, because Bear Metal isn’t really about us, but you. And – assuming you are a developer, entrepreneur or content provider – not really about you either. It’s about who we (you and us) serve. Because without them there is no market, no audience, no need, no problems to solve, no pains to relieve. Your customers should be the ones that matter to you. And they don’t care about you or us. They care about whether your product can make them shine.
Can your product help them kick ass? Does it? Are you communicating that effectively to your current and potential customers? That is all that should matter.
Unfortunately this can’t be said about the manual of the new version, Final Cut Pro X.↩
Get our Secrets of Successful Web Apps email course for free!
Sign up to get the lessons we’ve spent more than a decade learning straight to your inbox. 100% meat, no spam, ever.
Thanks! Now check your email, confirm your subscription, and off we go!