Orange is my favorite color

Here’s how to do serious scaling with PostgreSQL for pennies:

  1. Use pgpool for replication, load-balancing and failover
  2. Run the whole thing on Amazon Web Services like Soocial

Done. There were some examples in AWS’ docs suggesting an “average web database of 100gb” with 100 I/Os per second would cost around $36/month to run. I’m pretty sure I already pay $36/month for just the electricity in my server cabinet.

My only concern is durability… it seems like there are still some hoops to jump through to guarantee a node failure doesn’t take all of your data with it.

Anyone have experience running ColdFusion on AWS/EC2?

I have been working with a contractor in India recently on a Javascript project using Ext. I wanted to outsource the front-end development since we have extensive APIs that would take an outside developer longer to get up to speed on. Due to PCI DSS and general security practices however, we can’t just let the contractor log in and push code or make changes to our development server. In fact, we didn’t even give him Subversion access meaning he would have to code against our data services remotely.

The Setup

Development server at dev.domain.com. Off-site developer building an application on his laptop and connecting his AJAX/xmlHttpRequest calls to dev.domain.com.

The Problem

Browsers restrict cross-domain requests for security purposes. So Javascript at http://localhost can’t retrieve data from a remote API like http://dev.domain.com. In Firefox + Firebug, you would see the following:

Error: uncaught exception: Permission denied to call method XMLHttpRequest.open

There is a hack for Firefox but that doesn’t help you with IE, Safari or Chrome. We started out this way but when it came time to deploy we found issues with Safari and Chrome.

These were things we could have detected much earlier in the development cycle had the Javascript developer been able to test in those browsers. It was a big mistake on our part to delay the cross-browser testing and it’s a testament to the stability of the Ext components that we didn’t have 10,000 other issues.

The Solution

If you’re developing with Apache, the answer is quite simple actually: use a reverse proxy. Apache’s mod_proxy will take a request for something like “/foo” and actually tunnel the request to some remote destination like “http://dev.domain.com/bar”. The end result is that your web browser thinks you’ve made a call to http://localhost/foo but in reality you’re sending and retrieving data from a remote server. Security implications solved!

I searched for a long time but never found an intersection between AJAX development, cross-domain xmlHttpRequest restrictions and Apache’s mod_proxy. It wasn’t until I thought, “hey, that might work” and started searching specifically for reverse proxy details did I turn up an example.

Apache Configuration

This is a pretty basic Apache feature - first you will need to load the required modules:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so

Let’s assume that I want to access a file at http://dev.domain.com/remote/api.php. You would put all of the following into a <VirtualHost>:

# start mod_rewrite
RewriteEngine On

ProxyRequests Off
<Proxy>
	Order deny,allow
	Allow from all
</Proxy>

ProxyPass /apitest/ http://dev.domain.com/remote/api/
ProxyPassReverse /apitest/ http://dev.domain.com/remote/api/
RewriteRule ^/apitest/(.*)$ /remote/api/$1 [R]

Restart Apache and make a browser request to http://localhost/apitest/api.php and you should receive a response from the remote server at http://dev.domain.com/remote/api/api.php. The RewriteRule will pass along any query-string parameters too. Done!

If you use IIS instead of Apache, you should be able to do something similar with ISAPI_REWRITE to accomplish the same functionality. This is also applicable to other technologies like Adobe Flex or Flash which also have cross-domain restrictions.

The advantage over other solutions like a vanilla proxy server or server-side script that translates the request for you is that it requires few, if any, changes to your code. It’s also payload-agnostic so it will work with any number of remote services without care for the request or response format.

There’s one final tweak we could make so our development environment mirrors our staging and production servers. If the final endpoint for the Javascript will be:

http://dev.domain.com/service/foo?var=value

Then we could use the same relative URL “/service/foo?var=value” and make our reverse proxy mirror that structure:

ProxyPass /service/ http://dev.domain.com/service/
ProxyPassReverse /service/ http://dev.domain.com/service/ 

Now there is no change necessary in the Javascript; just make your request to “/service/foo?var=value” on either localhost or dev.domain.com and the browser will be properly routed to the right destination without running into any security restrictions!

In my previous post, I detailed my desire to convert from char(35) to Postgres’ native uuid datatype for storing ColdFusion UUIDs. I ran into a handful of problems and Andrew shared this tidbit of Postgres SQL that I haven’t used before:

ALTER TABLE uuidtest ALTER COLUMN uidTest TYPE uuid USING uidTest::uuid;

I ran this on my test table and sure enough it worked! I got the following data back when I selected all rows from the table:

"c5f25cc3-1d72-822b-7957-06e5a2c8f6b1"
"a5f01cc3-1d72-822b-7957-06e5a2c8f6b1"
"d6f25cc3-1d72-822b-7957-06e5a2c8f6b1"
"c5fc2cc3-1d72-822b-7957-06e5a2c8f6b1"
"e5f25cc3-1d72-822b-7957-06e5a2c8f6b1"

And these values passed the isValid(’guid’, uidTest) test with flying colors - conversion complete! On this table at least. Since my production application actually has 75 tables, the next test was to try it with foreign keys:

CREATE TABLE uuidtest (
	uidTest CHAR(35) NOT NULL PRIMARY KEY
) WITHOUT OIDS;

INSERT INTO uuidtest VALUES ('C5F25CC3-1D72-822B-795706E5A2C8F6B1');
INSERT INTO uuidtest VALUES ('A5F01CC3-1D72-822B-795706E5A2C8F6B1');
INSERT INTO uuidtest VALUES ('D6F25CC3-1D72-822B-795706E5A2C8F6B1');
INSERT INTO uuidtest VALUES ('C5FC2CC3-1D72-822B-795706E5A2C8F6B1');
INSERT INTO uuidtest VALUES ('E5F25CC3-1D72-822B-795706E5A2C8F6B1');

CREATE TABLE uuidtest_ref (
	uidTest CHAR(35) NOT NULL REFERENCES uuidtest(uidTest) ON UPDATE CASCADE ON DELETE CASCADE
) WITHOUT OIDS;

INSERT INTO uuidtest_ref VALUES ('C5F25CC3-1D72-822B-795706E5A2C8F6B1');
INSERT INTO uuidtest_ref VALUES ('A5F01CC3-1D72-822B-795706E5A2C8F6B1');

I tried all combinations of transaction blocks, deferred constraints and disabled triggers but no love. All tests resulted in the same error, blammo:

ERROR: foreign key constraint "uuidtest_ref_uidtest_fkey" cannot be implemented
SQL state: 42804
Detail: Key columns "uidtest" and "uidtest" are of incompatible types: character and uuid.

I decided to hit up my local SFPUG mailing list in hopes of a miracle solution but Josh Berkus and Dirk Jagdmann suggested dropping and recreating the foreign keys as part of the transaction. That’s not great, but it’s a whole lot better than the copy-delete-rename dance typically required by these kinds of operations.

Test Case Solution

Based on all of the feedback so far, here is the code for the conversion process. It requires identifying all of the foreign keys tied to char(35) columns in advance:

BEGIN;

-- drop constraints
ALTER TABLE uuidtest_ref DROP CONSTRAINT uuidtest_ref_uidtest_fkey;

-- update all columns
ALTER TABLE uuidtest ALTER COLUMN uidTest TYPE uuid USING CAST(regexp_replace(uidTest, '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2') AS uuid);
ALTER TABLE uuidtest_ref ALTER COLUMN uidTest TYPE uuid USING CAST(regexp_replace(uidTest, '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2') AS uuid);

-- restore constraints
ALTER TABLE uuidtest_ref ADD CONSTRAINT uuidtest_ref_uidtest_fkey FOREIGN KEY (uidTest) REFERENCES uuidtest(uidTest) ON UPDATE CASCADE ON DELETE CASCADE;

COMMIT;

Production Solution

Ok, we’ve established in our simple test case this will work. Now how do we do this database-wide? There are some ways to automate this but it will still require a bit of cut and paste. Start by dumping out the schema for the database:

pg_dump --format=p --file=schema.sql --schema-only --clean --username=<username> <database name>

The –clean will ensure that we get both DROP and ADD statements for the constraints. Open this file up in a text editor. Starting right at the very top you will have the DROP statements that look like this and cut and paste them into the “drop” portion of your SQL transaction:

ALTER TABLE ONLY public.tbllookupevent DROP CONSTRAINT xpftbllookupeventtype_fk;

Then search ahead for the first instance of “REFERENCES” in the schema.sql file. Start copying and pasting that block of statements into the “restore” portion of your SQL transaction:

ALTER TABLE ONLY tbllookupevent
    ADD CONSTRAINT "$1" FOREIGN KEY (uidclub) REFERENCES tbllookupclub(uidclub)
ON UPDATE CASCADE ON DELETE RESTRICT;

Now the last part you have to worry about is generating all of the actual ALTER COLUMN statements. Ugh. That could be ugly. Except we can use the information_schema tables and some concatenation to spit this out straight from the database:

SELECT 'ALTER TABLE ' || TABLE_NAME || ' ALTER COLUMN ' || COLUMN_NAME || ' TYPE uuid USING CAST(regexp_replace(' || COLUMN_NAME || ', \'([A-Z0-9]{4})([A-Z0-9]{12})\', E\'\\\\1-\\\\2\') AS uuid);' AS SQL
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'public'
AND data_type = 'character'
AND character_maximum_length = 35
AND table_name NOT IN (SELECT table_name FROM information_schema.views)
order by TABLE_NAME, ORDINAL_POSITION

We add a little extra filtering to be sure we don’t include views (which we don’t want to ALTER). The output looks like:

"ALTER TABLE tblmapthemes ALTER COLUMN uidclub TYPE uuid USING CAST(regexp_replace(uidclub, '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2') AS uuid);"
"ALTER TABLE tblmapthemes ALTER COLUMN uidtheme TYPE uuid USING CAST(regexp_replace(uidtheme, '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2') AS uuid);"
"ALTER TABLE tblmappackages ALTER COLUMN uidpackage TYPE uuid USING CAST(regexp_replace(uidpackage, '([A-Z0-9]{4})([A-Z0-9]{12})', E'\\1-\\2') AS uuid);"

Now take those results and paste them between your DROP and CREATE statements. Be sure you have the BEGIN and COMMIT transaction wrapped around it so if something blows up it will roll back the entire set of modifications. Now let it rip!

I haven’t run this entire transaction yet as I still need to test using GUIDs with Transfer and explore changing my isValid(’uuid’, …) checks in my application to use the GUID instead. I will also need to write a either a mod_rewrite rule or some Model-Glue controller code to look for and convert legacy 35-character UUIDs into the new 36-character format.

Hope this is interesting - please leave a comment if you attempt this conversion and if you run into any other gotchas!

Quick tip for anyone using ColdFusion’s serializeJSON routine for Javascript applications. The type conversion from CF to JSON is finicky and there are times when you need more finely grained control. Specifically using an Ext combobox, we ran into an issue where an <option> value of 0 was treated as false preventing selection of that item.

SerializeJSON encodes numeric values as floats so 0 becomes 0.0, 1 becomes 1.0, and so on. The solution is to pass the combobox a string (’0′) instead of a number (0). While JavaCast and other techniques failed, it is possible by putting a leading space on your numeric value:

<cfset records["someValue"] = ' 0' />
<cfreturn serializeJSON(records) />

The result is {”someValue”: ” 0″} which Javascript (or at least Ext’s combobox) handles just fine. Nice little trick to have in the toolbox when you need to treat numeric data like strings.