Chapter 23. Optimizing Ajax Applications

One of the main appeals of Ajax is that users see it as being faster than traditional web design models. As much as Ajax may increase the speed of your web page or application, however, you can use certain tricks and best practices to further improve speed. None of the suggestions in this chapter are necessary to write a good Ajax-based application. They are intended for developers who want to add that extra edge to their code. If that does not interest you, simply skip this chapter. You won’t hurt my feelings, I promise.

Site Optimization Factors

Unfortunately for you as an Ajax web developer, client-side programming requires planning and real thought when you’re developing code if it is to run optimally for the end user. This is not the case for a desktop application programmer, who does not truly concern himself with optimization. Why is this? For one, when most programming languages are compiled, the compiler optimizes them automatically. The compiler first converts the source code into tokens of keywords, variables, constants, symbols, and logical operators. It then parses these tokens to make sure the source code is written correctly, and it creates an intermediate code that is used in the final process. This process optimizes the intermediate code where it can, and produces a machine language “object” that will be sent to a linker to create the executable file. The desktop application programmer doesn’t really consider any of these compiler steps unless something goes wrong.

Now, what about you? There are two separate issues that you, as a web developer, need to consider when thinking about optimizing your client-side code:

  • File size

  • Execution time

The size of the files that will be downloaded from the web server to the client browser is an issue that affects XHTML, CSS, and JavaScript. The time it takes for code to execute, however, is an issue that solely affects JavaScript on the client side.

On the server side of things, the language you choose for your backend will affect how the language can be optimized. Some languages are compiled and go through all the steps I just outlined for desktop applications. Others go through parsers and can still utilize the optimization methods I will cover later in this chapter. Another consideration for the server end of an Ajax application is the database. How you create your SQL will affect how quickly the database responds. You should consider all of this when developing an application (any application). The more you use these methods, the less you will think about them; they will become second nature to you when you develop.

The biggest factor for the server side of the Ajax application is the execution time for the script and the database calls. Size is not important, because nothing on the server side of the application needs to be sent to the client for download.

Size

When a client browser downloads any XHTML, CSS, or JavaScript file from a web server, it is downloading every character byte that is contained within that file before it can parse and execute it. Therefore, you need to concern yourself with the size of the files that will be downloaded to the client, and minimize the number of bytes the files contain. This is something else that a desktop application programmer need not concern himself with. If he has a variable name that is 50 characters long or if he writes a novel of comments in his code, so what? The compiler replaces the names of all those variables, and it removes all the comments. The web application programmer is not so lucky.

As I just stated, to optimize your client-side code, you will need to minimize the number of bytes contained within every file that will be downloaded to the client. Obviously, the smaller the file, the faster it will be downloaded across the Internet. There are particular sizes you should consider and ultimately aim for, though, when you begin to reduce the size of any XHTML, CSS, or JavaScript code. Some books and Internet optimization sites out there say this magic number is 1,160 bytes. This is the number of bytes that these sources say will fit into a single TCP/IP packet. This is a very good number, and it works well in most cases. However, it does not take everything into consideration when it comes to the protocols that move information along on the Internet. I will go into more detail on packet sizes and the optimal number of bytes for a packet a little later in the chapter.

Execution Speed

The speed of your JavaScript makes a huge impact on a user’s perception of how good the application is. If the page downloads quickly, but then seemingly chugs along when it’s asked to do something trivial, users will turn away. No one wants to wait for something he feels should take almost no time at all to complete.

Fortunately, there are ways to alter the speed of scripts, on both the client and the server, to run more quickly and more efficiently. I will go into each type of script individually to show you ways to optimize it for speed. These steps are designed to help interpreted languages more than compiled ones. This is mainly because of what I highlighted earlier regarding compiled languages and the optimization methods performed at compile time. I cannot compete with that kind of optimization.

However, I can help with some common programming practices that can slow down your SQL code and how quickly it can retrieve data. You can manipulate these practices to optimize your SQL code to run as quickly as possible.

You can find more information on good programming practices for backend computing, especially compile languages, in Head First Object-Oriented Analysis and Design by David West et al. (O’Reilly), and Beautiful Code by Andy Oram and Greg Wilson (O’Reilly).

HTTP

HTTP is the protocol that drives the Web and, in turn, our Ajax applications. We can do nothing to the protocol to aid in optimizing our applications, but there are a couple of tricks we can perform at the server end of any transaction that could impact our application as a whole. Of course, if you do not have control of your web server for whatever reason, you are out of luck with this part of optimization. Do not despair if this is the case; avenues are still available for you to affect the optimization of your server-side code, if nothing else.

HTTP is in charge of delivering all data between the client and the server, so this is an important piece to optimize if possible. You can modify two parts of HTTP if you have the access to do it. They are:

  • HTTP headers

  • HTTP compression

HTTP Headers

The first optimization technique we will discuss is the HTTP response headers that the server sends to the client with every response. As you will see when we discuss packets, if we can reduce the size of the headers without impacting how the protocol works, we can send more data through in a single packet. Granted, we won’t be able to send much more data, but every little tweak can help in the long run. This is not the important change to your HTTP headers, though. What is more important is to get the client browser to cache as much content as possible so that not everything is loaded with every request.

HTTP response headers provide data that elaborates on the status line that is at the beginning of each server response. The response headers often reflect the type of request sent by the client. Nine response headers are defined for HTTP/1.1 (http://www.w3.org/Protocols/rfc2616/rfc2616.html), as shown in Table 23-1.

Table 23-1. The HTTP/1.1 response headers

Response header

Description

Accept-Ranges

Tells the client whether the server accepts partial content requests using the Range request header, and if it does, what type

Age

Tells the client the approximate age of the resource, determined by the server

ETag

Tells the client the entity tag or the entity included in the response

Location

Tells the client a new URL that the server instructs it to use in place of the one the client initially requested

Proxy-Authenticate

Specifies an authentication method as well as any other parameters needed for authentication

Retry-After

Tells the client when it should try its request again when the initial request is unsuccessful

Server

Identifies the type and version of the software generating the response

Vary

Identifies which request header fields fully determine whether a cache is allowed to use this response to reply to all other requests for the same resource without revalidating

WWW-Authenticate

Indicates how the server wants the client to authenticate when an Unauthorized response is sent

An HTTP response from the server can include other types of headers as well. General headers can be in any type of header (request, response, or message entity), whereas entity headers provide information about the resource of the body of the HTTP message. Table 23-2 shows all of these headers and explains their use.

Table 23-2. General and entity HTTP/1.1 headers

HTTP header

Description

Header type

Allow

Lists all of the methods that are supported for a particular resource

Entity

Cache-Control

Specifies directives that manage how caching is performed for HTTP requests or responses

General

Connection

Instructs the client about specific options desired for a particular connection that must not be retained by proxies and used for further connections

General

Content-Encoding

Describes any optional method that may have been used to encode the data

Entity

Content-Language

Specifies the natural language intended for using the data

Entity

Content-Length

Specifies the size of the data in octets

Entity

Content-Location

Specifies the resource location of the data, in the form of a URL

Entity

Content-MD5

Contains an MD5 digest for the data, used for message integrity checking

Entity

Content-Range

Indicates what portion of the overall file this message contains, as well as the total size of the overall file

Entity

Content-Type

Specifies the media type and subtype of the data, similar to MIME types

Entity

Date

Indicates the date and time when the message originated

General

Expires

Specifies a date and time after which the data in the message should be considered stale

Entity

Last-Modified

Indicates the date and time when the server believes the data was last changed

Entity

Pragma

Is used to enable specific directives to be applied to everything associated with a request and response

General

Trailer

Specifies headers that are appended after the data when chunked transfers are used

General

Transfer-Encoding

Indicates what encoding is being used for the body of a message

General

Upgrade

Allows a client device to specify what additional protocols it supports

General

Via

Specifies what gateways, proxies, and/or tunnels were used in conveying a request or response

General

Warning

Is used when additional information about the status of a message is needed

General

If we look at a typical HTTP header sent from an Apache web server, we will see something like the following:

HTTP Status Code: HTTP/1.1 200 OK
Date: Sun, 25 Nov 2007 15:50:44 GMT
Server: Apache
X-Powered-By: PHP/5.2.0
Set-Cookie: PHPSESSID=111111111aa111a11aa11a11111a11aa; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
content-encoding: gzip
Connection: close
Content-Type: text/html

This is a response header from the main page of my site, Holdener.com. We can do a couple of things to make all downloads from the server occur more quickly. Before we talk about this, though, there is something easier to address: giving the data being sent a better chance of being sent through one packet by shrinking the size of the HTTP header.

Some headers in the HTTP header are not required for the response to work correctly. For example, there is a general rule that custom headers should begin with X- so that they are easier to distinguish among the other headers. Right away we would want to rid ourselves of these headers being sent, if possible.

In Apache, the mod_headers module allows an administrator to control and modify HTTP request and response headers with directives to merge, replace, and remove headers. For more information on this module, see the Apache server documentation section at http://httpd.apache.org/docs/2.0/mod/mod_headers.html. The command to remove a header from the HTTP response is simple. For example:

RequestHeader   unset   X-Powered-By

This directive in the Apache configuration file removes the header X-Powered-By from the HTTP response headers. This is not the only response header that is not necessary and can be removed to shrink the header size. You also can safely remove the Date, Server, and Connection headers, unless you have a specific need for one or all of them. Really, you can use the mod_headers Apache module to remove or modify any header that you do not want to be sent to the client with every response to a request.

Tip

The mod_headers module is unable to alter the Server header in versions of Apache before 2.x. To get the same results in 1.3.x, Apache users will have to edit the defines in httpd.h and recompile Apache.

Unfortunately, it is not as simple with Internet Information Services (IIS) to remove response headers for an HTTP response. To do this, you have to create an ISAPI filter in C++ (for speed) that would take any outgoing messages and strip away the response headers that you do not want to have sent out. Microsoft wrote a nice little article on IIS customizations with ISAPI filters, which you can find at http://www.microsoft.com/msj/0498/iis/iis.aspx.

Besides reducing the size of the header, we should examine how to modify the response headers to most effectively take advantage of client caching. Client caching would greatly speed up the download of a site if most of the content was already cached and did not have to be downloaded at all.

Using the Expires response header gives you basic control over caches in that it tells a cache how long the associated data is “fresh.” After this expiration date, the cache should check with the sending server to see whether a document has changed. The basic problem with Expires is that it is human-settable, and because of this, the time set for expiring could pass, and the developer might forget what that date is. If this happens, the cache would be hitting the web server more often than was intended.

To address the limitations of the Expires header, HTTP 1.1 introduced the Cache-Control header to allow for more exact control over content caching. This header has a number of directives that you can set, which are shown in Table 23-3.

Table 23-3. A list of directives for the Cache-Control header

Directive

Description

max-age

Specifies, in seconds, the maximum amount of time the data will be considered fresh

s-maxage

Specifies, in seconds, the maximum amount of time the data will be considered fresh, applied to shared caches (proxies, etc.)

Public

Marks authenticated responses as cacheable; the default for authenticated responses is uncacheable

no-cache

Forces the cache to fetch the data from the server for validation before releasing a cached copy, every time

no-store

Instructs a cache not to cache a copy of the data under any circumstances

must-revalidate

Tells a cache that it must follow any freshness information given about data

proxy-revalidate

Tells a cache that it must follow any freshness information given about data, applied to proxy caches

Warning

The Pragma header, used to make data uncacheable, does not necessarily do this in practice. The HTTP specification sets guidelines for request headers, not for Pragma response headers. A few caches may honor the header, but you cannot count on it. It is recommended that you use Expires and/or Cache-Control instead.

Controlling the cache will give you better control over when content must be pulled from the web server, and can give your Ajax application a nice boost.

HTTP Compression

Compressing the output that is sent to the client is nothing new to web development, and the ability to accept compressed content has been built into all modern browsers. The type of compression is specific to the web server, and is usually done with either DEFLATE or gzip. Therefore, browsers must be able to accept both types of compression for compressed content to be readily available from all major web servers. Of course, a developer has no control over how the client implements this feature. So, we must turn our attention to activating compression on the server for clients to utilize.

Apache 2.x comes with a module to handle compression (mod_deflate) that adds a filter to output to gzip the content. A nice feature of the Apache module is that it allows for two ways to compress content: either a blanket compression or a selective compression. This means that everything is compressed if a blanket compression is used, or compression is based on specific MIME types that you can configure.

Though I said that there were only two compression methods with mod_deflate, the truth is that these compression methods are specific to the containers in which they are placed within the configuration file. This means that different containers can have different methods applied to them. Compression is activated with the DEFLATE filter, so you activate compression on a given container with the following:

SetOutputFilter    DEFLATE

If you want to instead filter by specific MIME types, you would do something like this:

AddOutputFilterByType    DEFLATE    text/html

This example filters only content for .html files in a specific container.

Older browsers obviously do not know how to handle compressed content, so Apache has a directive that controls which browsers should and should not have compressed content sent to them. The BrowserMatch directive allows this browser control and has a no-gzip and gzip-only-text/html configuration that you can use. For example:

BrowserMatch    ^Mozilla/4            gzip-only-text/html
BrowserMatch    ^Mozilla/4.0[678]    no-gzip
BrowserMatch    MSIE                !no-gzip !gzip-only-text/html

The first line checks for all 4.x versions of Netscape Navigator and sets it so that compression is activated only for .html files, as these versions can handle compression for only this type. The second line checks for versions 4.06, 4.07, and 4.08 of Netscape Navigator and deactivates all compression, because these versions cannot handle any compression. The third line checks for Internet Explorer browsers that misidentify themselves as Mozilla browsers. Internet Explorer can handle compression, and should therefore not have any restrictions set on it.

Adding HTTP compression to IIS 6 is just as simple (if not more so because of its GUI) as Apache to configure. In fact, IIS 6 has a built-in compression system that can configure compression for both static and dynamic content. Furthermore, it caches all of the compressed data, making it perform even better because it does not have to compress content that has already been compressed and is in the cache.

Enabling HTTP compression in IIS 6 is fast and easy. Go to the property window of the web site’s page (right-click Default Website and click Properties). Click the Service tab and configure the options as you desire, as shown in Figure 23-1.

The Service tab for configuring compression in IIS 6

Figure 23-1. The Service tab for configuring compression in IIS 6

Unfortunately, IIS 5 and earlier versions have no built-in methods for compressing the contents of a site. For these web servers, the only way to enable compression for a site is with an ISAPI filter. These are slower than any built-in options available with IIS 6 and Apache, but nonetheless they still enable the servers to compress content. An ISAPI filter that Microsoft recommends for its IIS 5 and earlier web servers is httpZip from Port80 Software (http://www.port80software.com/products/httpzip/). This ISAPI filter is compatible with IIS 6 as well and is the best commercial software available for compression.

Compression is one of many ways that you as a developer can add to an Ajax application to increase its speed and overall performance. Most other performance enhancements come by way of optimizing code on both the client and server sides, but all of these optimizations will still go through the HTTP server. It is well worth the trouble to add compression to a site if at all possible.

Packets

Why did I include a section on packets in an Ajax book? Packets control the amount of data that moves from the server to the client, and the more you can optimize this, the faster the Ajax application will be. In this section, I will give an overview of how data transfer works. For a more advanced look at the technologies involved with this discussion, refer to The TCP/IP Guide by Charles Kozierok (No Starch).

Every request for data from a server breaks down to packets of information being transferred back and forth between the client and the server. Certain protocols aid in moving these packets of information between destinations on the Internet. These protocols are part of what is called a network stack. A basic stack looks like Figure 23-2. In this figure, we are assuming that the link being used to connect to the Internet is Ethernet. As this varies, so does the size of the packets, as you will see in a moment.

A typical network stack

Figure 23-2. A typical network stack

We already talked about HTTP earlier in this chapter. That protocol is what the application uses to communicate on the Internet. HTTP needs a way to transport this data, and this is where TCP comes into play. TCP is the transport layer of a network stack and is responsible for moving data on the Internet. TCP needs a network on which to move data, and this is provided through IP. All Internet destinations are broken down into IP addresses, and these addresses make up the network that is the Internet. Finally, the network must have a link between nodes (the computers that are communicating), and for the most part, this is provided through the Ethernet protocol.

I said that this example would use the Ethernet protocol, but obviously more choices are available. Table 23-4 shows the different protocols, their speeds, and how they are physically connected through cables.

Table 23-4. A summary of the different Internet protocols available

Protocol

Cable

Speed

Ethernet

Twisted Pair, Coaxial, Fiber

10 Mbps

Fast Ethernet

Twisted Pair, Fiber

100 Mbps

Gigabit Ethernet

Twisted Pair, Fiber

1000 Mbps

LocalTalk

Twisted Pair

23 Mbps

Token Ring

Twisted Pair

4–16 Mbps

Fiber Distributed Data Interface (FDDI)

Fiber

100 Mbps

Asynchronous Transfer Mode (ATM)

Twisted Pair, Fiber

155–2,488 Mbps

Discussing the headers of the protocols and what each part means is beyond the scope of this book, so I hope you can take it for granted when I say that a single Ethernet frame can contain 1,500 bytes of data. A frame is what makes up a packet of data to be sent across the Internet, and this size can vary depending on the protocol used. Some protocols have bigger frame sizes and some have smaller ones. We are concentrating on the optimal size of data we want sent in a packet of data, so we have 1,500 bytes to work with.

Tip

Regardless of the size of the packet that you send from your node on the Internet, all of the router hops required to get from the origin to the destination can reduce the size of the packet if some of the router hops cannot transport as large a packet size. The data is then chunked, but a discussion of this is definitely beyond the scope of this book.

Optimal Sizes

So, let’s talk about packet sizes. We are looking at optimizing the size of the files the client needs to download so that they require the smallest number of packets to get from one point to the other. This is what speeds up the application, and it’s why I discuss file size and ways to optimize it throughout the chapter. The Ethernet protocol has room for 1,500 bytes of data, and within that data container we need the IP and the space it takes up, TCP and the space it takes up, and HTTP and the space it takes up. The first thing to do is to look at the header requirements of these protocols—more specifically, the IP and TCP headers. The IP header requires 20 bytes, as does the TCP header. Therefore, one HTTP packet can be no larger than 1,460 bytes to fit in a single Ethernet packet.

We have already talked about the HTTP header and what it contains, so how large a single file can be to fit into a packet depends on the size of the HTTP header. How large is the HTTP header? This is the gotcha with optimization; it varies among servers based on what is placed in the header, and it can even vary by individual response. Remember the magic number 1,160 I mentioned when I introduced the idea of file size? This number is derived from assuming that a typical HTTP header is around 300 bytes. To increase this number, we must decrease the size of the HTTP header. The HTTP header is the only protocol in the schema we are looking at that we can reduce or increase in size. The other protocol headers are of a fixed length.

Take a look at a typical HTTP header response from http://www.holdener.com/:

HTTP/1.1·200·OK
Date:·Sun,·19·Aug·2007·18:09:16·GMT
Server:·Apache
X-Powered-By:·PHP/5.2.0
Set-Cookie:·PHPSESSID=1b73fbacaf38398ada149dd83d9eceb0;·path=/
Expires:·Thu,·19·Nov·1981·08:52:00·GMT
Cache-Control:·no-store,·no-cache,·must-revalidate,·post-check=0,·pre-check=0
Pragma:·no-cache
content-encoding:·gzip
Content-Length:·3573
Connection:·close
Content-Type:·text/html

This response is 388 bytes in size, which means that one packet can contain even less space than the average size that was assumed. What can we do to fix this size? Remember, I said that we could get rid of any header starting with an X-, so that is 25 bytes right there. We can also remove the Server name, which is another 6 bytes. And we can remove the Connection header, giving us another 19 bytes. That is 50 bytes we’ve already removed. Now, the one problem with this example is that I am showing you the header to the PHP file; it is dynamic, so we do not want caching to occur. Most of those headers have to stay. The real question is, how likely would a PHP page fit in one packet anyway? After all, that would be a pretty small page.

It is the additional requests that we really care about—all of the requests for JavaScript, CSS, and media files. This is where we can speed up the process. This is where our gains will come in—when these files are optimized. Here is a typical response header for a CSS file:

HTTP/1.1·200·OK
Date: Sat, 18 Aug 2007 16:30:06 GMT
Server: Apache
Last-Modified: Wed, 01 Aug 2007 02:16:14 GMT
Etag: "14c0313-1df8-e9029780"
Accept-Ranges: bytes
Content-Length: 7672
Content-Type: text/css

There is a big difference here in terms of size. This header is only 215 bytes long, giving us more space to work with. Even so, we are still looking at a file size that needs to be less than 1,250 bytes. Based on this information, I propose that we aim for a file size close to 1,250 bytes for all of the external files that will be called. This will be the magic number we want to shoot for, and if we cannot achieve this, it should be some multiple of this number. The fewer packets we need to send, the faster the Ajax application will run.

Think about this for a second. If you have 5 CSS files, 3 JavaScript files, and 10 images that need to be loaded with the page that loads, that is a minimum of 18 packets of data that must be sent. Being realistic, the number is going to be at least twice that. However, we would not want each file to be just a little bit bigger than a multiple of our 1,250 bytes. Even if it is just a little bit bigger, another packet must be sent. If all of them are just a little bit bigger, 18 more packets must be sent. This will take more time before the page is considered finished and any other functionality can take place. We want to optimize all client-side files so that they are as small as possible and fit in a multiple of our magic number: 1,250 bytes.

Client-Side Optimizations

The first thing we want to concentrate on is the client side of an Ajax application and what we can do to optimize it and make it as efficient as possible. We can do several things with the code that runs on the client side to make it operate more efficiently.

XHTML and CSS

The first files that we will optimize are those that deal with structure and presentation on the client: XHTML and CSS. We can’t do a whole lot to reduce the size of these files besides removing comments and unnecessary whitespace, but we’ll do our best.

Now, I know that if you think back to all the examples you have seen in this book, I included a lot of comments about the structural elements in the .xhtml files and in the .css files. And now you must be wondering why I am telling you that you need to remove all of these comments to properly optimize your code. Well, you should have comments in all of your code, but you should remove them from the production code that will be presented to the end user. Keep in mind that comments really are not intended for the person who is using a web application; rather, they are intended for the application developers to help them to understand the code they are working on.

This is probably the simplest thing that a web developer can do to reduce the size of any client-side code file, so I am going to stress that if you do nothing else to reduce the size of your files, do this! A great number of bytes can be tied up in developer comments, and the easier it is to reduce the size, the better it is for your application. So, remove all comments from your XHTML and CSS files.

Tip

Sometimes it may be legally necessary to have a copyright notice remain intact with the client-side code when it is put into production and presented to the user for downloading. All I can say about this is that you should keep the legal comments in the code and try to keep them in the shortest form possible.

Size reduction

Comments aside, the only other easy way to reduce the size of your code is to remove the unnecessary whitespace from your files. In the case of XHTML and CSS, we can define whitespace as any unneeded space, tab, or line break that exists in the file. In the examples you’ve seen throughout the book, spaces, tabs, and line breaks exist only to keep the code clean and clear for those of you reading it. But just as with removing comments, you should remove whitespace from your production code that exists on the server.

Let’s first examine the whitespace that can and should be removed from XHTML code:

<div id="summary">
    <p>A Title Here.</p>
    <ul>
        <li>Item One</li>
        <li>Item Two</li>
    </ul>
    <pre>
This is preformatted
            text    and should
    be left alone.
    </pre>
</div>

The trick here is that you can strip out all of the tabs, spaces, and line feeds between and after the XHTML elements, except for the <pre> element because it is one element that should honor whitespace in a file. After removing the whitespace, the preceding example would look like this:

<div id="summary"><p>A Title Here.</p><ul><li>Item One</li><li>Item Two</li>
</ul><pre>
This is preformatted
            text    and should
    be left alone.
</pre></div>

As you can check, once you take out all of the extra spaces between elements, as well as all tabs and line feeds except for those within the <pre> element, this code still looks the same in the browser. The code snippet went from 216 bytes down to 173 bytes, saving 43 bytes in just 12 lines of code. These savings will add up, I guarantee it.

The whitespace in the CSS file is easier to remove than that in the XHTML file, because we do not have to concern ourselves with watching out for certain elements. Take a look at the following CSS snippet:

body {
    background-color: transparent;
    color: #000;
    padding: 2px 0;
    font-family: sans-serif;
}

#logo {
    background: url('../images/logo.png') no-repeat fixed center;
    color: #000;
}

The preceding code with all the whitespace stripped out would look something like the following:

body{background-color:transparent;color:#000;padding:2px 0;font-family:
sans-serif}#logo{background:url('../images/logo.png')no-repeat fixed
center;color:#000}

The original code is 203 bytes, whereas the optimized code is 158 bytes. I know this is a savings of only 45 bytes, but it is also only a small amount of code. The savings you can achieve when you have a full-fledged CSS file can be huge.

Tip

If you cannot remove the line breaks from your XHTML and CSS files, I still recommend that you check to make sure that any line breaks you keep in your code are in the Unix format ( ) and not in a Windows format ( ). It may not add up to much, but one byte instead of two is still a savings.

The second thing you can do to XHTML and CSS files in terms of code optimization is to shorten class and id names within the files. You can really reduce the size of your XHTML and CSS files with this technique.

Warning

A strong word of caution when changing class and id names, as this is not as simple a process as removing whitespace. It is not recommended that you do this by hand.

Take a look at the following XHTML code:

<div id="highlight">
    <p class="text">Text</p>
    <ul id="ext">
        <li class="light">Item One</li>
        <li class="dark">Item Two</li>
    </ul>
</div>

Now, I know this code is silly, but it is designed with a purpose. Here’s the catch with changing names by hand: suppose you want to change the class named light to lt and the class named dark to dk, so you do a simple search and replace in your editor; if you did just a blanket replace all (because they are class names after all, and there may be a lot of them), you could end up with the following:

<div id="highlt">
    <p class="text">Text</p>
    <ul id="ext">
        <li class="lt">Item One</li>
        <li class="dk">Item Two</li>
    </ul>
</div>

In the preceding code, the id named highlight might have been renamed highlt by mistake, thus breaking any potential styles that were associated with this <div> element. Like I said, this is a simple example, but you can understand the problem you may have by changing names by hand, depending on what those names are.

Another pain to consider is that you may have to make these changes to every ex-HTML file that you have deployed, plus all of your corresponding CSS (and potentially JavaScript) files. Not only is this a lot of work, but you are setting yourself up for mistakes that will break your web application. You should seriously consider the risks and gains before going to this extreme.

Another tip that I can give you for optimizing CSS files is to use shorthand notation whenever possible. You may have noticed that in the CSS examples throughout this book, I always used shorthand notation in my code, whether it was to shorten hexadecimal color names or whether I used it for rules, padding, or margins. Small things such as this can provide great gains in the long run. Finally, make sure you allow your CSS files to cascade the way they should. If you are unsure how to program using these techniques, take a look at CSS: The Definitive Guide, Third Edition, by Eric Meyer (O’Reilly), for reference.

JavaScript

I can show you a number of optimization techniques with JavaScript. I can show you so many because of the way JavaScript is implemented. Having a language that is parsed at runtime allows for several coding techniques that can improve script execution time. There are also a number of ways to reduce the size of the JavaScript files that are to be downloaded, beyond what I discussed with XHTML and CSS.

Because JavaScript is the heart of any Ajax application, it is important to make it as fast and lean as possible. Doing so will give your users the best possible experience as far as functionality is concerned.

Size reduction

There are many tricks to reducing the size of a JavaScript file that are not available to other client-side code such as XHTML and CSS. Before we get to those, though, we can employ the same tricks these files use to shrink the size of the file.

Removing the comments in your JavaScript files, especially if you are using JSDoc comments to document your code, can greatly reduce the size of your code. You do not necessarily want everyone who downloads your JavaScript files to see what you commented on anyway. So, remove those comments from production files!

Then there is all the whitespace in a JavaScript file. Consider the following lines of JavaScript code.

function GetActiveSS( ) {
    for (var i = 0; (a = document.getElementsByTagName('link')[i]); i++)
        if (a.getAttribute('rel').indexOf('style') != -1 &&
                a.getAttribute('title') && !a.disabled)
            return a.getAttribute('title'),
    return (null);
}

We can remove whitespace in plenty of places in this code. Here is the same code, with the whitespace removed:

function GetActiveSS( ){for(var i=0;(a=document.getElementsByTagName('link')[i]);
i++)if(a.getAttribute('rel').indexOf('style')!=-1&&a.getAttribute('title')&&!
a.disabled)return a.getAttribute('title'),return(null)}

The code is not affected in the least, as the semicolon (;) separates commands in JavaScript, but the size of the snippet went from 267 bytes to 212 bytes, a savings of 55 bytes. This is a small size advantage in this example, but with a full JavaScript file, you can really reduce the size of the file.

Often you will find that you need to test whether a value is valid or invalid. This usually involves testing whether something equals true, false, null, or undefined. This is demonstrated in the following:

if (myValue == true) {
    // execute code
}

if (myValue != null) {
    // execute code
}

if (myValue != undefined) {
    // execute code
}

All of these are correctly written statements, and they work just fine, but we can rewrite them all by testing the variable, and in case of the != by using the NOT operator:

if (myValue) {
    // execute code
}

if (!myValue) {
    // execute code
}

This reduces the size of the script without affecting its operation. When the JavaScript is parsed, it does a type conversion on myValue, making it true, false, null, or undefined, and voilà! This type conversion may also produce a 1 or 0, which can optimize Boolean variables a bit more throughout your script. Take a look at the following code segment:

var isOk = false;

for (var i = 0; isOk || i < anArray.length; i++) {
    if (anArray[i] == 'found')
        isOk = true;
    else {
        // execute code
    }
}

This segment contains only two Boolean variables, but what about code that uses and sets Boolean values all over the place? Keep in mind as you look at this segment of code again that the Boolean values are only being substituted for 0 and 1:

var isOk = 0;

for (var i = 0; isOk || i < anArray.length; i++) {
    if (anArray[i] == 'found')
        isOk = 1;
    else {
        // execute code
    }
}

This simple code change does nothing to alter the outcome of this code segment, but it can save you three bytes for every instance of true and four bytes for every instance of false. That can add up to a lot of savings, and it is a very simple change.

Warning

Users of ASP.NET must be careful when sending the results of client scripts back to the server, as the Boolean values true and false must be sent, and not 0 or 1, or you will experience unexpected results.

The final way to save on size in a JavaScript file is by using array and object literals. An array literal is a list of zero or more expressions that are enclosed in square brackets ([]), where each expression represents an array element. When an array is created in this way, it is initialized to the values specified in the list, and the length of the array is set to the number of expressions listed.

Here is how you are first taught by any JavaScript book to create arrays:

var bookTypes = new Array( );

bookTypes[0] = 'Fiction';
bookTypes[1] = 'Children's Literature';
bookTypes[2] = 'History';

It is the traditional way, and perfectly legitimate—it is easy to understand exactly what is happening in the code without the need for comments. However, our aim is for smaller size, not ease of understanding. It takes 126 bytes to declare the array in this way. We can reduce that like this:

var bookTypes = new Array('Fiction', 'Children's Literature', 'History'),

Here, we set all of the array elements from within the array constructor. Declaring an array in this way reduces the size of the code considerably. It takes only 74 bytes to declare the array in this way, but now let’s look at using an array literal:

var bookTypes = ['Fiction', 'Children's Literature', 'History'];

This takes our code segment down to 65 bytes, which is a small decrease from the second code segment, but a savings nonetheless. Using array literals to create your arrays is just a good practice to get into.

Object literals are similar to array literals; an object literal is a list of zero or more expression pairs that are enclosed in curly braces ({}), where each expression pair represents a property name and its associated value. Creating an object in this way initializes it and creates the properties that were defined in the expression list.

Warning

It is a bad idea to use an object literal at the beginning of a statement. If you were to do this, the opening curly brace ({) will be interpreted as the beginning of a block, which would lead to an error, or the object may not behave as you expect it to. If you must begin a statement in this way, surround the statement in parentheses.

function book(p_title, p_isbn) {
    this._title = p_title;
    this._isbn = p_isbn;
    this.getTitle = function( ) {
        return this._title;
    };
}

var newBook = new book('Ajax: The Definitive Guide', '0-596-52838-8'),

The preceding code segment is not really much of an object, but it is enough to give you an idea of what we are aiming at. That first bit of code is 234 bytes long. We can reduce it to this:

var newBook = new Object( );

newBook._title = 'Ajax: The Definitive Guide';
newBook._isbn = '0-596-52838-8';
newBook.getTitle = function( ) { return this._title; };

The preceding code segment creates the same object as the previous segment, but the number of bytes with this code is 167. Now, we will create the object using an object literal:

var newBook = { _title: 'Ajax: The Definitive Guide', _isbn: '0-596-52838-8',
getTitle: function( ) { return _title; }};

This takes our object down to 119 bytes, a pretty big savings over our original object creation segment and even a pretty good gain over our second example. It is a good idea to take advantage of this ability when creating your objects, as it gives good byte savings.

Tip

If you are thinking, “Man, I’ve seen that notation somewhere before,” you are right, you have! Array and object literals are what JavaScript Object Notation (JSON) boils down to, and it’s why JSON is a good way to send data to the client.

Code speed enhancements

Perhaps the more important part of JavaScript optimization, execution time affects how your application runs once it has been downloaded to the client. Some of the enhancements that I will show you for optimizing your JavaScript code may not be easy to implement for one reason or another, but others you should always implement to achieve speed enhancements consistently.

An important part of JavaScript is the ability to assign variables in your code. However, did you know that where your variables are declared in your code can make a difference in terms of a program’s execution speed? It is important to define your variables at the scope in which they will be used. This is because it takes CPU cycles to test every level, from the current level of execution to the top level of the program. The less the parser has to search to find the variable it needs to execute with, the faster the program’s execution time will be. As a general rule of thumb, global variables are bad. Not that they don’t serve their purpose, but they create longer execution time because of their scope. For example:

for (i = 0; i < 20; i++)
    alert(i + '<br />
'),

That’s a stupid example, I know, but the point is that this code would execute more quickly if the variable was defined within the scope of the loop, like this:

for (var i = 0; i < 20; i++)
    alert(i + '<br />
'),

There are two optimization techniques that you can easily apply to most for loops. The first is all about setting a local variable to any value that would have to be looked up and pulled out each time through the loop. A good example of this is looking up the lengths of arrays. For example:

var count = 0;

for (var i = 0; i < arrNumbers.length; i++)
    count += arrNumbers[i];

You could optimize the preceding code as follows:

var count = 0;

for (var i = 0, il = arrNumbers.length; i < il; i++)
    count += arrNumbers[i];

The other for loop optimization has to do with what operations are faster for JavaScript to execute. It is easier for JavaScript to test a value against zero than it is to test against a number. What we want to do, then, is reverse the loop so that it counts down and checks for zero instead of checking in the other, more common direction. The speed gains on a small for loop are negligible; with larger loops, however, you will start to notice them. Here is an example loop:

var count = 0;

for (var i = arrNumbers.length - 1; i > 0; i--)
    count += arrNumbers[i];

These optimizations are trivial compared to accessing and manipulating the Document Object Model (DOM) document. Doing so is one of the most costly operations that you can perform as a JavaScript programmer. Every DOM manipulation will in some way change the way the page is displayed to the user, because to make sure the page is rendered correctly, it must recalculate every last object and element on the page. This takes a significant amount of time, in computing terms, so keep in mind that every time you add, modify, or delete something from the DOM structure, you will be increasing the amount of time that your code takes to execute. To keep these calculations and times to a minimum, you should create objects outside of the DOM and manipulate the DOM with these objects instead of directly.

Suppose we want to add an array of titleDsc strings that contain the description of a book title to a <div> element with an id of descript, separating each item in the array with a <br> element:

var i = 0, il = titleDsc.length;
var dsc = document.getElementById('descript'),

/* Loop through the array while there are elements */
do {
    dsc.appendChild(document.createTextNode(titleDsc[i]));
    i++;
    /* Are there still elements to parse? */
    if (i < il)
        dsc.appendChild(document.createElement('br'));
} while (i < il);

There are a couple of problems with this code segment—not with the code itself (it executes just fine), but rather in terms of the speed at which it executes. The first problem is the first dsc.appendChild( ) call, which adds a text node to the <div> element to be updated. The second problem is that the second dsc.appendChild( ) call, which adds a <br> element to the <div> element descript, causes the page to recalculate so that it can be displayed correctly to the user. Just with these two problems, you are causing the page to recalculate two times every trip through the loop. Suppose that there are 15 items in the titleDsc array; that would mean the page must recalculate a total of 30 times when this segment is executed.

To reduce the number of page recalculations, use a document fragment to hold all of the text nodes and <br> elements until the loop has completed, and then add the fragment to the DOM:

var i = 0, il = titleDsc.length;
var dsc = document.getElementById('descript'),
var frag = document.createDocumentFragment( );

/* Loop through the array while there are elements */
do {
    frag.appendChild(document.createTextNode(titleDsc[i]));
    i++;
    /* Are there still elements to parse? */
    if (i < il)
        frag.appendChild(document.createElement('br'));
} while (i < il);
dsc.appendChild(frag);

In this new version of the code segment, a document fragment is created outside the DOM before we start looping. Then, the text nodes and <br> elements are added to the fragment inside the loop, before the fragment is finally added to the <div> element that is already part of the DOM document after the loop has completed.

Tip

Passing a document fragment using the appendChild( ) method actually appends the child or children of the fragment to the calling object and not to the fragment itself.

You can do other little things to speed up the execution of your code as well. For instance, did you know that you can define more than one variable with a single var statement? Moreover, it does not matter whether you define variables of different types within this single var. Remember, eliminating lines of code that the client must parse allows your code to run faster. Consider the following code segment that defines three variables:

var count = 3;
var title = 'The Coolest Page Ever';
var tabs = ['Tab One', 'Tab Two', 'Tab Three'];

We can use a single var statement to define all three of these variables, thus speeding up the segment:

var count = 3, title = 'The Coolest Page Ever', tabs = ['Tab One', 'Tab Two',
'Tab Three'];

Another simple way to speed up code execution is to store a value in a local variable if you need to use that value more than twice. This is even more important if you get the values from the property of an object or directly from an object in the DOM. For instance:

var fontSizes = p_xhrResponse.responseXML.getElementsByTagName(
'languageChanges').item(0).getElementsByTagName(
'fontSizes').item(0).firstChild.data;
var languages = p_xhrResponse.responseXML.getElementsByTagName(
'languageChanges').item(0).getElementsByTagName(
'languageSwitch').item(0).firstChild.data;
var mapLink = p_xhrResponse.responseXML.getElementsByTagName(
'languageChanges').item(0).getElementsByTagName(
'mapLink').item(0).firstChild.data;

document.getElementById('fontSizes').innerHTML = fontSizes;
document.getElementById('languages').innerHTML = languages;
document.getElementById('mapLink').innerHTML = mapLink;

You could use a variable to store the value of the root node in this code segment, as well as the responseXML, for that matter. You could optimize it like this:

var xmlDocument = p_xhrResponse.responseXML;
var root = xmlDocument.getElementsByTagName('languageChanges').item(0);
var fontSizes = root.getElementsByTagName('fontSizes').item(0).firstChild.data;
var languages = root.getElementsByTagName('languageSwitch').item(0).firstChild.data;
var mapLink = root.getElementsByTagName('mapLink').item(0).firstChild.data;

document.getElementById('fontSizes').innerHTML = fontSizes;
document.getElementById('languages').innerHTML = languages;
document.getElementById('mapLink').innerHTML = mapLink;

The last optimization techniques are related to the incrementing and decrementing operators. As a quick refresher to everyone, incrementing and decrementing operators increase and decrease their variables by one numeric value using (++) and (--), respectively. Whenever you are using these operators, consider your code and decide whether you can combine multiple statements into one statement. For example:

1 var i = 0, il = titleDsc.length;
2 var dsc = document.getElementById('descript'),
3 var frag = document.createDocumentFragment( );
4
5 /* Loop through the array while there are elements */
6 do {
7     frag.appendChild(document.createTextNode(titleDsc[i]));
8     i++;
9     /* Are there still elements to parse? */
10     if (i < il)
11         frag.appendChild(document.createElement('br'));
12 } while (i < il);
13 dsc.appendChild(frag);

You can combine the statements on lines 7 and 8 in the preceding code into one statement by placing the incrementing operator line of code into the preceding line:

var i = 0, il = titleDsc.length;
var dsc = document.getElementById('descript'),
var frag = document.createDocumentFragment( );

/* Loop through the array while there are elements */
do {

    frag.appendChild(document.createTextNode(titleDsc[i++]));
    /* Are there still elements to parse? */
    if (i < il)
        frag.appendChild(document.createElement('br'));
} while (i < il);
dsc.appendChild(frag);

This is possible because the incrementing operators in this case are placed postfix, or after the variable, and the value of i is increased after the rest of the statement has been executed. Remember to check your code to make sure that combining statements will not affect the outcome and will only help to reduce parsing.

Warning

Though it is always possible to combine incrementing and decrementing operator statements together with other statements, you must be careful when doing so. When the operator is placed postfix, the increment or decrement takes place after the whole statement has been executed. If the operator is placed prefix, or before the variable, however, the increment or decrement takes place when it is encountered, which may cause your whole statement to not execute as you had planned.

We could make other, more detailed optimizations as well, but they deal with less common coding statements and more complicated techniques. A good book on this subject is Speed Up Your Site: Web Site Optimization by Andrew B. King (New Riders).

Of course, the client side is not the only side of an Ajax application, and we will now turn our attention to server-side optimization techniques.

Server-Side Optimizations

On the server side of your Ajax application, it does not matter whether you remove whitespace and comments from your scripts. These scripts are solely for the server and do not need to be downloaded or parsed on the client. In fact, short of getting into an argument on whether a for loop or while loop is faster in an application (different code bases might implement this differently), there are no code techniques that can be implemented for the server code.

Instead, we should focus on things such as how quickly we can get data from the SQL database, and how quickly we can get page code to the client. How do we do this? Through server compression whenever possible, and through SQL optimization.

Compression

Earlier, we talked about implementing HTTP compression on the web server for better download times on the client. However, what is a developer to do when she does not have access to the server to make these changes? Luckily for her, she can still add compression from within the code itself.

In PHP, it is pretty simple to add compression to the server output, thanks to the PHP function ob_start( ), which turns output buffering on. This stores all output in an internal buffer that can then be acted upon before it is sent to the client. A callback function, provided as a parameter to the ob_start( ) function, is called before sending and is used to add the compression to the page (see Example 23-1).

Example 23-1. output.php: Adding compression to a site using PHP

<?php
/**
 * This file, output.php, handles all of the functionality that concerns itself
 * with the output being sent to the client.
 */

/**
 * This function, compress_output, is called just before the output is sent to
 * the client and determines if the output is to be compressed in any manner or
 * not.
 *
 * @param string $p_output The output buffer to be sent to the client.
 * @return string The string to be sent to the client, either compressed or
 *     left alone.
 */
function compress_output($p_output) {
    /* Is the length of the data even worth compacting? */
    if (strlen($p_output) >= 1000) {
        /* Get the compression method */
        $gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'),
        $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate'),
        $encoding = (($gzip) ? 'gzip' : (($deflate) ? 'deflate' : 'none'));

        /* Is this a buggy version of Internet Explorer? */
        if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
                preg_match('/^Mozilla/4.0 (compatible; MSIE ([0-9].[0-9])/i',
                $_SERVER['HTTP_USER_AGENT'], $matches)) {
            $version = floatval($matches[1]);

            /* Is this less than version 6? */
            if ($version < 6)
                $encoding = 'none';
            /* Is this a function IE 6? */
            if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
                $encoding = 'none';
        }
        /* Is this data to be compressed? */
        if ($encoding != 'none') {
            header('Content-Encoding: '.$encoding);
            $p_output = gzencode($p_output, 6, (($gzip) ? FORCE_GZIP :
                FORCE_DEFLATE));
            header('Content-Length: '.strlen($p_output));
        }
    }
    return ($p_output);
}

ob_start('compress_output'),
?>

We must add the output.php file to all the pages in the application that need compression added to them, like this:

<?php
include_once('includes/php/output.php'),

// Rest of code here...
?>

Other languages have similar ways of providing compression to their pages. Example 23-2 shows one more way to add compression to a page on the server side, this time using C# .NET.

Example 23-2. Adding compression to a site using C# .NET

using System;

namespace AjaxTDG.Output {
    /// <summary>
    ///     This class, Utilities, provides the utilities needed when outputting
    ///     data to the client.
    /// </summary>
    public class Utilities {
        /// <summary>
        ///     This method, CompressPage, detects the encoding types that the
        ///     client accepts, and compresses all output to the best compression
        ///     type possible.
        /// </summary>
        /// <permission cref="System.Security.PermissionSet">
        ///     Public Access
        /// </permission>
        public static void CompressPage( ) {
            string strEncoding =
                System.Web.HttpContext.Current.Request.Headers["Accept-Encoding"];
            bool bGZip = strEncoding.Contains("gzip");
            bool bDeflate = strEncoding.Contains("deflate");

            // Does the client accept a coding type to compress with?
            if (!string.IsNullOrEmpty(strEncoding) && (bGZip || bDeflate)) {
                System.Web.HttpResponse Response =
                    System.Web.HttpContext.Current.Response;

                // Does the client use gzip or deflate?
                if (bGZip)
                    Response.Filter =
                        new System.IO.Compression.GZipStream(Response.Filter,
                        System.IO.Compression.CompressionMode.Compress);
                else
                    Response.Filter =
                        new System.IO.Compression.DeflateStream(Response.Filter,
                        SSystem.IO.Compression.CompressionMode.Compress);
                Response.AppendHeader("Content-Encoding", strEncoding);
            }
        }
    }
}

You can then use this class within code-behind files in your application, like this:

using AjaxTDG.Output;

namespace AjaxTDG.CodeBehinds {
    /// <summary>
    ///        This class, Page1, is a sample page class for a code behind.
    /// </summary>
    public class Page1: System.Web.UI.Page {
        /// <summary>
        ///        This method, Page_Load, is where the compression should be
        ///        placed.
        /// </summary>
        /// <param name="sender">The object that called this method.</param>
        /// <param name="e">The event that caused this method to be called.</param>
        /// <permission cref="System.Security.PermissionSet">
        ///     Protected Access
        /// </permission>
        protected void Page_Load(object sender, EventArgs e) {
            Output.Utilities.CompressPage( );

            // Rest of code here...
        }
    }
}

Example 23-1 and Example 23-2 show great ways to add compression when you do not have access to the HTTP server to make modifications. The only unfortunate thing with this method is that it only compresses the data being sent by the page call. All calls for JavaScript, CSS, images, and so on will not be compressed with this compression method.

SQL Optimization

The quicker the data can be pulled from a SQL server, the faster the client can return the data. We do not want the slowest part of an Ajax application to be the data pull. Therefore, it is important that we optimize our SQL pulls as much as possible. Of course, we can tweak the server to make it run more efficiently. I do not profess to be an expert when it comes to SQL architecture, so I will refer you to the documentation that is specific to the SQL server you use to lead you down the right path. Also, some books do provide hints regarding where you can make changes to fine-tune the server. Some of these books are:

  • Understanding MySQL Internals by Sasha Pachev (O’Reilly)

  • Programming SQL Server 2005 by Bill Hamilton (O’Reilly)

  • Optimizing Oracle Performance by Carl Millsap (O’Reilly)

Instead of talking about optimizing the SQL server itself, I will concentrate on something I know more about—pulling data from the server. You can pull data from a SQL server in two ways: via inline queries and via stored procedures. And you can optimize both to make data return more quickly. Also, note that I am concentrating on bringing back data to the client, and not on the other create, read, update, and delete (CRUD) operations. This is because we can’t do much to make INSERT, UPDATE, or DELETE statements run much faster than they do naturally. Anything you can do you can find in books that specialize in SQL optimization, such as High Performance MySQL by Derek J. Balling and Jeremy Zawodny (O’Reilly).

Inline queries

Inline queries are SQL statements that are written dynamically by server code before being executed on the server. In general, these types of queries run more slowly than if you executed the same code in a stored procedure (more on this in the next section). Even though they are slower, there are a few good practices you can use that can lead to faster code statements. You should apply these best practices to the code in stored procedures as well.

The first good practice for returning data quicker is to only return the data that the application actually needs. For an example, take a look at the following SQL statement:

SELECT * FROM my_table WHERE column2 = valueX;

This kind of code is commonly written by developers, but it is not optimized. Why? This code queries a table and returns every column in the table that meets the given criteria. The problem with this is that if code like this is executed on a table that has millions of records and a good number of columns, the amount of data being returned will potentially be huge. Instead, the preceding code should look something like this:

SELECT column2, column3, column5 FROM my_table WHERE column2 = valueX;

Remember that you should never request more data than you actually need. By doing this, you can significantly speed up query execution time as well as the time it takes to process this data on the server by requiring less memory to deal with it.

Another good SQL keyword to avoid is UNION. When this must be executed against two SELECT statements, which hopefully have followed my first good practice or there will be real trouble here, it causes SQL to do a lot of work that will slow the execution time considerably. Try to avoid this at all costs. It is a speed killer, unless small SELECT results are being combined in this way. So, instead of doing something like this:

SELECT
    t1.column1,
    t1.column2,
    t1.column3,
    t2.column7
FROM
    table1 t1 INNER JOIN table2 t2 ON t1.column1 = t2.column2
WHERE
    t1.column1 = value1 AND
    t2.column7 IS NOT NULL
UNION
SELECT
    column1,
    column2,
    column3,
    column4
FROM
    table3
WHERE
    column1 = value1

you would do something like this:

CREATE TEMPORARY TABLE temp_table1(col1 INTEGER,
                                   col2 INTEGER,
                                   col3 VARCHAR(30),
                                   col4 DATE);
INSERT INTO temp_table1
SELECT
    t1.column1,
    t1.column2,
    t1.column3,
    t2.column7
FROM
    table1 t1 INNER JOIN table2 t2 ON t1.column1 = t2.column2
WHERE
    t1.column1 = value1 AND
    t2.column7 IS NOT NULL;
INSERT INTO temp_table1
SELECT
    column1,
    column2,
    column3,
    column4
FROM
    table3
WHERE
    column1 = value1;
SELECT * FROM temp_table1;

This solution will avoid the potential performance problems that UNION can give you, and will give you improved join performance should you need to join the UNION results to other tables. In fact, should you require another join, you could improve this even more by adding a primary key to the temporary table.

SQL, like most other languages, provides more than one way to accomplish almost anything. If you feel that a query you have written is not performing as quickly as it should, contact your database administrator, if you have one, and have him take a look. If a database administrator is not at your disposal, try forums or Internet relay chat (IRC), or refer to a book. There may be a better way to write your query.

Sometimes, however, what you wrote is not necessarily bad, but it still runs slowly. When this happens, first look at the indexes on the tables you are querying. Perhaps one or more tables require an additional index put on them. With MySQL, you can accomplish this with the following:

CREATE INDEX _ndx_table1_column2 ON table1 (column2);

This code puts an index called _ndx_table1_column2 on table table1 for column2, which the name obviously implies. Naming an index in a meaningful way can go a long way toward simplifying database maintenance and troubleshooting. This will keep your database administrator happier—something that is always worthwhile to do.

I want to make it clear that I am not suggesting that you throw indexes on tables just because your database queries are running slowly. You must do this correctly or you will end up with another problem: your database may start to take up too much space. This will make your database administrator angrier—something that is never worthwhile to do.

Indexes help out only so much.

If you are still having problems with speed, I can suggest one more thing. Check what SQL functions you are using in your code. Functions such as IFNULL( ) can affect the speed of a statement depending on how it is used. Take the following code, for example:

SELECT
    t1.col1,
    t1.col2,
    t2.col2 AS col3,
    IFNULL(t3.col2, 'none') AS col4,
    IFNULL(t3.col3, 'empty') AS col5
FROM
    table1 t1 INNER JOIN table2 t2 ON t1.col1 = t2.col1
    LEFT OUTER JOIN table3 t3 ON t1.col2 = t3.col1
WHERE
    t1.col3 IS NOT NULL AND
    t2.col2 = 1
GROUP BY
    t1.col1,
    t1.col2,
    t2.col2,
    IFNULL(t3.col2, 'none'),
    IFNULL(t3.col3, 'empty')

When a function must be called for every record multiple times, chances are good that this will slow your code. Some of the usual suspects (in MySQL) are functions such as CASE, NULLIF( ), REPLACE( ), SOUNDS_LIKE( ), and the function I already mentioned, IFNULL( ). Do not avoid these functions, as they do have real benefits, but beware of what could happen should you use them as I showed in the preceding example.

Stored procedures

Stored procedures will generally take less time to run than an inline query pulling the same results. However, stored procedures will have the same issues with performance as inline queries when the problems I just demonstrated exist in the stored procedure code. In fact, if you write a stored procedure incorrectly, it may not experience any speed gains over a dynamic query pulling those same results.

Stored procedures built with a number of parameters that may vary each time the procedure runs can hurt performance. When your stored procedure has been programmed to accept multiple parameters, but some of those parameters are optional and may not be used, do not write your stored procedure generically so that it does not care how many parameters it actually gets. Using this method can lead to unnecessary joins to tables that do not need to be joined, based on the parameters that were passed. These unnecessary joins lead to a small performance hit that can be avoided. Instead of coding your stored procedure generically, include IF...ELSE logic into your stored procedure and write separate queries for each combination of parameters. Now, you can ensure that the stored procedure runs as efficiently as possible.

The trick, however, is that you must take this one step further so that your SQL server does not recompile the stored procedure every time, and that is to call other stored procedures that handle every combination of possible parameters. For example:

CREATE PROCEDURE example1_sp (param1 INTEGER, param2 INTEGER, param3 INTEGER)
BEGIN
    IF param1 IS NULL AND param2 IS NULL AND param3 IS NULL THEN
        CALL example2_sp
    ELSEIF param2 IS NULL AND param3 IS NULL THEN
        CALL example3_sp(param1)
    ELSEIF param3 IS NULL THEN
        CALL example4_sp(param1, param2)
    ELSE
        CALL example5_sp(param1, param2, param3)
    END IF
END

With this type of stored procedure, the server will always optimize a query plan, and you will not suffer the speed losses that would occur should stored procedures not be called from within the main stored procedure.

Tip

Varying parameter options in a stored procedure leads to parameter sniffing. The SQL server will use the values of the parameters it is first called with to build a query plan. As the number of variables changes with subsequent calls, the query plan must be rebuilt, which leads to performance loss.

The best advice I can offer on stored procedures, and on SQL statements in general, is that you should always use EXPLAIN to analyze the plan the query will use to execute the SQL statements. It is easiest to spot potential problems from the query plan, and to fix them, before they become issues in production.

Ajax Optimization

The way to optimize any Ajax application is to find the best method to optimize every element that may go into it. It is important that the application run as quickly and efficiently as possible. In this chapter, we looked at the different Ajax elements that can be optimized, as well as some web server optimization techniques. An application built on the Web must run as quickly as possible for people to believe it works just as well as a desktop application. Optimizing the application is the way to achieve this.

Communication

The communication between the client and the server is perhaps the most important part of an Ajax application, as this is the heart of Ajax programming in general. Ajax cannot succeed as a technology unless it is proven to be a stable and fast means of communication between the client and the server. Ajax’s stability comes down to your application being able to handle both the good and the bad so that there is nothing the user identifies as unusual. This means good error handling for bad data and efficient data handling for good data.

An Ajax application is fast when there is no huge delay in receiving new data from the server. Optimizing two areas will help your application succeed here. The first is to compress all data sent to the client from the server. This is important in terms of quick data transport. The second concerns the data itself. The data that is sent back and forth, both from the client and from the server, should be optimized as much as possible as well.

Data

When sending data to the server, send what would be considered the minimum amount of information. If you are sending key/value pairs, keep both the key and the value small. Enumerate choices whenever possible. Instead of something like this:

user_choice=add_data_to_database&data1=value1&data2=value2

consider letting each choice be set to a single value, and send that value instead:

c=3&d1=value1&d2=value2

Smaller data makes it harder for intercepted information to be interpreted (a good security benefit) and keeps the size of the data that needs to be sent and parsed smaller.

For data being sent from the server, send what makes the most sense for the application. This means that sometimes, sending JSON will make it easier for the client to parse and use the response. Other times, it will make more sense to send an XML string containing an XHTML fragment that can be inserted directly into the DOM document of the requesting page.

Code Optimization

The other important part of Ajax optimization is to optimize the JavaScript code that is executed on the client, and to create the fastest data retrieval that you can. We looked at the ways inline SQL queries can be optimized to pull the best data set to be sent to the server. We also looked at stored procedures, and how to make sure they are used as efficiently as possible. These optimizations lead to data being sent back to the client as quickly as possible.

On the client, I showed you a number of JavaScript techniques that will help to increase the execution time of your code. This is especially important when it comes to the DOM manipulation that your code may need to do when the server receives an Ajax response.

Nothing says that your Ajax application will not run smoothly and efficiently without any optimization. In most cases, the speed of Ethernet connections, the processing power of computers, and the better implementation of JavaScript in browsers will ensure that your applications run well. Optimization will give you an application that runs that much faster. For the user, faster is always better.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset