Showing posts with label Performance and Scalability. Show all posts
Showing posts with label Performance and Scalability. Show all posts

Sunday, July 5, 2015

Postgres indexes

Recently, I had a situation where I needed to think how I was using Postgres indexes. I had a simple Book table with the following schema...
>\d book

                  Table "shopping.book"
       Column        |          Type          | Modifiers 
---------------------+------------------------+-----------
 id                  | uuid                   | not null
 version             | bigint                 | not null
 amount_minor_units  | integer                | not null
 currency            | character varying(255) | not null
 author       | character varying(255) | not null
 publisher           | character varying(255) |    
The author and publisher columns were just String pointers to actual Author and Publisher references that were on another system meaning that classical foreign keys couldn't be used and that these dudes were just normal columns.

I needed to get an idea how the table would perform with lots of data, so first up some simple SQL to put in lots of test data:

 
> CREATE EXTENSION "uuid-ossp";
> insert into book (id, version, amount_minor_units, currency, author, publisher) 
select uuid_generate_v4(), 2, 22, 'USD', 'author' || x.id, 'publisher' || x.id from generate_series(1,1000000) AS x(id); 
This table was going to be hit lots of times with this simple query:
select * from book where author = 'Tony Biggins' and publisher='Books unlimited';
To get the explain plain, I did:
dublintech=> EXPLAIN (FORMAT JSON) select * from book where author = 'Tony Biggins' and publisher = 'Books unlimited';
                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 [                                                                                                                                +
   {                                                                                                                              +
     "Plan": {                                                                                                                    +
       "Node Type": "Seq Scan",                                                                                                   +
       "Relation Name": "book",                                                                                                   +
       "Alias": "book",                                                                                                           +
       "Startup Cost": 0.00,                                                                                                      +
       "Total Cost": 123424.88,                                                                                                   +
       "Plan Rows": 1,                                                                                                            +
       "Plan Width": 127,                                                                                                         +
       "Filter": "(((author)::text = 'Tony Biggins'::text) AND ((publisher)::text = 'Books unlimited'::text))"+
     }                                                                                                                            +
   }                                                                                                                              +
 ]
(1 row)
As can be seen, Postgres is doing a Seq Scan, aka a table scan. I wanted to speed things up. There was only one index on the table which was for the id. This was just a conventional B-Tree index which would be useless in this query since it wasn't even in the where clause. Some of options I was thinking about:
  • Create an index on author or publisher
  • Create an index on author and create an index on publisher
  • Create a combination index on both index and publisher.
Hmmm... let the investigations begin. Start by indexing just author.
dublintech=> create index author_idx on book(author);
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 [                                                                            +
   {                                                                          +
     "Plan": {                                                                +
       "Node Type": "Index Scan",                                             +
       "Scan Direction": "Forward",                                           +
       "Index Name": "author_idx",                                  +
       "Relation Name": "book",                                               +
       "Alias": "book",                                                       +
       "Startup Cost": 0.42,                                                  +
       "Total Cost": 8.45,                                                    +
       "Plan Rows": 1,                                                        +
       "Plan Width": 127,                                                     +
       "Index Cond": "((author)::text = 'author3'::text)",+
       "Filter": "((publisher)::text = 'publisher3'::text)"                     +
     }                                                                        +
   }                                                                          +
 ]
(1 row)
As can be seen Postgres performs an index scan and the total cost is much lower than the same query which uses a table scan. What about the multiple column index approach? Surely, since both are used in the query it should be faster again, right?
dublintech=> create index author_publisher_idx on book(author, publisher);
CREATE INDEX
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 [                                                                                                                        +
   {                                                                                                                      +
     "Plan": {                                                                                                            +
       "Node Type": "Index Scan",                                                                                         +
       "Scan Direction": "Forward",                                                                                       +
       "Index Name": "author_publisher_idx",                                                                     +
       "Relation Name": "book",                                                                                           +
       "Alias": "book",                                                                                                   +
       "Startup Cost": 0.42,                                                                                              +
       "Total Cost": 8.45,                                                                                                +
       "Plan Rows": 1,                                                                                                    +
       "Plan Width": 127,                                                                                                 +
       "Index Cond": "(((author)::text = 'author3'::text) AND ((publisher)::text = 'publisher3'::text))"+
     }                                                                                                                    +
   }                                                                                                                      +
 ]
(1 row)
This time Postgres, uses the multi-index, but the query doesn't go any faster. Mai, pourquoi? Recall, how we populated the table.
insert into book (id, version, amount_minor_units, currency, author, publisher) 
select uuid_generate_v4(), 2, 22, 'USD', 'author' || x.id, 'publisher' || x.id from generate_series(1,1000000) AS x(id); 
There are lots of rows, but every row has a unique author value and a unique publisher value. That would mean the author index for this query should perform just as well. An analogy would be, you go into a music shop looking for a new set of loudspeakers someone has told you to buy that have a particular cost and a particular power output (number of watts). When you enter the shop, you see the speakers are nicely ordered by cost and you know what? No two sets of loudspeakers have the same cost. Think about it. Are you going to find the speakers any faster if you use just use the cost or you use the cost and the loudspeaker?

Now, imagine the case if lots of the loudspeakers were the same cost. Then of course using both the cost and the power will be faster.

Now, let's take this point to the extremes in our test data. Suppose all the authors were the same. The author index becomes useless and if we don't have the author / publisher combination index we would go back to table scan.

// drop combination index and just leave author index on table 
dublintech=> drop index author_uesr_ref_idx;
DROP INDEX
dublintech=> update book set author='author3';
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                                      QUERY PLAN                                                       
-----------------------------------------------------------------------------------------------------------------------
 [                                                                                                                    +
   {                                                                                                                  +
     "Plan": {                                                                                                        +
       "Node Type": "Seq Scan",                                                                                       +
       "Relation Name": "book",                                                                                       +
       "Alias": "book",                                                                                               +
       "Startup Cost": 0.00,                                                                                          +
       "Total Cost": 153088.88,                                                                                       +
       "Plan Rows": 1,                                                                                                +
       "Plan Width": 123,                                                                                             +
       "Filter": "(((publisher)::text = 'publisher3'::text) AND ((author)::text = 'author3'::text))"+
     }                                                                                                                +
   }                                                                                                                  +
 ]
(1 row)
So we can conclude from this that single column indexes for combination searches can perform as well as combinational indexes when there is a huge degree of variance in the data of that single column. However, when there isn't, they won't perform as well and a combinational index should be used. Yes, I have tested by going to extremes but that is the best way to make principles clear.And please note: For the case when there is maximum variance in data, adding another index to the other column in the where clause, publisher made no difference. This is as expected.

Ok, let's stick with the case when there is massive variance in data values in the column. Consider the case of maximum variance and the query only ever involves exact matching. In this case, all authors values are guaranteed to be unique and you never have any interest in doing anything like less than or greater than. So why not use a hash index instead of a B-Tree index?

dublintech=> create index author_hash on book using hash (author);
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 [                                                                            +
   {                                                                          +
     "Plan": {                                                                +
       "Node Type": "Index Scan",                                             +
       "Scan Direction": "NoMovement",                                        +
       "Index Name": "author_hash",                                 +
       "Relation Name": "book",                                               +
       "Alias": "book",                                                       +
       "Startup Cost": 0.00,                                                  +
       "Total Cost": 8.02,                                                    +
       "Plan Rows": 1,                                                        +
       "Plan Width": 127,                                                     +
       "Index Cond": "((author)::text = 'author3'::text)",+
       "Filter": "((publisher)::text = 'publisher3'::text)"                     +
     }                                                                        +
   }                                                                          +
 ]
(1 row)
Interesting, we have gone faster again. Not a massive difference this time around but an improvement nonetheless that could be more relevant with more data growth and / or when a more complex query with more computation is required. We can safely conclude from this part that yeah if you are only interested in exact matches then the hash index beats the b-tree index. Until the next time take care of yourselves. References:
  • http://www.postgresql.org/docs/9.1/static/using-explain.html
  • http://www.postgresql.org/docs/9.3/static/indexes-bitmap-scans.html

Saturday, August 4, 2012

Book Review: Even Faster Websites (Steve Souders)

Author Steve Souders (Head Performance Engineer at Google, previously Chief Performance at Yahoo) grew to fame with the YSlow Firefox plugin - a super little gizmo which gave web developers all sorts of ideas for how to speed up their web sites. The book 'High Performance Web Sites: Essential Knowledge for Front-End Engineers book' elaborated on everything the YSlow plugin was telling you. That book effectively serves a forerunner to 'Even Faster Web Sites'. In fact there is an assumption the reader has already read it, is already familiar with all the points it made and is now prepared to dig deeper.  Anyone with an interest in performance is in for a treat with this little gem.



I thought 'Even Faster Web Sites' detailed many clever techiques to boost performance. Amongst some of my favourites:
Steve Sounders
  • Create a <script> element and set its src attribute to a JavaScript file you want to download asychronously.
  • Use the script onload's event - which will only be called when the script has finished downloading - to avoid race conditions.
  • Succint explainations on how deep scope chains in JavaScript can degrade performance
  • Tips on when to use if statements, switch statements and arrays for flow control.
  • A good overview of the Comet Architectural approach and how it can be achieved.
  • Domain sharding tips: split based on resource type e.g put CSS and images on one domain, everything else on another domain.

There are also timely reminders, including:
  • That IFrames make it easy for the UserAgent to print part of a page and are a mechanism to split part of your document giving it independent characteristics.
  • IFrames are more expensive to download.
  • That a browser's busy indicator stops once the Window.onLoad event is fired.
  • Some browsers have limits on how long the JavaScript engine can run
  • Web proxies and security software can mangle the Accept-Encoding header of request so that they speed up their screening of the response from the web server in a process Souders refers to as Turle Tapping.
  • Browsers enforce their maximum connections per server rule based on the hostname in the URL not the IP address it resolves to.
  • That CSS selectors work in a "rightmost first" fashion.
It is a super book. Not just to get ideas to boost web performance but because it helps deepen architectural understanding of the web. There are many important parts in the Web tier: all the different resource types, sychronous / asychronous processing, the various ways blocking can happen, a very sophisticated object model, a funny old language by the name of JavaScript and of course the endless list of quirks from different browsers. There was a time when everything revolved around the database but now a deep understanding web is probably more important. This book most definetly helps deepen understanding of the Web Tier.

Thursday, May 24, 2012

Book Review: 'Scalability Rules: 50 Principles for Scaling Web Sites'

I absolutely love Technical Architecture. It is something that requires high standards in engineering to do well.  In 'Scalability Rules', Martion Abbott and Michael Fisher detail 50 tips, where each tip communicates a simple or sophisticated idea in a few short pages. Their ideas are based from real world experience of working with over 200 internet architectures.

Performance and its cousin Scalability are always an important part of any software architecture and while some cynics will say some of the tips in this book are common sense, there's plenty of really good advice that if adhered to would certaintly lower the probability of scalability issues which are nearly inevitable at some stage in the life of a project.

Among my favourites tips:
  • Put Object caches on their own tier. This makes it easier to size their hardware needs - object caches typically need a lot of memory. It it easier to organise and control this when they have their own tier.
  • Pass on multi-phase commits if possible as they are difficult to scale.
  • Smart reminders such as when it is really important to use aschronous models (integrating with 3PP frameworks, when there is a temporal constraint etc).
I wouldn't just recommend individuals to read this book, I would recommend teams read it. Some important ideas such as spliting up system processing by something like customerId are given concrete names such as Z-Axis splits. The would help teams speak start speaking the same language when communicating ideas. It would also help to remind teams that some simple things such as using logfiles, monitoring your system properly and not relying on QA to find faults that should be found earlier are very important and should not be forgotten.

In summary, there are not too many good books on software architecture and this is certainly one of the best I have read. I have already read parts of it 3 times and I am sure I will be referring to parts of it again.

Book Review: 97 things every Software Architect should know


You can't judge a book by its cover but you can certainly ask questions about its title. Why '97 things every Software Architect should know'? Why not 98, 99 or even 100? Well the word on infoq is that they wanted a number near 100 so that there would be enough material for a reasonably sized book. Fair enough so... The book contains 97 articles published by a range of software professional expressing their views on various aspects of software architecture. Many of the articles are not very technical in nature and there are - perhaps - a lot of similarities between this book and '12 Essential Skills for Software Architects' where author Dave Hendricksen focusses on non-technical skills essential to be a succseful architect. Other articles probably aren't just things an Architect should know but really things anyone working in Software Engineering could benefit from knowing and thinking about. I even include Project Managers in that!

That said, there are some really enjoyable bits and pieces. My favourite parts:
  • Keith Braithwaite's reminding of the architect's need to quantify things. Characteristics such as average response time should be not be phrased using terms such as 'good' or 'satifactory' but quantified as something like: 'between 750ms and 1,250ms'
  • Craig Russell's points about including the human interaction time in any performance analysis. The system may respond very fast to API calls, but the if the UI is counter-intuitive, it means the user will spend a longer time try to get his result.
  • Michael Nygard advice for engineering the 'white spaces'. Don't just have arrows between components specifying the communication protocol, describe the performance expectation of interaction e.g. 100 requests per second, response time 250ms 99% of time. Describe how the system will handle overload, bad response times, unavailability etc.
  • Mark Richards classification of architectural patterns:
    • Enterprise Architecture Patterns: EDA, SOA, ROA, Pipeline architecture
    • Application Architecture: Session Facade, Transfer Object
    • Integration Patterns: File sharing, RPC, Messaging
    • Design Patterns: GoF
  • Gregor Hohpe arguments about the 'predictive call-stack architecture' becoming a thing of the past.
    In massive distributed systems, it's not so easy to define the order things happen in. Architectures now have to be able to respond to events in any time order.
  • Bill de hOra discussion of inevitable tradeoffs using Brewer's conjecture (CAP) as an example.
  • Dave Anderson's arguments for the inevitabitly of legacy and preparing your system for maintenance.
So plenty of good advise in a short book that never gets too technical. The role of the architect is not just to be capable of understanding complicated problems but to stand back and look at the big picture, checking for gaps and to ensure the right actions are taken to ensure project success. This means it's not really just about things a software architect should know, but about things a software architect should ensure they never forget. 

Friday, February 24, 2012

CSS Sprites


The problem

You have a menu bar with the standars buttons, including the proverbial "previous / back" and "next / forward" buttons which look like...

You have been asked to improve the usability of them. In addition, your application is not coming up well in performance analysis tools. One reason for this is that many pages are downloading numerous components resulting in too many HTTP requests. It's time to consider mechanisms to reduce the number of HTTP request required to display your pages.

The solution

Well consider the usability first. The images for the buttons don't look that bad. It's obvious what they mean and what they will do, so there's not much point adding tooltips. But we could make things look sleeker with some "hovering".  What's hovering?  Hovering is a technique which dynamically changes a component when a mouse "hovers" over it. Technically, tooltips are a form of hovering but there are other types: components can change colour or light up. Let's apply some hovering magic to our prev / next buttons.  Hover your mouse over the buttons below, do it a few times and watch them glow.

So how did we do all that? Well, the first thing we do is make alternative images of our original images. This can be done using any decent editing tool: gimp, paint shop pro, take your pick - for this example, I just used Picaso. The second thing to do is to amalgamate the four separate images using a tool such as CSS Sprite generator into one single image.

Why one single image? Well that's for the performance part. HTTP requests can be expensive. We don't want to have to 4 separate HTTP requests for 4 images. So instead, we have one image which contains everything we need. This downloads all four components in one go. We then use some CSS tricks to:
  1. Pull the 2 images out of the one big sprite
  2. Switch the images to their hover versions when the mouse hovers over them
So how do we all that... time to look at some CSS.
#navlist{position:relative;}
#navlist li{margin:0;padding:0;list-style:none;position:absolute;top:0;}
#navlist li, #navlist a{height:60px;display:block;}

#prev{left:0px;width:60px;}
#prev{background:url('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQTHOhAUUfvhMQd9wO68I_rFvM6N_u2oyx29R4IOGlHGdVa9rijixF2OgVDIHGZIMLucPUuXsyl0q4Whc0MpeKWCTuDsTdAnaoXlBYB6KsWRo09LNU022GecQvRQipB3TWOBq3qQVjeYTS/s200/sprite.png') 0 0;}
#prev a:hover{background: url('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQTHOhAUUfvhMQd9wO68I_rFvM6N_u2oyx29R4IOGlHGdVa9rijixF2OgVDIHGZIMLucPUuXsyl0q4Whc0MpeKWCTuDsTdAnaoXlBYB6KsWRo09LNU022GecQvRQipB3TWOBq3qQVjeYTS/s200/sprite.png') -60px -60px;}

#next{left:61px;width:60px;}
#next{background:url('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQTHOhAUUfvhMQd9wO68I_rFvM6N_u2oyx29R4IOGlHGdVa9rijixF2OgVDIHGZIMLucPUuXsyl0q4Whc0MpeKWCTuDsTdAnaoXlBYB6KsWRo09LNU022GecQvRQipB3TWOBq3qQVjeYTS/s200/sprite.png') 0 -60px;}
#next a:hover{background: url('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQTHOhAUUfvhMQd9wO68I_rFvM6N_u2oyx29R4IOGlHGdVa9rijixF2OgVDIHGZIMLucPUuXsyl0q4Whc0MpeKWCTuDsTdAnaoXlBYB6KsWRo09LNU022GecQvRQipB3TWOBq3qQVjeYTS/s200/sprite.png') +60px 0px;}

The second half of the cake is the HTML, note the id's


Explanation
  1. The CSS shows a styled navigation list. A list with id="navlist" will reap the styled benefits.
  2. An element with id="prev" will disect up the collection of images aka the "sprite sheet" and take out the part of the image it wants i.e. the left arrow
  3. An element with id="next" take out the right arrow
  4. "prev" and "next" have hover images which are also taken from the sprite sheet and will be displayed when the mouse hovers over them.
So there we go. Better usability and better performace.  A good day's work. So any other questions? Well an obvious one would be why not just use an image map? It will also reduce HTTP requests. That's true. But, it's not as flexible as using CSS sprites. When image maps are used, the images have to be continguous. Using the CSS sprites technique, you can split them up whatever way you want. A whole bunch of images can be put onto the one sprite and then can be used together, seperate and in whatever order you want.  In fact this is how most companies used CSS sprites. They create a sprite sheet which contains various images for all parts of the web application.  Inline images are another approach. This approach will download the image in the same HTTP request as the page and thus also reduce HTTP requests but it willalso increase the size of the HTML page.  Browser support for CSS sprites approach is also better.

So, is anyone else using CSS sprites - hell yeah! Recognise anything here...


References
  1. Really interesting page about the evolution of google super sprite
  2. W3C page on CSS sprites
  3. Sprite generator

Tuesday, December 27, 2011

JAXB, SAX, DOM Performance

This post investigates the performance of unmarshalling an XML document to Java objects using a number of different approaches. The XML document is very simple. It contains a collection of Person entities. 

There is a corresponding Person Java object for the Person entity in the XML
...

and a PersonList object to represent a collection of Persons. 

The approaches investigated were:
  • Various flavours of JAXB
  • SAX
  • DOM
In all cases, the objective was to get the entities in the XML document to the corresponding Java objects. The JAXB annotations on the Person and PersonList POJOS are used in the JAXB tests. The same classes can be used in SAX and DOM tests (the annotations will just be ignored). Initially the reference
implementations for JAXB, SAX and DOM were used. The Woodstox STAX parsing was then used. This would have been called in some of the JAXB unmarshalling tests.

The tests were carried out on my Dell Laptop, a Pentium Dual-Core CPU, 2.1 GHz running Windows 7.

Test 1 - Using JAXB to unmarshall a Java File.


Test 1 illustrates how simple the progamming model for JAXB is. It is very easy to go from an XML file to Java objects. There is no need to get involved with the nitty gritty details of marshalling and parsing.

Test 2 - Using JAXB to unmarshall a Streamsource

Test 2 is similar Test 1, except this time a Streamsource object wraps around a File object. The Streamsource object gives a hint to the JAXB implementation to stream the file.


Test 3 - Using JAXB to unmarshall a StAX XMLStreamReader

Again similar to Test 1, except this time an XMLStreamReader instance wraps a FileReader instance which is unmarshalled by JAXB.


Test 4 - Just use DOM
This test uses no JAXB and instead just uses the JAXP DOM approach. This means straight away more code is required than any JAXB approach.
Test 5 - Just use SAX Test 5 uses no JAXB and uses SAX to parse the XML document. The SAX approach involves more code and more complexity than any JAXB approach. The Developer has to get involved with the parsing of the document.

The tests were run 5 times for 3 files which contain a collection of Person entities. The first first file contained 100 Person entities and was 5K in size. The second contained 10,000 entities and was 500K in size and the third contained 250,000 Person entities and was 15 Meg in size. In no cases was any XSD used, or any validations performed. The results are given in result tables where the times for the different runs are comma separated.

TEST RESULTS
The tests were first run using JDK 1.6.26, 32 bit and the reference implementation for SAX, DOM and JAXB shipped with JDK was used.

Unmarshall Type 100 Persons time (ms) 10K Persons time (ms)  250K Persons time (ms)
JAXB (Default)  48,13, 5,4,4 78, 52, 47,50,50 1522, 1457, 1353, 1308,1317
JAXB(Streamsource) 11, 6, 3,3,2 44, 44, 48,45,43 1191, 1364, 1144, 1142, 1136
JAXB (StAX) 18, 2,1,1,1 111, 136, 89,91,92 2693, 3058, 2495, 2472, 2481
DOM 16, 2, 2,2,2 89,50, 55,53,50 1992, 2198, 1845, 1776, 1773
SAX 4, 2, 1,1,1 29, 34, 23,26,26 704, 669, 605, 589,591
JDK 1.6.26 Test comments

  1. The first time unmarshalling happens is usually the longest.
  2. The memory usage for the JAXB and SAX is similar. It is about 2 Meg for the file with 10,000 persons and 36 - 38 Meg file with 250,000.  DOM Memory usage is far higher.  For the 10,000 persons file it is 6 Meg, for the 250,000 person file it is greater than 130 Meg. 
  3. The performance times for pure SAX are better. Particularly, for very large files.
The exact same tests were run again, using the same JDK (1.6.26) but this time the Woodstox implementation of StAX parsing was used.

 
Unmarshall Type 100 Persons time (ms) 10K Persons time (ms)  250K Persons time (ms)
JAXB (Default)  168,3,5,8,3 294, 43, 46, 43, 42 2055, 1354, 1328, 1319, 1319
JAXB(Streamsource) 11, 3,3,3,4 43,42,47,44,42 1147, 1149, 1176, 1173, 1159
JAXB (StAX) 30,0,1,1,0 67,37,40,37,37 1301, 1236, 1223, 1336, 1297
DOM 103,1,1,1,2 136,52,49,49,50 1882, 1883, 1821, 1835, 1822
SAX 4, 2, 2,1,1 31,25,25,38,25 613, 609, 607, 595, 613
JDK 1.6.26 + Woodstox test comments

  1. Again, the first time unmarshalling happens is usually proportionally longer.
  2. Again, memory usage for SAX and JAXB is very similar. Both are far better
    than DOM.  The results are very similar to Test 1.
  3. The JAXB (StAX) approach time has improved considerably. This is due to the
    Woodstox implementation of StAX parsing being used.
  4. The performance times for pure SAX are still the best. Particularly
    for large files.
The the exact same tests were run again, but this time I used JDK 1.7.02 and the Woodstox implementation of StAX parsing.

Unmarshall Type 100 Persons time (ms) 10,000 Persons time (ms)  250,000 Persons time (ms)
JAXB (Default)  165,5, 3, 3,5 611,23, 24, 46, 28 578, 539, 511, 511, 519
JAXB(Streamsource) 13,4, 3, 4, 3 43,24, 21, 26, 22 678, 520, 509, 504, 627
JAXB (StAX) 21,1,0, 0, 0 300,69, 20, 16, 16 637, 487, 422, 435, 458
DOM 22,2,2,2,2 420,25, 24, 23, 24 1304, 807, 867, 747, 1189
SAX 7,2,2,1,1 169,15, 15, 19, 14 366, 364, 363, 360, 358
JDK 7 + Woodstox test comments:

  1.  The performance times for JDK 7 overall are much better.   There are some anomolies - the first time the  100 persons and the 10,000 person file is parsed.
  2. The memory usage is slightly higher.  For SAX and JAXB it is 2 - 4 Meg for the 10,000 persons file and 45 - 49 Meg for the 250,000 persons file.  For DOM it is higher again.  5 - 7.5 Meg for the 10,000 person file and 136 - 143 Meg for the 250,000 persons file.
Note: W.R.T. all tests
  1. No memory analysis was done for the 100 persons file. The memory usage was just too small and so it would have pointless information.
  2. The first time to initialise a JAXB context can take up to 0.5 seconds. This was not included in the test results as it only took this time the very first time. After that the JVM initialises context very quickly (consistly < 5ms). If you notice this behaviour with whatever JAXB implementation you are using, consider initialising at start up.
  3. These tests are a very simple XML file. In reality there would be more object types and more complex XML. However, these tests should still provide a guidance.
Conclusions:
  1. The peformance times for pure SAX are slightly better than JAXB but only for very large files. Unless you are using very large files the performance differences are not worth worrying about. The progamming model advantages of JAXB win out over the complexitiy of the SAX programming model.  Don't forget JAXB also provides random accses like DOM does. SAX does not provide this.
  2. Performance times look a lot better with Woodstox, if JAXB / StAX is being used.
  3. Performance times with 64 bit JDK 7 look a lot better. Memory usuage looks slightly higher.

Thursday, September 22, 2011

SQL or NOSQL that is the question?

So what's the deal with NoSQL?

Is NoSQL just a controversial buzzword? Could you imagaine if the term 'Object Orientated' didn't exist and instead architectures based on concepts such as encapsulation, polymorphism and inhertiance were referred to as 'NoProcedural'?  Could you imagine if .net was called 'NoJava'?  Leinster was called 'NoMunster'?

Well controversial name aside,  a good way to appreciate the hype about NoSQL is to consider scalability - the classical non-functional architectural concern. In a classical OLTP architecture, when load increases and your JVM is under pressure, you need to scale.  You have two choices:
  • vertical scaling - adding more CPU power to your JVM
  • horizontal scaling - adding more JVMs (usally one more boxes)
It's generally never any problem scaling the business tier horizontally. Follow J2EE / JEE specs and unless you've done something crazy your business tier will scale.  Just add more JVMs and load balance between them.  However, while the business tier may be straighforward, the persistence tier ain't so easy.   Let's say you are using a classical relational database (such as MySQL, SQLServer, DB2 or Oracle) for your persistence, you can't just add database machines like you can add JVMs.  Why not?  Imagine trying to do SQL joins when tables are on the same machine and when the tables are on different machines! Imagine trying to do maintain ACID characteristics for your transactions when your database is split across various CPUs?  Now think trying to do all that on 5 machines, 50 , 500, 5000 machines?  The more machines the harder it gets.

The leading relational databases will scale horizontally.  But only by so much.  To get around this an architect usually will consider:
  • Scaling vertically - putting the database on the best hardware that can be afforded
  • Partitioning out legacy data and thus reduce things like the size of index tables. This will boost performance and put less pressure on the need to scale
  • Remove the amount of pressure on the database by caching more in the business tier
  • Pay a DBA a lot of money!
But what if you just run out of all possible database optimisations options and you have to scale horizontally? Not just to a few machines but to a few hundred if not thousand. This is where NoSQL architectures become relevant.

With a NoSQL database there is no strict schema.  Everything is effectively collapsed into one very fat table - a bit like an old skool flat file, but where each row stores a huge amout of data.   So, instead of having a table for Users and a table for Activities (representing User's activities), you put all the User information together in one fat row. This means there are no joins across tables.  It also means there is a lot of data redundancy which means more storage space required. In addition, more computational power will be needed for writes. But because data that is used together is located at the very same place - within the same row - it means no complex joins and hence it is easier to scale. The computational requirement for reads is also less.  So reads can go faster.

Another advantage of NoSQL databases is derived from the freedom that comes with not having to be tied to strict schema.  You know that headache where a change to a data model  can cause big problems? Well since there is no strict schema with NoSQL - this problem does not exist.  This makes the architecture more flexible and more extensible.

Right now, it's fair to say NoSQL is only relevant in the minority of architectures. But could this be another case of technical innovation driving business innovation as we have seen with smart phones?  There wasn't a need for smart phones but the technical innovation provided business opportunities. I think the same could happen with NoSQL Architectures.

Take a step back from Computer Science and just think Science.  Science used to be hypotheisis centric, now it is becoming more and more data centric. CERN, genome sequencing, climate change analysis - all involve tonnes and tonnes of data. Surely NoSQL architectures allied with searching technologies such as MapReduce / Hadoop will open up new ways to do Science?

So any disadvantages with NoSQL architectures? Well it's still an immature technology. Indexing, Security models are just not as sophisticated as they are with classical relational databases. And because most of it is coming from the open source community the support is not as good as it is for relational databases.  So don't throw out your SQL just yet!

PS Well done Dublin and winning the All Ireland!



References:
1. http://about.digg.com/blog/looking-future-cassandra
2. http://www.techrepublic.com/blog/10things/10-things-you-should-know-about-nosql-databases/1772
3. http://nosqltapes.com/

Monday, June 20, 2011

Consistent Hashing

Consistent Hashing is a clever algorithm that is used in high volume caching architectures where scaling and availability are important. It is used in many high end web architectures for example: Amazon's Dynamo.  Let me try and explain it!


Firstly let's consider the problem.

Let's say your website sells books (sorry Amazon but you're a brilliant example). Every book has an author, a price, details such as the number of pages and an ISBN which acts as a primary key uniquely identifying each book. To improve the performance of your system you decide to cache the books.  You split the cache over four servers.  You have to decide which book to put on which server.  You want to do this using a deterministic function so you can be sure where things are.  You also want to do this at low computational cost (otherwise what's the point caching).  So you hash the book's ISBN and then mod the result by the number of servers which in our case is 4.  Let's call this number the book's hash key.

So let's say your books are:
  1. Toward the Light, A.C. Grayling (ISBN=0747592993)
  2. Aftershock, Philippe Legrain (ISBN=1408702231)
  3. The Outsider, Albert Camus (ISBN=9780141182506)
  4. This History of Western Philosophy, Bertrand Russell (ISBN=0415325056)
  5. The life you can save, Peter Singer (ISBN=0330454587)
... etc

After hashing the ISBN and moding the result by 4, let's say the resulting hash keys are:
  1. Hash(Toward the Light) % 4 = 2. Hashkey 2 means this book will be cached by Server 2.
  2. Hash(Aftershock) % 4 = 1. Hashkey 1 means this book will be cached by Server 1.
  3. Hash(The Outsider) % 4 = 4. Hashkey 1 means this book will be cached by Server 4.
  4. Hash(The History of Western Philosophy) % 4 = 1. Hashkey 1 means this book will be cached by Server 1.
  5. Hash(The Life you can save) % 4 = 3. Hashkey 1 means this book will be cached by Server 3.
Oh wow doesn't everything look so great. Anytime we have a book's ISBN we can work out its hash key and know what server its on!  Isn't that just so amazing!  Well no.  Your website has become so cool, more and more people are using it.  Reading has become so cool there are more books you need to cache.  The only thing that hasn't become so cool is your system.  Things are slowing down and you need to scale.  Vertical scaling will only get you so far; you need to scale horizontally.

Ok, so you go out and you buy another 2 servers thinking this will solve your problem.  You now have six servers.  This is where you think the pain will end but alas it won't.  Because you now have 6 servers so your algorithm changes. Instead of moding by 4 you mod by 6.  What does this mean?  Initially, when you look for a book because moding by 6 will mean you'll end up with a different hash key for it and hence a different server to look for it on. It won't be there and you'll have incurred a database read to bring it back into the cache.  It's not just one book, it will be the for the majority of your books.  Why? Because the only time a book will be on the correct server and not need to be re-read from the database is when the hash(isbn) % 4 = hash(isbn) % 6.  Mathematically this will be the minority of your books.
So, your attempt at scaling has put a burden on the majority of your cache to restructure itself resulting in massive database re-reads.  This can bring your system down.  Customers won't be happy with you sunshine!

We need a solution!

The solution is to come up with a system which has the characteristic that when you add more servers and only a small minority of books will move to new servers meaning a minimum number of database reads.  Let's go for it!

Consistent Hashing explained

Consistent hashing is an approach where the books get the same hash key irrespective of the number of books and irrespective of the number of servers - unlike our previous algorithm which mod'ed by the number of servers.  It doesn't matter if there is one server, 5 servers or 5 million servers, the books always always always always get the same hash key. 

So how exactly do we generate consistent hash values for the book
s?

Simple.  We use a similar approach to our initial approach except we stop moding on the number of servers. Instead we mod by something else, that is constant and independent of the number of servers. 

Ok, so let's hash the ISBN as before and then mod by 100.  So if you have 1,000 books.  You end up with a distribution of hash keys for the books between 0 - 100 irrespective of the number of servers.  All good. All we need is a way to figure determinstically and at low computational cost which books reside on which servers.  Otherwise again what would be the point in caching?

So here's the ultra funky part... You take something unique and constant for each server (for example its IP address) and you pass that through the exact same algorithm. This means you also end up with a hash key (in this case a number between 0 and 100) for each server.

Let's say:
  1. Server 1 gets: 12
  2. Server 2 gets: 37
  3. Server 3 gets: 54
  4. Server 4 gets: 87
Now we assign each server to be responsible for caching the books with hash keys between its own hash key and that of the next neighbour (next in the upward direction).

This means:
  1. Server 1 stores all the books with hash key between 12 and 37
  2. Server 2 stores all the books with hash key between 37 and 54
  3. Server 3 stores all the books with hash key between 54 and 87
  4. Server 4 stores all the books with hash key between 87 and 100 and 0 and 12.
If you are still with me... great. Because now we are going to scale.  We are going to add two more servers.  Lets say server 5 is added and gets the hash key 20.  And server 6 is added and gets the hash value 70.
This means:
  1. Server 1 will now only store books with hash key between 12 and 20
  2. Server 5 will stores the books with hash key between 20 and 37.
  3. Server 3 will now only store books with hash key between 54 and 70.
  4. Server 6 will stores books with the hash key between 70 and 87.
Server 2 and Server 4 are completly unaffected.

Ok so this means:
  1. All books still get the same hash key. Their hash keys are consistent.
  2. Books with hash keys between 20 and 37 and between 70 and 87 are now sought from new servers.
  3. The first time they are sought they won't be there and they will be re-read from the system and then cached in the respective servers. This is ok as long as it's only for a small amount of books.
  4. There is a small initial impact to the system but its managable.
Now, you're probably saying:
"I get all this but I'd like to see some better distribution. When you added two servers, only two servers got their load lessoned. Could you share the benefits please?"

Of course. To do that, we allocate each server a number of small ranges rather than just one large range.  So, instead of server 2 getting one large range between 37 and 54.  It gets a number of small ranges. So for example, if could get: 5 - 8, 12 - 17, 24 - 30, 43 - 49, 58 - 61, 71 - 74, 88 - 91.  Same for all servers.  The small ranges all randomly spread meaning that one server won't just have one adjacent neighbour but a collection of different neighbours for each of its small ranges.   When a new server is added it will also get a number if ranges, and number of different neighbours which means its benefits will be distributed more evenly.

Isn't that so cool!

Consistent hashing benefits aren't just limited to scaling. They also are brilliant for availability. Let's say server 2 goes offlines.  What happens is the complete opposite to what happens for when a new server is added.  Each one of Server 2 segments will become the responsibility of the server who is responsible for the preceeding segment. Again, if servers are getting a fair distribution of ranges they are responsible for it means when a server fails, the burden will be evenly distributed amongst the remaining servers.

Again the point has to emphasised, the books never have to rehashed. Their hashes are consistent.
References
  1. http://www.allthingsdistributed.com/2007/10/amazons_dynamo.html
  2. http://www.tomkleinpeter.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/
  3. http://michaelnielsen.org/blog/consistent-hashing/