<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PlanetMysql.ru - информация о СУБД MySQL &#187; Development</title>
	<atom:link href="http://planetmysql.ru/category/development/feed/" rel="self" type="application/rss+xml" />
	<link>http://planetmysql.ru</link>
	<description>Блог о самой популярной СУБД MySQL</description>
	<lastBuildDate>Tue, 07 Sep 2010 07:50:51 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Scribd is Hiring (I’m Looking for an Operations Engineer to Join My Team)</title>
		<link>http://feedproxy.google.com/~r/Homo-Adminus/~3/PqbAzRHL-mg/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=scribd-is-hiring-i%25e2%2580%2599m-looking-for-an-operations-engineer-to-join-my-team</link>
		<comments>http://feedproxy.google.com/~r/Homo-Adminus/~3/PqbAzRHL-mg/#comments</comments>
		<pubDate>Tue, 17 Aug 2010 15:51:06 +0000</pubDate>
		<dc:creator>Alexey Kovyrin</dc:creator>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Databases]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Links]]></category>
		<category><![CDATA[Operations]]></category>
		<category><![CDATA[Work]]></category>
		<category><![CDATA[admin]]></category>
		<category><![CDATA[networks]]></category>
		<category><![CDATA[scribd]]></category>

		<guid isPermaLink="false">http://kovyrin.net/?p=502</guid>
		<description><![CDATA[Scribd is a top 100 site on the web and one of the largest sites built using Ruby on Rails. As one of the first rails sites to reach scale, we&#8217;ve built a lot of infrastructure and solved a lot of challenges to get Scribd to where it is today. We actively try to push the envelope and have contributed substantial work back to the open source community.
Scribd has an agile, startup culture and an unusually close working relationship between engineering and ops. You&#8217;ll regularly find cross-over work at Scribd, with ops people writing application-layer code and engineers figuring out operations-level problems. We think we&#8217;re able to make that work because of the uniquely talented people we have on the team.
To allow us to keep scaling, we&#8217;re now looking to add a strong, experienced operations guru to the team. As a member of Scribd operations, you&#8217;ll have tremendous ownership and responsibility for one of the web&#8217;s most popular applications. Because Scribd is a startup, you will wear many hats and have broader responsibility than you would at a larger company.
If you read this blog, you should already have a good sense of the kind of work you&#8217;ll be doing on this position. 
The Ideal Profile
You are an experienced operations professional and have run ops at at least one large-scale website. You have comprehensive knowledge of a broad variety of system tools, from MySQL and Nginx to Squid and Memcached. You should also have strong software development skills and be well-versed in major programming languages. You should be strongly motivated, a creative solution finder, and ready to jump into the thorniest technical problems whenever necessary.
 Responsibilities

Develop and maintain all aspects of Scribd&#8217;s operations infrastructure, including system monitoring, backups, server configuration, databases, and caching systems
Collaborate with engineering to create next generation infrastructure to support changing requirements
Predict scaling problems before they occur and work with engineering to prevent them
Write and debug application level ruby code
Participate in an on-call rotation
Quickly diagnose server problems and employ preventive measures to maintain high availability servers

Qualifications

Bachelors degree in CS or equivalent experience
3-5 years of professional experience in site operations
Strong software engineering skills, including knowledge of major programming languages
Strong database skills, preferably with MySQL, and overall linux knowledge
Experience with most of the following technologies: MySQL, Nginx, Ruby, Memcached, Squid, git, Solr, HBase, Postfix
Proven ability to quickly learn and implement unfamiliar technologies
Strong desire to work hard at a rapidly growing company

Location: You are preferably located near San Francisco, CA. Relocation assistance is designed on a per-case basis. In short, we&#8217;ll be creative to get you here.
Contact: Please send your email cover letter and resume with the subject &#8220;Your name – Senior Site Operations Engineer – via Kovyrin.net&#8221; to jobs@scribd.com or contact me directly using any of my contacts. All communication and correspondence is held in the strictest confidence to ensure that you can connect and learn more without exposure.



  
]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.scribd.com/">Scribd</a> is a <a href="http://rails100.pbworks.com/Alexa+Rankings">top 100 site</a> on the web and one of the largest sites built using Ruby on Rails. As one of the first rails sites to reach scale, we&#8217;ve built a lot of infrastructure and solved a lot of challenges to get Scribd to where it is today. We actively try to push the envelope and have contributed substantial work back to the open source community.</p>
<p>Scribd has an agile, startup culture and an unusually close working relationship between engineering and ops. You&#8217;ll regularly find cross-over work at Scribd, with ops people writing application-layer code and engineers figuring out operations-level problems. We think we&#8217;re able to make that work because of the uniquely talented people we have on the team.</p>
<p>To allow us to keep scaling, we&#8217;re now looking to add a strong, experienced operations guru to the team. As a member of Scribd operations, you&#8217;ll have tremendous ownership and responsibility for one of the web&#8217;s most popular applications. Because Scribd is a startup, you will wear many hats and have broader responsibility than you would at a larger company.</p>
<p>If you read this blog, you should already have a good sense of the kind of work you&#8217;ll be doing on this position. </p>
<p><strong>The Ideal Profile</strong></p>
<p>You are an experienced operations professional and have run ops at at least one large-scale website. You have comprehensive knowledge of a broad variety of system tools, from MySQL and Nginx to Squid and Memcached. You should also have strong software development skills and be well-versed in major programming languages. You should be strongly motivated, a creative solution finder, and ready to jump into the thorniest technical problems whenever necessary.</p>
<p><strong> Responsibilities</strong></p>
<ul>
<li>Develop and maintain all aspects of Scribd&#8217;s operations infrastructure, including system monitoring, backups, server configuration, databases, and caching systems</li>
<li>Collaborate with engineering to create next generation infrastructure to support changing requirements</li>
<li>Predict scaling problems before they occur and work with engineering to prevent them</li>
<li>Write and debug application level ruby code</li>
<li>Participate in an on-call rotation</li>
<li>Quickly diagnose server problems and employ preventive measures to maintain high availability servers</li>
</ul>
<p><strong>Qualifications</strong></p>
<ul>
<li>Bachelors degree in CS or equivalent experience</li>
<li>3-5 years of professional experience in site operations</li>
<li>Strong software engineering skills, including knowledge of major programming languages</li>
<li>Strong database skills, preferably with MySQL, and overall linux knowledge</li>
<li>Experience with most of the following technologies: MySQL, Nginx, Ruby, Memcached, Squid, git, Solr, HBase, Postfix</li>
<li>Proven ability to quickly learn and implement unfamiliar technologies</li>
<li>Strong desire to work hard at a rapidly growing company</li>
</ul>
<p>Location: <strong>You are preferably located near San Francisco, CA</strong>. Relocation assistance is designed on a per-case basis. In short, we&#8217;ll be creative to get you here.</p>
<p>Contact: Please send your email cover letter and resume with the subject &#8220;Your name – Senior Site Operations Engineer – via Kovyrin.net&#8221; to jobs@scribd.com or contact me directly using <a href="http://kovyrin.net/contact/">any of my contacts</a>. All communication and correspondence is held in the strictest confidence to ensure that you can connect and learn more without exposure.</p>

<p><a href="http://feedads.g.doubleclick.net/~a/RWGCGmnRKReW6byQK16_MflmLOE/0/da"><img src="http://feedads.g.doubleclick.net/~a/RWGCGmnRKReW6byQK16_MflmLOE/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/RWGCGmnRKReW6byQK16_MflmLOE/1/da"><img src="http://feedads.g.doubleclick.net/~a/RWGCGmnRKReW6byQK16_MflmLOE/1/di" border="0" ismap="true"></img></a></p><div>
<a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=PqbAzRHL-mg:y4-nncz8nKI:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=PqbAzRHL-mg:y4-nncz8nKI:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=PqbAzRHL-mg:y4-nncz8nKI:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=PqbAzRHL-mg:y4-nncz8nKI:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=PqbAzRHL-mg:y4-nncz8nKI:V_sGLiPBpWU" border="0"></img></a>
</div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25576&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25576&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://feedproxy.google.com/~r/Homo-Adminus/~3/PqbAzRHL-mg/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Oracle legal move evokes many questions</title>
		<link>http://feedproxy.google.com/~r/451opensource/~3/O13mTw0ChSw/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=oracle-legal-move-evokes-many-questions</link>
		<comments>http://feedproxy.google.com/~r/451opensource/~3/O13mTw0ChSw/#comments</comments>
		<pubDate>Fri, 13 Aug 2010 18:30:11 +0000</pubDate>
		<dc:creator>The 451 Group</dc:creator>
				<category><![CDATA[451 group]]></category>
		<category><![CDATA[451caostheory]]></category>
		<category><![CDATA[451group]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Linux Foundation]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Open Invention Network]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[caostheory]]></category>
		<category><![CDATA[court]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[glassfish]]></category>
		<category><![CDATA[jay lyman]]></category>
		<category><![CDATA[jaylyman]]></category>
		<category><![CDATA[law]]></category>
		<category><![CDATA[lawsuit]]></category>
		<category><![CDATA[lawyers]]></category>
		<category><![CDATA[legal]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[oin]]></category>
		<category><![CDATA[open source software]]></category>

		<guid isPermaLink="false">http://blogs.the451group.com/opensource/?p=2201</guid>
		<description><![CDATA[There are many questions that arise out of Oracle&#8217;s copyright and patent infringement complaint against Google regarding its use of Java in Android. There are several things that make the suit significant to the entire industry: it centers not just on software copyright, but also software patents (an increasingly and hotly debated issue), the quickly-expanding smartphone market and open source software. The first question is: what is Oracle doing?
Many are speculating that this is simply an effort to further and more effectively monetize Java, a storied program language that has move more toward openness and survived several supposed death sentences as newer languages arrived. Still, with all of the open source parts &#8212; GlassFish application server, MySQL database, OpenOffice.org suite &#8212; is Java the most significant to Oracle? It may be, but regardless of what Oracle is doing, its legal moves here may certainly have an impact on the many other open source projects from Sun that are now under Oracle&#8217;s umbrella.
Oracle may also simply be initiating an IP licensing effort around Java, but as Microsoft has found, this can be a delicate endeavor to say the least. Another possibility is that Oracle, not typically mentioned or meaningful when we discuss the hot market of smartphones, wants to make sure the world knows its Java code is in many of that Android technology. Still, there are more constructive ways to go about that, I would think.
We have questioned Oracle&#8217;s full appreciation for open source software before, but its latest action simply brings more questions to mind.
The smartphone market is seeing incredible opportunity, competition and innovation right now? In addition, with waves of iPhone and more recently Android popularity, the smartphone market might even be poised for a slow in growth (even though it is by many accounts the fastest growing technology market). Still, if there is some slowing that was part of the natural market cycle, will Oracle take some or even all of the blame?
Given that Google is adept at software development and using open source, we also have to wonder about the impact of any and all major workarounds. Plans may already be well underway to circumvent the use of Java in Android and any range of other devices or markets where it has managed to stay relevant despite its age. This could finally make Java less relevant, or at the least have a negative effect on Java development going forward.
One thing seems clear, Oracle&#8217;s move makes all that software patent discussion and debate more relevant and more real. We have sensed a coming storm over software patents, but we did not anticipate a first shot from Oracle, frankly. One of the biggest questions now is what kind of reaction will this trigger from the likes of the Open Invention Network, Linux Foundation (of which Oracle is a Platinum member and Google is a Gold member) or others with resources and interest in legally defending Linux and open source software?
]]></description>
			<content:encoded><![CDATA[<p>There are many questions that arise out of Oracle&#8217;s copyright and patent infringement complaint against Google regarding its use of Java in Android. There are several things that make the suit significant to the entire industry: it centers not just on software copyright, but also software patents (an increasingly and hotly debated issue), the quickly-expanding smartphone market and open source software. The first question is: what is Oracle doing?</p>
<p>Many are speculating that this is simply an effort to further and more effectively monetize Java, a storied program language that has move more toward openness and survived several supposed death sentences as newer languages arrived. Still, with all of the open source parts &#8212; GlassFish application server, MySQL database, OpenOffice.org suite &#8212; is Java the most significant to Oracle? It may be, but regardless of what Oracle is doing, its legal moves here may certainly have an impact on the <a href="http://blogs.the451group.com/opensource/2010/07/14/judgment-day-for-open-source-at-oracle/">many other open source projects</a> from Sun that are now under Oracle&#8217;s umbrella.</p>
<p>Oracle may also simply be initiating an IP licensing effort around Java, but as <a href="http://blogs.the451group.com/opensource/2009/07/16/dont-confuse-microsofts-ip-with-linux/">Microsoft has found</a>, this can be a delicate endeavor to say the least. Another possibility is that Oracle, not typically mentioned or meaningful when we discuss the hot market of smartphones, wants to make sure the world knows its Java code is in many of that Android technology. Still, there are more constructive ways to go about that, I would think.</p>
<p>We have <a href="http://blogs.the451group.com/opensource/2009/04/20/oracle-buys-sun-but-does-it-buy-open-source/">questioned</a> Oracle&#8217;s full appreciation for open source software before, but its latest action simply brings more questions to mind.</p>
<p>The smartphone market is seeing incredible opportunity, competition and innovation right now? In addition, with waves of iPhone and more recently Android popularity, the smartphone market might even be poised for a slow in growth (even though it is by many accounts the fastest growing technology market). Still, if there is some slowing that was part of the natural market cycle, will Oracle take some or even all of the blame?</p>
<p>Given that Google is adept at software development and using open source, we also have to wonder about the impact of any and all major workarounds. Plans may already be well underway to circumvent the use of Java in Android and any range of other devices or markets where it has managed to stay relevant despite its age. This could finally make Java less relevant, or at the least have a negative effect on Java development going forward.</p>
<p>One thing seems clear, Oracle&#8217;s move makes all that software patent discussion and debate more relevant and more real. We have sensed a coming storm over software patents, but we did not anticipate a first shot from Oracle, frankly. One of the biggest questions now is what kind of reaction will this trigger from the likes of the Open Invention Network, Linux Foundation (of which Oracle is a Platinum member and Google is a Gold member) or others with resources and interest in legally defending Linux and open source software?</p>
<img src="http://feeds.feedburner.com/~r/451opensource/~4/O13mTw0ChSw" height="1" width="1" /><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25556&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25556&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://feedproxy.google.com/~r/451opensource/~3/O13mTw0ChSw/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Table refactoring &amp; application version upgrades, Part II</title>
		<link>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-ii?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=table-refactoring-application-version-upgrades-part-ii</link>
		<comments>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-ii#comments</comments>
		<pubDate>Thu, 12 Aug 2010 03:24:06 +0000</pubDate>
		<dc:creator>Shlomi Noach</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2801</guid>
		<description><![CDATA[Continuing Table refactoring &#38; application version upgrades, Part I, we now discuss code &#38; database upgrades which require DROP operations. As before, we break apart the upgrade process into sequential steps, each involving either the application or the database, but not both.
As I&#8217;ll show, DROP operations are significantly simpler than creation operations. Interestingly, it the same as in life.
DROP COLUMN
A column turns to be redundant, unused. Before it is dropped from the database, we must ensure no one is using it anymore. The steps are:

App: V1 -&#62; V2. Remove all references to column; make sure no queries use said column.
DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is DROP COLUMN.

DROP INDEX
A possibly simpler case here. Why would you drop an index? Is it because you found out you never use it anymore? Then all you have to do is just drop it.
Or perhaps you don&#8217;t need the functionality the index supports anymore? Then first drop the functionality:

(optional) App: V1 -&#62; V2. Discard using functionality which relies on index.
DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is DROP INDEX. Check out InnoDB Plugin here.

DROP UNIQUE INDEX
When using Master-Slave failover for table refactoring, we&#8217;re now removing a constraint from the slave. Since the master is more constrained than the slave, there is no problem here. It&#8217;s mostly the same as with a normal DROP INDEX, with a minor addition:

(optional) App: V1 -&#62; V2. Discard using functionality which relies on index.
DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is DROP INDEX.
(optional) App: V2 -&#62; V3. Enable functionality that inserts duplicates.

DROP FOREIGN KEY
Again, we are removing a constraint.

DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is DROP INDEX.
(optional) App: V2 -&#62; V3. Enable functionality that conflicts with removed constraint. I mean, if you really know what you are doing.

DROP TABLE
The very simple steps are:

App: V1 -&#62; V2. Make sure no reference to table is made.
DB: V1 -&#62; V2. Issue a DROP TABLE.

With ext3 dropping a large table is no less than a nightmare. Not only does the action take long time, it also locks down the table cache, which very quickly leads to having dozens of queries hang. xfs is a good alternative.
Conclusion
We looked at single table operations, coupled with application upgrades. By carefully looking at the process breakdown, multiple changes can be addressed with ease and safety. Not all operations are completely safe when used with replication failover. But they are mostly safe if you have some trust in your code.]]></description>
			<content:encoded><![CDATA[<p>Continuing <a href="http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-i">Table refactoring &amp; application version upgrades, Part I</a>, we now discuss code &amp; database upgrades which require <strong>DROP</strong> operations. As before, we break apart the upgrade process into sequential steps, each involving either the application or the database, but not both.</p>
<p>As I&#8217;ll show, DROP operations are significantly simpler than creation operations. Interestingly, it the same as in life.</p>
<h4>DROP COLUMN</h4>
<p>A column turns to be redundant, unused. Before it is dropped from the database, we must ensure no one is using it anymore. The steps are:</p>
<ol>
<li>App: <strong>V1</strong> -&gt; <strong>V2</strong>. Remove all references to column; make sure no queries use said column.</li>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>DROP COLUMN</strong>.</li>
</ol>
<h4>DROP INDEX</h4>
<p>A possibly simpler case here. Why would you drop an index? Is it because you found out you never use it anymore? Then all you have to do is just drop it.</p>
<p>Or perhaps you don&#8217;t need the functionality the index supports anymore? Then first drop the functionality:</p>
<ol>
<li>(optional) App: <strong>V1</strong> -&gt; <strong>V2</strong>. Discard using functionality which relies on index.</li>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>DROP INDEX</strong>. Check out InnoDB Plugin here.<span></span></li>
</ol>
<h4>DROP UNIQUE INDEX</h4>
<p>When using Master-Slave failover for table refactoring, we&#8217;re now removing a constraint from the slave. Since the master is more constrained than the slave, there is no problem here. It&#8217;s mostly the same as with a normal DROP INDEX, with a minor addition:</p>
<ol>
<li>(optional) App: <strong>V1</strong> -&gt; <strong>V2</strong>. Discard using functionality which relies on index.</li>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>DROP INDEX</strong>.</li>
<li>(optional) App: <strong>V2</strong> -&gt; <strong>V3</strong>. Enable functionality that inserts duplicates.</li>
</ol>
<h4>DROP FOREIGN KEY</h4>
<p>Again, we are removing a constraint.</p>
<ol>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>DROP INDEX</strong>.</li>
<li>(optional) App: <strong>V2</strong> -&gt; <strong>V3</strong>. Enable functionality that conflicts with removed constraint. I mean, if you really know what you are doing.</li>
</ol>
<h4>DROP TABLE</h4>
<p>The very simple steps are:</p>
<ol>
<li>App: <strong>V1</strong> -&gt; <strong>V2</strong>. Make sure no reference to table is made.</li>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong>. Issue a <strong>DROP TABLE</strong>.</li>
</ol>
<p>With <strong>ext3</strong> dropping a large table is no less than a nightmare. Not only does the action take long time, it also locks down the table cache, which very quickly leads to having dozens of queries hang. <strong>xfs</strong> is a good alternative.</p>
<h4>Conclusion</h4>
<p>We looked at single table operations, coupled with application upgrades. By carefully looking at the process breakdown, multiple changes can be addressed with ease and safety. Not all operations are completely safe when used with replication failover. But they are mostly safe if you have some trust in your code.</p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25545&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25545&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-ii/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Table refactoring &amp; application version upgrades, Part I</title>
		<link>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-i?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=table-refactoring-application-version-upgrades-part-i</link>
		<comments>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-i#comments</comments>
		<pubDate>Tue, 10 Aug 2010 12:36:28 +0000</pubDate>
		<dc:creator>Shlomi Noach</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[indexing]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2775</guid>
		<description><![CDATA[A developer&#8217;s major concern is: How do I do application &#38; database upgrades with minimal downtime? How do I synchronize between a DB&#8217;s version upgrade and an application&#8217;s version upgrade?

I will break down the discussion into types of database refactoring operations, and I will limit to single table refactoring. The discussion will try to understand the need for refactoring and will dictate the steps towards a successful upgrade.
Reader prerequisites
I will assume MySQL to be the underlying database. To take a major component out of the equation: we may need to deal with very large tables, for which an ALTER command may take long hours. I will assume familiarity with Master-Master (Active-Passive) replication, with possible use of MMM for MySQL. When I describe &#8220;Failover from M1 to M2&#8220;, I mean &#8220;Make the ALTER changes on M2 (passive), then switch your application from M1 to M2 (change of IPs, VIP, etc.), promoting M2 to active position, then apply same changes on M1 (now passive) or completely rebuild it&#8221;.
Phew, a one sentence description of M-M usage&#8230;
I also assume the reader&#8217;s understanding that a table&#8217;s schema can be different on master &#38; slave, which is the basis for the &#8220;use replication for refactoring&#8221; trick. But it cannot be too different, or, to be precise, the two schemata must both support the ongoing queries for the table.
A full discussion of the above is beyond the scope of this post.
Types of refactoring needs
As I limit this discussion to single table refactoring,we can look at major refactoring operations and their impact on application &#38; upgrades. We will discuss ADD/DROP COLUMN, ADD/DROP INDEX, ADD/DROP UNIQUE INDEX, ADD/DROP FOREIGN KEY, ADD/DROP TABLE.
We will assume the database and application are both in Version #1 (V1), and need to be upgraded to V2 or greater.
ADD INDEX
Starting with the easier actions. Why would you add an index? Either:

There is some existing query which can be optimized by the new query
Or there is some new functionality which issues a query for which the new index is required.

Adding an index is an easy action in that the table&#8217;s data does not really change.
In case #1, all you need to do is to add the new index (if the table is large, fail over from M1 to M2). There is no application upgrade, so all that happens is that the database upgrades V1 -&#62; V2.
In case #2, the database must be prepared with new schema before the new functionality/query is introduced (since it depends on the existence of the index). The steps, therefore, are:

DB: V1 -&#62; V2 (possibly failover from M1 to M2)
(Sometime later) App: V1 -&#62; V2. Application will issue queries which utilize the new index.

The application does not have to be upgraded at the same instant the DB gets upgraded. In fact, we&#8217;ll see that this is a typical scenario: we can separate upgrades into smaller steps, which allow for time lapse. One could work out steps 1 &#38; 2 together, but that would take an extra effort.
ADD COLUMN
This must be one of the most common table schema upgrades: a new property is needed on the application side. It must be supported by the database. Perhaps a new field in some Java Object, with Hibernate mapping that field onto a new column. Or maybe the new column is there for purpose of de-normalization.
This is also a more complicated task. Let&#8217;s look at the required steps:

DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is ADD COLUMN.
App: V1 -&#62; V2. Change is: provide column value for newly INSERTed rows.
If needed, retroactively update column values for all pre-existing rows.
App: V2 -&#62; V3. Application begins to use (read, SELECT) new column.

The above procedure assumes that the new column must have some calculated value. A 10-million rows table must now be updated, to have the correct values filled in. So we ask of the application to start filling in data for new rows, which makes the invalid row set static. We can just take a &#8220;from row&#8221; and a &#8220;to row&#8221; and fill in the missing column&#8217;s value for those rows. Only when all rows contain valid values can we let the application start using that row. This makes for two application upgrades.
If you&#8217;re content with just a static DEFAULT value, then step 3 can be skipped, and step 4 can be merged with step 2.
ADD UNIQUE INDEX
This is an altogether different case than the normal ADD INDEX, even though they may seem similar. And the case is particularly different when using Master-Slave failover for rebuilding the table.
Consider the case where we add a UNIQUE INDEX on a slave. Some INSERT query executes on the master, successfully, and is logged to the binary log. The slave picks it up, tries to execute it, to find that it fails on a DUPLICATE KEY error.
The UNIQUE INDEX is a constraint, and it makes the slave more constrained than the master. This is a delicate situation. Here how to (mostly) work it out:

App: V1 -&#62; V2. Change INSERT queries on relevant table to INSERT IGNORE or REPLACE queries, whichever is more appropriate.
DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is ADD UNIQUE KEY (and while at it, a tip: are you aware of ALTER IGNORE TABLE?)

The change of query ensures that the query will succeed on the slave (either by silently doing nothing or by actually replacing content). It also means that the slave can now have different data than the master. Of course, it you trust your application to never INSERT duplicates, you can sleep better.
We do not handle UPDATE statements here.
ADD CONSTRAINT FOREIGN KEY
As with ADD UNIQUE INDEX, there is a new constraint here. A slave becomes more constrained than the master. But we now have to make sure INSERT, UPDATE and DELETE statements all go peacefully (well, it also depends on the type of ON DELETE and ON UPDATE property of the FK).
The steps would be:

DB: V1 -&#62; V2 (possibly failover from M1 to M2), change is ADD CONSTRAINT FOREIGN KEY.

And then cross your fingers or have trust in your application. If the table is small enough, one does not have to use replication to do the refactoring, and life is simpler. Just execute the ALTER on the active master, and continue with your life.
CREATE TABLE
This is a simple case, since the table is new. The steps are:

DB: V1 -&#62; V2 (no need to use slaves here)
App: V1 -&#62; V2. Application will start using new table.

Conslusion
Having such steps formalized help with development management and database management. It makes clear what is expected of the application, and what is expected of the database. The breaking down of these operations into sequential steps allows us to work more slowly; make preparation work; work within our own working hours; get a chance to see the family.
In this post we took a look at &#8220;creation&#8221; refactoring changes. New columns, new keys, new constraints. In the next part of this article, we&#8217;ll discuss DROP operations.]]></description>
			<content:encoded><![CDATA[<p>A developer&#8217;s major concern is: <em>How do I do application &amp; database upgrades with minimal downtime? How do I synchronize between a DB&#8217;s version upgrade and an application&#8217;s version upgrade?<br />
</em></p>
<p>I will break down the discussion into types of database refactoring operations, and I will limit to single table refactoring. The discussion will try to understand the need for refactoring and will dictate the steps towards a successful upgrade.</p>
<h4>Reader prerequisites</h4>
<p>I will assume MySQL to be the underlying database. To take a major component out of the equation: we may need to deal with very large tables, for which an <strong>ALTER</strong> command may take long hours. I will assume familiarity with Master-Master (Active-Passive) replication, with possible use of <a href="http://mysql-mmm.org/">MMM for MySQL</a>. When I describe &#8220;Failover from <strong>M1</strong> to <strong>M2</strong>&#8220;, I mean &#8220;Make the <strong>ALTER</strong> changes on <strong>M2</strong> (passive), then switch your application from <strong>M1</strong> to <strong>M2</strong> (change of IPs, VIP, etc.), promoting <strong>M2</strong> to active position, then apply same changes on <strong>M1</strong> (now passive) or completely rebuild it&#8221;.</p>
<p>Phew, a one sentence description of M-M usage&#8230;</p>
<p>I also assume the reader&#8217;s understanding that a table&#8217;s schema can be different on master &amp; slave, which is the basis for the &#8220;use replication for refactoring&#8221; trick. But it cannot be too different, or, to be precise, the two schemata must both support the ongoing queries for the table.</p>
<p>A full discussion of the above is beyond the scope of this post.</p>
<h4>Types of refactoring needs</h4>
<p>As I limit this discussion to single table refactoring,we can look at major refactoring operations and their impact on application &amp; upgrades. We will discuss ADD/DROP COLUMN, ADD/DROP INDEX, ADD/DROP UNIQUE INDEX, ADD/DROP FOREIGN KEY, ADD/DROP TABLE.</p>
<p>We will assume the database and application are both in Version #1 (<strong>V1</strong>), and need to be upgraded to <strong>V2</strong> or greater.<span></span></p>
<h4>ADD INDEX</h4>
<p>Starting with the easier actions. Why would you add an index? Either:</p>
<ol>
<li>There is some existing query which can be optimized by the new query</li>
<li>Or there is some new functionality which issues a query for which the new index is required.</li>
</ol>
<p>Adding an index is an easy action in that the table&#8217;s data does not really change.</p>
<p>In case <strong>#1</strong>, all you need to do is to add the new index (if the table is large, fail over from <strong>M1</strong> to <strong>M2</strong>). There is no application upgrade, so all that happens is that the database upgrades <strong>V1 </strong>-&gt;<strong> V2</strong>.</p>
<p>In case <strong>#2</strong>, the database must be prepared with new schema before the new functionality/query is introduced (since it depends on the existence of the index). The steps, therefore, are:</p>
<ol>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>)</li>
<li>(Sometime later) App: <strong>V1</strong> -&gt; <strong>V2</strong>. Application will issue queries which utilize the new index.</li>
</ol>
<p>The application does not have to be upgraded at the same instant the DB gets upgraded. In fact, we&#8217;ll see that this is a typical scenario: we can separate upgrades into smaller steps, which allow for time lapse. One <em>could</em> work out steps <strong>1</strong> &amp; <strong>2</strong> together, but that would take an extra effort.</p>
<h4>ADD COLUMN</h4>
<p>This must be one of the most common table schema upgrades: a new property is needed on the application side. It must be supported by the database. Perhaps a new field in some Java Object, with Hibernate mapping that field onto a new column. Or maybe the new column is there for purpose of de-normalization.</p>
<p>This is also a more complicated task. Let&#8217;s look at the required steps:</p>
<ol>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>ADD COLUMN</strong>.</li>
<li>App: <strong>V1</strong> -&gt; <strong>V2</strong>. Change is: provide column value for newly <strong>INSERT</strong>ed rows.</li>
<li>If needed, retroactively update column values for all pre-existing rows.</li>
<li>App: <strong>V2</strong> -&gt; <strong>V3</strong>. Application begins to use (read, <strong>SELECT</strong>) new column.</li>
</ol>
<p>The above procedure assumes that the new column must have some calculated value. A 10-million rows table must now be updated, to have the correct values filled in. So we ask of the application to start filling in data for new rows, which makes the invalid row set static. We can just take a &#8220;from row&#8221; and a &#8220;to row&#8221; and fill in the missing column&#8217;s value for those rows. Only when all rows contain valid values can we let the application start using that row. This makes for <em>two</em> application upgrades.</p>
<p>If you&#8217;re content with just a static <strong>DEFAULT</strong> value, then step <strong>3</strong> can be skipped, and step <strong>4</strong> can be merged with step <strong>2</strong>.</p>
<h4>ADD UNIQUE INDEX</h4>
<p>This is an altogether different case than the normal <strong>ADD INDEX</strong>, even though they may seem similar. And the case is particularly different when using Master-Slave failover for rebuilding the table.</p>
<p>Consider the case where we add a <strong>UNIQUE INDEX</strong> on a slave. Some <strong>INSERT</strong> query executes on the master, successfully, and is logged to the binary log. The slave picks it up, tries to execute it, to find that it fails on a DUPLICATE KEY error.</p>
<p>The <strong>UNIQUE INDEX</strong> is a constraint, and it makes the slave more constrained than the master. This is a delicate situation. Here how to (mostly) work it out:</p>
<ol>
<li>App: <strong>V1</strong> -&gt; <strong>V2</strong>. Change <strong>INSERT</strong> queries on relevant table to <strong>INSERT IGNORE</strong> or <strong>REPLACE</strong> queries, whichever is more appropriate.</li>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>ADD UNIQUE KEY</strong> (and while at it, a tip: are you aware of <a href="http://dev.mysql.com/doc/refman/5.1/en/alter-table.html">ALTER IGNORE TABLE</a>?)</li>
</ol>
<p>The change of query ensures that the query will succeed on the slave (either by silently doing nothing or by actually replacing content). It also means that the slave can now have different data than the master. Of course, it you trust your application to never <strong>INSERT</strong> duplicates, you can sleep better.</p>
<p>We do not handle <strong>UPDATE</strong> statements here.</p>
<h4>ADD CONSTRAINT FOREIGN KEY</h4>
<p>As with <strong>ADD UNIQUE INDEX</strong>, there is a new constraint here. A slave becomes more constrained than the master. But we now have to make sure <strong>INSERT</strong>, <strong>UPDATE</strong> and <strong>DELETE</strong> statements all go peacefully (well, it also depends on the type of <strong>ON DELETE</strong> and <strong>ON UPDATE</strong> property of the FK).</p>
<p>The steps would be:</p>
<ol>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (possibly failover from <strong>M1</strong> to <strong>M2</strong>), change is <strong>ADD CONSTRAINT FOREIGN KEY</strong>.</li>
</ol>
<p>And then cross your fingers or have trust in your application. If the table is small enough, one does not have to use replication to do the refactoring, and life is simpler. Just execute the <strong>ALTER</strong> on the active master, and continue with your life.</p>
<h4>CREATE TABLE</h4>
<p>This is a simple case, since the table is new. The steps are:</p>
<ol>
<li>DB: <strong>V1</strong> -&gt; <strong>V2</strong> (no need to use slaves here)</li>
<li>App: <strong>V1</strong> -&gt; <strong>V2</strong>. Application will start using new table.</li>
</ol>
<h4>Conslusion</h4>
<p>Having such steps formalized help with development management and database management. It makes clear what is expected of the application, and what is expected of the database. The breaking down of these operations into sequential steps allows us to work more slowly; make preparation work; work within our own working hours; get a chance to see the family.</p>
<p>In this post we took a look at &#8220;creation&#8221; refactoring changes. New columns, new keys, new constraints. In the next part of this article, we&#8217;ll discuss <strong>DROP</strong> operations.</p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25516&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25516&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/table-refactoring-application-version-upgrades-part-i/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Judgment day for open source at Oracle</title>
		<link>http://feedproxy.google.com/~r/451opensource/~3/33R0BcAovFI/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=judgment-day-for-open-source-at-oracle</link>
		<comments>http://feedproxy.google.com/~r/451opensource/~3/33R0BcAovFI/#comments</comments>
		<pubDate>Thu, 15 Jul 2010 00:25:38 +0000</pubDate>
		<dc:creator>The 451 Group</dc:creator>
				<category><![CDATA[451 group]]></category>
		<category><![CDATA[451caostheory]]></category>
		<category><![CDATA[451group]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[OpenSolaris CDs]]></category>
		<category><![CDATA[RDMS]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[caostheory]]></category>
		<category><![CDATA[cds]]></category>
		<category><![CDATA[cloud computing]]></category>
		<category><![CDATA[community]]></category>
		<category><![CDATA[glassfish]]></category>
		<category><![CDATA[jaspersoft]]></category>
		<category><![CDATA[jay lyman]]></category>
		<category><![CDATA[jaylyman]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[open source at Oracle]]></category>
		<category><![CDATA[openoffice]]></category>
		<category><![CDATA[opensolaris]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[oracle]]></category>
		<category><![CDATA[solaris]]></category>

		<guid isPermaLink="false">http://blogs.the451group.com/opensource/?p=2062</guid>
		<description><![CDATA[There are signals of continued problems and dysfunction &#8212; namely lack of support, organization and communication &#8212; in the OpenSolaris community. This follows on a deterioration of the OS leadership and support since Oracle bought Sun Microsystems, including the elimination of OpenSolaris CDs, one of the things that made the open source version of Solaris more like Linux. 
We had speculated on the fate of Sun open source software under Oracle and while we acknowledged Oracle&#8217;s participation in, contribution and commitment to and opportunity from open source software, we questioned its appreciation of open source software communities beyond code and customers. It appears the OpenSolaris community and thus the OS itself, which we believe is key to advancing development of the more popular, proprietary cousin Solaris &#8212; are not a priority for Oracle.
The same cannot be said for all open source from Sun, and there&#8217;s a lot of it, now at Oracle. Amid the struggles of the OpenSolaris community, one of the other open source keystones from Sun, MySQL, seems to be doing well, despite persisting claims Oracle purchased Sun and MySQL simply to keep it from competing with Oracle database products. According to a Jaspersoft survey of customers/developers, there is a lack of awareness or concern of Oracle&#8217;s involvement in MySQL (59 percent were not aware Oracle reorganized and established a separate MySQL business unit apart from Oracle’s traditional RDBMS business &#8230;). Another 43% of Jaspersoft&#8217;s respondents said MySQL development and innovation would improve under Oracle.
The Jaspersoft survey found even more love for Java under Oracle, with 80 percent of respondents indicating they believe the Java process will improve or stay the same under Oracle. The related GlassFish application server also appears to be healthy with both community and commercial versions recently released.
The OpenOffice community appears also to be continuing forward supported and unfettered by Oracle (perhaps because it was typically fettered by Sun?), but it may also me failing to fully seize the opportunity. 
It has also been interesting to see how Sun&#8217;s cloud computing technology has helped give Oracle new love for the term and the market. 
There are a number of key open source projects and pieces from Sun, those listed above as well as many others, that may be on the line right now (or may have already been branded &#8217;stay&#8217; or &#8217;stop&#8217;). We will be watching to see how Sun&#8217;s open source continues to shine or to set at Oracle. 
]]></description>
			<content:encoded><![CDATA[<p>There are signals of continued <a href="http://www.h-online.com/open/news/item/OpenSolaris-governing-board-threatens-dissolution-1037134.html">problems</a> and <a href="http://news.cnet.com/8301-30685_3-20010587-264.html">dysfunction</a> &#8212; namely lack of support, organization and communication &#8212; in the OpenSolaris community. This follows on a deterioration of the OS leadership and support since Oracle bought Sun Microsystems, including the <a href="http://www.h-online.com/open/news/item/OpenSolaris-free-CDs-halted-977945.html">elimination</a> of OpenSolaris CDs, one of the things that made the open source version of Solaris more like Linux. </p>
<p>We had speculated on the fate of Sun open source software under Oracle and while we acknowledged Oracle&#8217;s participation in, contribution and commitment to and opportunity from open source software, we <a href="http://blogs.the451group.com/opensource/2009/04/20/oracle-buys-sun-but-does-it-buy-open-source/">questioned</a> its appreciation of open source software communities beyond code and customers. It appears the OpenSolaris community and thus the OS itself, which we believe is key to advancing development of the more popular, proprietary cousin Solaris &#8212; are not a priority for Oracle.</p>
<p>The same cannot be said for all open source from Sun, and there&#8217;s a lot of it, now at Oracle. Amid the struggles of the OpenSolaris community, one of the other open source keystones from Sun, MySQL, seems to be doing well, despite persisting claims Oracle purchased Sun and MySQL simply to keep it from competing with Oracle database products. According to a <a href="http://openbookonbi.blogspot.com/2010/07/survey-results-point-to-future-of-java.html">Jaspersoft survey</a> of customers/developers, there is a lack of awareness or concern of Oracle&#8217;s involvement in MySQL (59 percent were not aware Oracle reorganized and established a separate MySQL business unit apart from Oracle’s traditional RDBMS business &#8230;). Another 43% of Jaspersoft&#8217;s respondents said MySQL development and innovation would improve under Oracle.</p>
<p>The Jaspersoft survey found even more love for Java under Oracle, with 80 percent of respondents indicating they believe the Java process will improve or stay the same under Oracle. The related GlassFish application server also appears to be healthy with both community and commercial versions recently <a href="http://sun.systemnews.com/articles/148/3/ja/23253">released</a>.</p>
<p>The OpenOffice community appears also to be continuing forward supported and unfettered by Oracle (perhaps because it was typically fettered by Sun?), but it may also me failing to fully seize the <a href="http://www.pcworld.com/article/201056/office_2010_sales_are_lagging_says_npd.html?tk=hp_blg">opportunity</a>. </p>
<p>It has also been interesting to see how Sun&#8217;s cloud computing technology has helped give Oracle new love for the term and the market. </p>
<p>There are a number of key open source projects and pieces from Sun, those listed above as well as many others, that may be on the line right now (or may have already been branded &#8217;stay&#8217; or &#8217;stop&#8217;). We will be watching to see how Sun&#8217;s open source continues to shine or to set at Oracle. </p>
<img src="http://feeds.feedburner.com/~r/451opensource/~4/33R0BcAovFI" height="1" width="1" /><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25307&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25307&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://feedproxy.google.com/~r/451opensource/~3/33R0BcAovFI/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Improving MySQL Productivity &#8211; From Design to Implementation</title>
		<link>http://ronaldbradford.com/blog/improving-mysql-productivity-from-design-to-implementation-2010-07-01/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=improving-mysql-productivity-from-design-to-implementation</link>
		<comments>http://ronaldbradford.com/blog/improving-mysql-productivity-from-design-to-implementation-2010-07-01/#comments</comments>
		<pubDate>Thu, 01 Jul 2010 22:18:55 +0000</pubDate>
		<dc:creator>Ronald Bradford</dc:creator>
				<category><![CDATA[Databases]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Kaleidoscope 2010]]></category>
		<category><![CDATA[Oracle/MySQL Conferences]]></category>
		<category><![CDATA[Professional]]></category>
		<category><![CDATA[ROI]]></category>
		<category><![CDATA[best practices]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[odtug]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[productivity]]></category>

		<guid isPermaLink="false">http://ronaldbradford.com/blog/?p=2999</guid>
		<description><![CDATA[My closing presentation at the dedicated MySQL track at ODTUG Kaleidoscope 2010 discussed various techniques and best practices for improving the ROI of developer resources using MySQL.  Included in the sections on Design, Security, Development, Testing, Implementation, Instrumentation and Support were also a number of horror stories of not what to do, combined with practical examples of improving productivity.
Increasing MySQL Productivity
View more presentations from Ronald Bradford.
]]></description>
			<content:encoded><![CDATA[<p>My closing presentation at the dedicated <a href="http://www.odtugkaleidoscope.com/MySQL.html">MySQL track</a> at ODTUG Kaleidoscope 2010 discussed various techniques and best practices for improving the ROI of developer resources using MySQL.  Included in the sections on Design, Security, Development, Testing, Implementation, Instrumentation and Support were also a number of horror stories of not what to do, combined with practical examples of improving productivity.</p>
<div><strong><a href="http://www.slideshare.net/ronaldbradford/increasing-mysql-productivity" title="Increasing MySQL Productivity">Increasing MySQL Productivity</a></strong>
<div>View more <a href="http://www.slideshare.net/">presentations</a> from <a href="http://www.slideshare.net/ronaldbradford">Ronald Bradford</a>.</div>
</div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25181&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25181&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://ronaldbradford.com/blog/improving-mysql-productivity-from-design-to-implementation-2010-07-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL: good comments conventions</title>
		<link>http://code.openark.org/blog/mysql/sql-good-comments-conventions?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=sql-good-comments-conventions</link>
		<comments>http://code.openark.org/blog/mysql/sql-good-comments-conventions#comments</comments>
		<pubDate>Thu, 01 Jul 2010 07:36:32 +0000</pubDate>
		<dc:creator>Shlomi Noach</dc:creator>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Syntax]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2581</guid>
		<description><![CDATA[I happened upon a customer who left me in awe and admiration. The reason: excellent comments for their SQL code.
I list four major places where SQL comments are helpful. I&#8217;ll use the sakila database. It is originally scarcely commented; I&#8217;ll present it now enhanced with comments, to illustrate.
Table definitions
The CREATE TABLE statement allows for a comment, intended to describe the nature of the table:

CREATE TABLE `film_text` (
 `film_id` smallint(6) NOT NULL,
 `title` varchar(255) NOT NULL,
 `description` text,
 PRIMARY KEY (`film_id`),
 FULLTEXT KEY `idx_title_description` (`title`,`description`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Reflection of `film`, used for FULLTEXT search.'


It&#8217;s too bad the comment&#8217;s max length is 60 characters, though. However, it&#8217;s a very powerful field.
Column definitions
One may comment particular columns:

CREATE TABLE `film` (
 `film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
 `title` varchar(255) NOT NULL,
 `description` text,
 `release_year` year(4) DEFAULT NULL,
 `language_id` tinyint(3) unsigned NOT NULL COMMENT 'Soundtrack spoken language',
 `original_language_id` tinyint(3) unsigned DEFAULT NULL COMMENT 'Filmed spoken language',
 `rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
 `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
 `length` smallint(5) unsigned DEFAULT NULL,
 `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
  ...
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8


Stored routines definitions
Here&#8217;s an original sakila procedure, untouched. It is already commented:

CREATE DEFINER=`root`@`localhost` PROCEDURE `rewards_report`(
 IN min_monthly_purchases TINYINT UNSIGNED
 , IN min_dollar_amount_purchased DECIMAL(10,2) UNSIGNED
 , OUT count_rewardees INT
)
 READS SQL DATA
 COMMENT 'Provides a customizable report on best customers'
BEGIN

 DECLARE last_month_start DATE;
 DECLARE last_month_end DATE;
 ...


SQL queries
Last but not least, while not part of the schema, SQL queries define the use of the schema. That is, the schema exists for the sole reason of being able to query it.
Where did that query come from? Which piece of code issued it? Why? What&#8217;s its purpose?
Looking at the PROCESSLIST, the slow log, etc., it is easier when the queries are commented:

SELECT
 /* List film details along with participating actors */
 /* Issued by analytics module */
 film.*,
 COUNT(*) AS count_actors,
 GROUP_CONCAT(CONCAT(actor.first_name, ' ', actor.last_name))
FROM
 film
 JOIN film_actor USING(film_id)
 JOIN actor USING(actor_id)
GROUP BY film.film_id;


Conclusion
Source code commenting is an important practice, and usually watched out for. SQL &#38; table definitions commenting are often scarce or non-existent. I urge DBAs to adopt a comments coding convention for SQL, and apply it whenever they can.]]></description>
			<content:encoded><![CDATA[<p>I happened upon a customer who left me in awe and admiration. The reason: excellent comments for their SQL code.</p>
<p>I list four major places where SQL comments are helpful. I&#8217;ll use the <a href="http://dev.mysql.com/doc/sakila/en/sakila.html">sakila</a> database. It is originally scarcely commented; I&#8217;ll present it now enhanced with comments, to illustrate.</p>
<h4>Table definitions</h4>
<p>The <strong>CREATE TABLE</strong> statement allows for a comment, intended to describe the nature of the table:</p>
<blockquote>
<pre>CREATE TABLE `film_text` (
 `film_id` smallint(6) NOT NULL,
 `title` varchar(255) NOT NULL,
 `description` text,
 PRIMARY KEY (`film_id`),
 FULLTEXT KEY `idx_title_description` (`title`,`description`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 <strong>COMMENT='Reflection of `film`, used for FULLTEXT search.'</strong>
</pre>
</blockquote>
<p>It&#8217;s too bad the comment&#8217;s max length is 60 characters, though. However, it&#8217;s a very powerful field.</p>
<h4>Column definitions</h4>
<p>One may comment particular columns:<span></span></p>
<blockquote>
<pre>CREATE TABLE `film` (
 `film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
 `title` varchar(255) NOT NULL,
 `description` text,
 `release_year` year(4) DEFAULT NULL,
 `language_id` tinyint(3) unsigned NOT NULL <strong>COMMENT 'Soundtrack spoken language'</strong>,
 `original_language_id` tinyint(3) unsigned DEFAULT NULL <strong>COMMENT 'Filmed spoken language'</strong>,
 `rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
 `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
 `length` smallint(5) unsigned DEFAULT NULL,
 `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
  ...
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
</pre>
</blockquote>
<h4>Stored routines definitions</h4>
<p>Here&#8217;s an original <strong>sakila</strong> procedure, untouched. It is already commented:</p>
<blockquote>
<pre>CREATE DEFINER=`root`@`localhost` PROCEDURE `rewards_report`(
 IN min_monthly_purchases TINYINT UNSIGNED
 , IN min_dollar_amount_purchased DECIMAL(10,2) UNSIGNED
 , OUT count_rewardees INT
)
 READS SQL DATA
 <strong>COMMENT 'Provides a customizable report on best customers'</strong>
BEGIN

 DECLARE last_month_start DATE;
 DECLARE last_month_end DATE;
 ...
</pre>
</blockquote>
<h4>SQL queries</h4>
<p>Last but not least, while not part of the schema, SQL queries define the use of the schema. That is, the schema exists for the sole reason of being able to query it.</p>
<p>Where did <em>that</em> query come from? Which piece of code issued it? Why? What&#8217;s its purpose?</p>
<p>Looking at the <strong>PROCESSLIST</strong>, the slow log, etc., it is easier when the queries are commented:</p>
<blockquote>
<pre>SELECT
 <strong>/* List film details along with participating actors */</strong>
 <strong>/* Issued by analytics module */</strong>
 film.*,
 COUNT(*) AS count_actors,
 GROUP_CONCAT(CONCAT(actor.first_name, ' ', actor.last_name))
FROM
 film
 JOIN film_actor USING(film_id)
 JOIN actor USING(actor_id)
GROUP BY film.film_id;
</pre>
</blockquote>
<h4>Conclusion</h4>
<p>Source code commenting is an important practice, and usually watched out for. SQL &amp; table definitions commenting are often scarce or non-existent. I urge DBAs to adopt a comments coding convention for SQL, and apply it whenever they can.</p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25171&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25171&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/sql-good-comments-conventions/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ever tried calling a win32ole (COM) object from Ruby’s DRb?</title>
		<link>http://www.linuxbloke.com/?p=266&amp;utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=ever-tried-calling-a-win32ole-com-object-from-ruby%25e2%2580%2599s-drb</link>
		<comments>http://www.linuxbloke.com/?p=266#comments</comments>
		<pubDate>Sat, 26 Jun 2010 14:25:31 +0000</pubDate>
		<dc:creator>Fred Mitchell</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[windows]]></category>

		<guid isPermaLink="false">http://www.linuxbloke.com/?p=266</guid>
		<description><![CDATA[Before we get started here, let me state that I am using Ruby 1.9.1 (I refuse to look back!), and that I have not tested this solution on Ruby 1.8.6, but it should work there as well, though I may have some 1.9-isms in my code. Should be easy enough to spot.
I am working on writing an application in Ruby that can talk to an Windows application that has an ActiveX COM Automation object exposed. Ruby is basically the wrapper so that I can access the application from the Linux side of the world. So, I am using Ruby&#8217;s DRb to bridge those worlds because, after all, I am the Linux Bloke!
Well, as you may have guessed, I ran into problems with this approach. I simply could not call the COM objects from a call initiated with DRb, though I could call them directly just fine. After scratching my head a bit, I figured it out.
The win32ole module that runs on the Windows side of the world in Ruby only wants to run in the same thread that it was started in. win32ole is simply not thread-safe, and this has to do in large part to how ActiveX works under Windows. No need to delve into the gory details as we want code that works already!
DRb is very much all about threads. The DRb Server runs in a separate thread, and threads are launched each time a DRb request comes in. Threads abound like crazy! After all, it is very clear that the implementation of DRb was based, in part, on the Java threading model and Java&#8217;s RMI. But we knew that. We know that Ruby Threads parrot Java Threads. And I&#8217;ve done a lot of work with Java Threads in the past and almost feel a bit of &#8220;déjà vu&#8221; in working with them in Ruby. Oh the days&#8230;
But I digress.
We have a major problem here. How do we get around it, without having to throw out DRb and doing something funky like writing some custom RPC bit just to make Windows happy?
Well, as you may have guess, the Linux Bloke created the very solution you need!! Funnel!
Funnel works by wrapping a given object with a &#8220;meta&#8221; object that can then be called from any thread. All the calls are actually queued up and processed by the thread the target object wants to run in. The calling threads block until the target object returns the call, and the result objects are stuffed somewhere so that the calling thread can find them.
It&#8217;s all very transparent and you need not do anything special &#8212; much. You will need to call process_funnel_messages() in the funneled thread. And you may do this once in which case process_funnel_messages() will loop forever and never return, or you can call it at regular intervals if you need to do other processing in that same thread.
You, of course, can use Funnel anywhere you need to funnel calls from multiple threads to a single thread to access something that is not inherently thread-safe or thread-aware.
The downloadable code is posted here:

?Download funnel.rb1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
=begin rdoc
Funnel created by Fred Mitchell (LinuxBloke.com) on 2010-06-05                         
&#160;
=Funnel -- funnel calls to an object to a specific thread that created said object.    
&#160;
With some systems, like win32ole, the system basically wants to run on the same thread
the system was started on. To facilitate that need in a multi-threaded environment,
we create the Funnel.                                                                  
&#160;
The Funnel wrapper on an object will basically intercept all method calls and
funnel those calls to the wrapped object in the thread it was created in. The
caller thread will basically block until the Funnel calls the target object's method
and will be given, as a return, the result object of that call.                        
&#160;
The Funnel thread will basically sit in a loop waiting for something to come in,
and wake up to process the entries, then go back to sleep until the next ones come
in.                                                                                    
&#160;
Any exceptions (or errors) that occur in the Funnel shall be
thrown to the caller thread, as though the exception took place in that thread.        
&#160;
This code is released under the GPLv3.                                                 
&#160;
=end                         
&#160;
module Funnel
  class Wrapper
    def initialize&#40;target&#41;
      @targetOb = target
      @targetThr = Thread.current
      @targetThr&#91;:methQueue&#93; = &#91;&#93; if @targetThr&#91;:methQueue&#93;.nil?
    end                                                                                
&#160;
    def method_missing&#40;meth, *parms&#41;
      Thread.current&#91;:methResult&#93; = :nothing_yet
      @targetThr&#91;:methQueue&#93; &#38;lt;&#38;lt; &#91;@targetOb, meth, Thread.current, parms&#93;               
&#160;
      # Thing is, we may have gotten a response already!
      while Thread.current&#91;:methResult&#93; == :nothing_yet
        if @targetThr.stop?
          @targetThr.wakeup
          # Thread.stop
        end
        Thread.pass
      end
      Thread.current&#91;:methResult&#93;
    end
  end                                                                                  
&#160;
  # Called by the orginal thread to process object messages.
  # This function never returns.
  def process_funnel_messages&#40;loop_forever = true&#41;
    begin
      meth = nil
      &#40;ob, meth, thr, parms&#41; = Thread.current&#91;:methQueue&#93;.shift unless Thread.current&#91;\
:methQueue&#93;.nil?
      unless meth.nil?
        begin
          thr&#91;:methResult&#93; = ob.send&#40;meth, *parms&#41;
          thr.run
        rescue
          thr.raise&#40;$!&#41;
        end
      else
        Thread.stop if loop_forever
      end
    end while loop_forever
  end                                                                                  
&#160;
  def wrap&#40;target&#41;
    Wrapper.new&#40;target&#41;
  end
end

And here is an example of its use:

?Download example_of_funnel_use.rb1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
require 'funnel'                                                                       
include Funnel                                                                         
&#160;
class StupidThreadUnsafeThing                                                          
  def callme                                                                           
    puts &#34;*** I've been called. My thread is&#34;                                          
    p Thread.current                                                                   
    puts                                                                               
  end                                                                                  
end                                                                                    
&#160;
stut = StupidThreadUnsafeThing.new                                                     
&#160;
# This is the easy to use wrapper                                                      
fstut = wrap stut                                                                      
&#160;
stut.callme                                                                            
&#160;
Thread.new do                                                                          
  10.times do &#124;i&#124;                                                                      
    sleep 1                                                                            
    Thread.new &#123;                                                                       
      puts &#34;XXX #{i} calling stut from thread&#34;                                         
      p Thread.current                                                                 
      fstut.callme                                                                     
    &#125;                                                                                  
  end                                                                                  
  exit                                                                                 
end                                                                                    
&#160;
# Here we loop forever processing messages.                                            
# Optionally, we could call this repeateady                                            
# to process messages by using a parameter of                                          
# &#34;false&#34;.                                                                             
process_funnel_messages

This code is fairly straightforward, as you can see. If there is enough interest, I&#8217;ll consider turning this into a gem. ]]></description>
			<content:encoded><![CDATA[<br/><p><em>Before we get started here, let me state that I am using Ruby 1.9.1 (I refuse to look back!), and that I have not tested this solution on Ruby 1.8.6, but it should work there as well, though I may have some 1.9-isms in my code. Should be easy enough to spot.</em></p>
<p>I am working on writing an application in Ruby that can talk to an Windows application that has an ActiveX COM Automation object exposed. Ruby is basically the wrapper so that I can access the application from the Linux side of the world. So, I am using Ruby&#8217;s DRb to bridge those worlds because, after all, I am the Linux Bloke!</p>
<p>Well, as you may have guessed, I ran into problems with this approach. I simply could not call the COM objects from a call initiated with DRb, though I could call them directly just fine. After scratching my head a bit, I figured it out.</p>
<p>The win32ole module that runs on the Windows side of the world in Ruby only wants to run in the same thread that it was started in. win32ole is simply not thread-safe, and this has to do in large part to how ActiveX works under Windows. No need to delve into the gory details as we want code that works already!</p>
<p>DRb is very much all about threads. The DRb Server runs in a separate thread, and threads are launched each time a DRb request comes in. Threads abound like crazy! After all, it is very clear that the implementation of DRb was based, in part, on the Java threading model and Java&#8217;s RMI. But we knew that. We know that Ruby Threads parrot Java Threads. And I&#8217;ve done a lot of work with Java Threads in the past and almost feel a bit of &#8220;déjà vu&#8221; in working with them in Ruby. Oh the days&#8230;</p>
<p>But I digress.</p>
<p>We have a major problem here. How do we get around it, without having to throw out DRb and doing something funky like writing some custom RPC bit just to make Windows happy?</p>
<p>Well, as you may have guess, the Linux Bloke created the very solution you need!! Funnel!</p>
<p>Funnel works by wrapping a given object with a &#8220;meta&#8221; object that can then be called from any thread. All the calls are actually queued up and processed by the thread the target object wants to run in. The calling threads block until the target object returns the call, and the result objects are stuffed somewhere so that the calling thread can find them.</p>
<p>It&#8217;s all very transparent and you need not do anything special &#8212; much. You will need to call process_funnel_messages() in the funneled thread. And you may do this once in which case process_funnel_messages() will loop forever and never return, or you can call it at regular intervals if you need to do other processing in that same thread.</p>
<p>You, of course, can use Funnel anywhere you need to funnel calls from multiple threads to a single thread to access something that is not inherently thread-safe or thread-aware.</p>
<p>The downloadable code is posted here:</p>

<div><span><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples"  title="WP-CodeBox HowTo?"><span>?</span></a></sup></span><span>Download <a href="http://www.linuxbloke.com/wp-content/plugins/wp-codebox/wp-codebox.php?p=266&amp;download=funnel.rb">funnel.rb</a></span><div></div></div><div><table><tr><td><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
</pre></td><td><pre><span>=begin rdoc
Funnel created by Fred Mitchell (LinuxBloke.com) on 2010-06-05                         
&nbsp;
=Funnel -- funnel calls to an object to a specific thread that created said object.    
&nbsp;
With some systems, like win32ole, the system basically wants to run on the same thread
the system was started on. To facilitate that need in a multi-threaded environment,
we create the Funnel.                                                                  
&nbsp;
The Funnel wrapper on an object will basically intercept all method calls and
funnel those calls to the wrapped object in the thread it was created in. The
caller thread will basically block until the Funnel calls the target object's method
and will be given, as a return, the result object of that call.                        
&nbsp;
The Funnel thread will basically sit in a loop waiting for something to come in,
and wake up to process the entries, then go back to sleep until the next ones come
in.                                                                                    
&nbsp;
Any exceptions (or errors) that occur in the Funnel shall be
thrown to the caller thread, as though the exception took place in that thread.        
&nbsp;
This code is released under the GPLv3.                                                 
&nbsp;
=end</span>                         
&nbsp;
<span>module</span> Funnel
  <span>class</span> Wrapper
    <span>def</span> initialize<span>&#40;</span>target<span>&#41;</span>
      <span>@targetOb</span> = target
      <span>@targetThr</span> = <span>Thread</span>.<span>current</span>
      <span>@targetThr</span><span>&#91;</span><span>:methQueue</span><span>&#93;</span> = <span>&#91;</span><span>&#93;</span> <span>if</span> <span>@targetThr</span><span>&#91;</span><span>:methQueue</span><span>&#93;</span>.<span>nil</span>?
    <span>end</span>                                                                                
&nbsp;
    <span>def</span> method_missing<span>&#40;</span>meth, <span>*</span>parms<span>&#41;</span>
      <span>Thread</span>.<span>current</span><span>&#91;</span><span>:methResult</span><span>&#93;</span> = <span>:nothing_yet</span>
      <span>@targetThr</span><span>&#91;</span><span>:methQueue</span><span>&#93;</span> <span>&amp;</span>lt;<span>&amp;</span>lt; <span>&#91;</span>@targetOb, meth, <span>Thread</span>.<span>current</span>, parms<span>&#93;</span>               
&nbsp;
      <span># Thing is, we may have gotten a response already!</span>
      <span>while</span> <span>Thread</span>.<span>current</span><span>&#91;</span><span>:methResult</span><span>&#93;</span> == <span>:nothing_yet</span>
        <span>if</span> <span>@targetThr</span>.<span>stop</span>?
          <span>@targetThr</span>.<span>wakeup</span>
          <span># Thread.stop</span>
        <span>end</span>
        <span>Thread</span>.<span>pass</span>
      <span>end</span>
      <span>Thread</span>.<span>current</span><span>&#91;</span><span>:methResult</span><span>&#93;</span>
    <span>end</span>
  <span>end</span>                                                                                  
&nbsp;
  <span># Called by the orginal thread to process object messages.</span>
  <span># This function never returns.</span>
  <span>def</span> process_funnel_messages<span>&#40;</span>loop_forever = <span>true</span><span>&#41;</span>
    <span>begin</span>
      meth = <span>nil</span>
      <span>&#40;</span>ob, meth, thr, parms<span>&#41;</span> = <span>Thread</span>.<span>current</span><span>&#91;</span><span>:methQueue</span><span>&#93;</span>.<span>shift</span> <span>unless</span> <span>Thread</span>.<span>current</span><span>&#91;</span>\
<span>:methQueue</span><span>&#93;</span>.<span>nil</span>?
      <span>unless</span> meth.<span>nil</span>?
        <span>begin</span>
          thr<span>&#91;</span><span>:methResult</span><span>&#93;</span> = ob.<span>send</span><span>&#40;</span>meth, <span>*</span>parms<span>&#41;</span>
          thr.<span>run</span>
        <span>rescue</span>
          thr.<span>raise</span><span>&#40;</span>$!<span>&#41;</span>
        <span>end</span>
      <span>else</span>
        <span>Thread</span>.<span>stop</span> <span>if</span> loop_forever
      <span>end</span>
    <span>end</span> <span>while</span> loop_forever
  <span>end</span>                                                                                  
&nbsp;
  <span>def</span> wrap<span>&#40;</span>target<span>&#41;</span>
    Wrapper.<span>new</span><span>&#40;</span>target<span>&#41;</span>
  <span>end</span>
<span>end</span></pre></td></tr></table></div>

<p>And here is an example of its use:</p>

<div><span><sup><a href="http://www.ericbess.com/ericblog/2008/03/03/wp-codebox/#examples"  title="WP-CodeBox HowTo?"><span>?</span></a></sup></span><span>Download <a href="http://www.linuxbloke.com/wp-content/plugins/wp-codebox/wp-codebox.php?p=266&amp;download=example_of_funnel_use.rb">example_of_funnel_use.rb</a></span><div></div></div><div><table><tr><td><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td><pre><span>require</span> <span>'funnel'</span>                                                                       
<span>include</span> Funnel                                                                         
&nbsp;
<span>class</span> StupidThreadUnsafeThing                                                          
  <span>def</span> callme                                                                           
    <span>puts</span> <span>&quot;*** I've been called. My thread is&quot;</span>                                          
    <span>p</span> <span>Thread</span>.<span>current</span>                                                                   
    <span>puts</span>                                                                               
  <span>end</span>                                                                                  
<span>end</span>                                                                                    
&nbsp;
stut = StupidThreadUnsafeThing.<span>new</span>                                                     
&nbsp;
<span># This is the easy to use wrapper                                                      </span>
fstut = wrap stut                                                                      
&nbsp;
stut.<span>callme</span>                                                                            
&nbsp;
<span>Thread</span>.<span>new</span> <span>do</span>                                                                          
  <span>10</span>.<span>times</span> <span>do</span> <span>|</span>i<span>|</span>                                                                      
    <span>sleep</span> <span>1</span>                                                                            
    <span>Thread</span>.<span>new</span> <span>&#123;</span>                                                                       
      <span>puts</span> <span>&quot;XXX #{i} calling stut from thread&quot;</span>                                         
      <span>p</span> <span>Thread</span>.<span>current</span>                                                                 
      fstut.<span>callme</span>                                                                     
    <span>&#125;</span>                                                                                  
  <span>end</span>                                                                                  
  <span>exit</span>                                                                                 
<span>end</span>                                                                                    
&nbsp;
<span># Here we loop forever processing messages.                                            </span>
<span># Optionally, we could call this repeateady                                            </span>
<span># to process messages by using a parameter of                                          </span>
<span># &quot;false&quot;.                                                                             </span>
process_funnel_messages</pre></td></tr></table></div>

<p>This code is fairly straightforward, as you can see. If there is enough interest, I&#8217;ll consider turning this into a gem. </p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25120&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25120&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://www.linuxbloke.com/?p=266/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Nginx-Fu: X-Accel-Redirect From Remote Servers</title>
		<link>http://feedproxy.google.com/~r/Homo-Adminus/~3/cuG-f1auDlo/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=nginx-fu-x-accel-redirect-from-remote-servers</link>
		<comments>http://feedproxy.google.com/~r/Homo-Adminus/~3/cuG-f1auDlo/#comments</comments>
		<pubDate>Thu, 24 Jun 2010 03:45:52 +0000</pubDate>
		<dc:creator>Alexey Kovyrin</dc:creator>
				<category><![CDATA[Admin-tips]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[download]]></category>
		<category><![CDATA[networks]]></category>
		<category><![CDATA[nginx-fu]]></category>
		<category><![CDATA[redirect]]></category>
		<category><![CDATA[x-accel-redirect]]></category>

		<guid isPermaLink="false">http://kovyrin.net/?p=462</guid>
		<description><![CDATA[We use nginx and its features a lot in Scribd. Many times in the last year we needed some pretty interesting, but not supported feature &#8211; we wanted nginx X-Accel-Redirect functionality to work with remote URLs. Our of the box nginx supports this functionality for local URIs only. In this short post I want to explain how did we make nginx serve remote content via X-Accel-Redirect.

First of all, here is what you may need this feature. Let&#8217;s imagine you have a file storage on Amazon S3 where you store tons of content. And you have an application where you have some content downloading functionality that you want to be available for logged-in/paying/premium users and/or you want to keep track of downloads your users perform on your site. If your content was on your web server, you could have used simple controlled downloads functionality built-in to nginx out of the box. But the problem is that your content is remote.
Here is what we do to solve this problem.
First, we create a special location on our nginx server. This location will be used as a proxy for all our accelerated file downloads:
1234567891011121314151617181920212223242526272829303132333435# Proxy download 
location ~* ^/internal_redirect/&#40;.*?&#41;/&#40;.*&#41; &#123;
&#160; &#160; # Do not allow people to mess with this location directly
&#160; &#160; # Only internal redirects are allowed
&#160; &#160; internal;

&#160; &#160; # Location-specific logging
&#160; &#160; access_log logs/internal_redirect.access.log main;
&#160; &#160; error_log logs/internal_redirect.error.log warn;

&#160; &#160; # Extract download url from the request
&#160; &#160; set $download_uri $2;
&#160; &#160; set $download_host $1;

&#160; &#160; # Compose download url
&#160; &#160; set $download_url http://$download_host/$download_uri;

&#160; &#160; # Set download request headers
&#160; &#160; proxy_set_header Host $download_host;
&#160; &#160; proxy_set_header Authorization '';

&#160; &#160; # The next two lines could be used if your storage 
&#160; &#160; # backend does not support Content-Disposition 
&#160; &#160; # headers used to specify file name browsers use 
&#160; &#160; # when save content to the disk
&#160; &#160; proxy_hide_header Content-Disposition;
&#160; &#160; add_header Content-Disposition 'attachment; filename=&#34;$args&#34;';

&#160; &#160; # Do not touch local disks when proxying 
&#160; &#160; # content to clients
&#160; &#160; proxy_max_temp_file_size 0;

&#160; &#160; # Download the file and send it to client
&#160; &#160; proxy_pass $download_url;
&#125;
After adding this location to our nginx config we could start sending responses with headers like the following:
1234567# This header will ask nginx to download a file 
# from http://some.site.com/secret/url.ext and send it to user
X-Accel-Redirect: /internal_redirect/some.site.com/secret/url.ext

# This header will ask nginx to download a file 
# from http://blah.com/secret/url and send it to user as cool.pdf
X-Accel-Redirect: /internal_redirect/blah.com/secret/url?cool.pdf
Here is an example code you could use in a Rails application to use our internal redirect location:
12345678910def x_accel_url&#40;url, file_name = nil&#41;
&#160; uri = &#34;/internal_redirect/#{url.gsub('http://', '')}&#34;
&#160; uri &#60;&#60; &#34;?#{file_name}&#34; if file_name
&#160; return uri
end

def download
&#160; headers&#91;'X-Accel-Redirect'&#93; = x_accel_url&#40;some_secret_url, pretty_name&#41;
&#160; render :nothing =&#62; true
end
As you can see, nginx is really powerful tool and when you turn your creativity on you can make it even more powerful. Stay tuned for more Nginx-Fu posts.



  
]]></description>
			<content:encoded><![CDATA[<p>We use <a href="http://nginx.org/">nginx</a> and its features a lot in <a href="http://www.scribd.com/">Scribd</a>. Many times in the last year we needed some pretty interesting, but not supported feature &#8211; we wanted nginx <a href="http://wiki.nginx.org/NginxXSendfile"><tt>X-Accel-Redirect</tt></a> functionality to work with remote URLs. Our of the box nginx supports this functionality for local URIs only. In this short post I want to explain how did we make nginx serve remote content via <nobr><tt>X-Accel-Redirect</tt></nobr>.</p>
<p><span></span></p>
<p>First of all, here is what you may need this feature. Let&#8217;s imagine you have a file storage on <a href="http://aws.amazon.com/s3/">Amazon S3</a> where you store tons of content. And you have an application where you have some content downloading functionality that you want to be available for logged-in/paying/premium users and/or you want to keep track of downloads your users perform on your site. If your content was on your web server, you could have used simple <a href="http://kovyrin.net/2006/11/01/nginx-x-accel-redirect-php-rails/">controlled downloads</a> functionality built-in to nginx out of the box. But the problem is that your content is remote.</p>
<p>Here is what we do to solve this problem.</p>
<p>First, we create a special location on our nginx server. This location will be used as a proxy for all our accelerated file downloads:</p>
<div><table cellspacing="0" cellpadding="0"><tbody><tr><td><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br /></div></td><td><div><span># Proxy download </span><br />
<span>location</span> ~* ^/internal_redirect/<span>&#40;</span>.*?<span>&#41;</span>/<span>&#40;</span>.*<span>&#41;</span> <span>&#123;</span><br />
&nbsp; &nbsp; <span># Do not allow people to mess with this location directly</span><br />
&nbsp; &nbsp; <span># Only internal redirects are allowed</span><br />
&nbsp; &nbsp; <span>internal</span>;<br />
<br />
&nbsp; &nbsp; <span># Location-specific logging</span><br />
&nbsp; &nbsp; <span>access_log</span> logs/internal_redirect.access.log main;<br />
&nbsp; &nbsp; <span>error_log</span> logs/internal_redirect.error.log warn;<br />
<br />
&nbsp; &nbsp; <span># Extract download url from the request</span><br />
&nbsp; &nbsp; set <span>$download_uri</span> <span>$2</span>;<br />
&nbsp; &nbsp; set <span>$download_host</span> <span>$1</span>;<br />
<br />
&nbsp; &nbsp; <span># Compose download url</span><br />
&nbsp; &nbsp; set <span>$download_url</span> <span>http</span>://<span>$download_host</span>/<span>$download_uri</span>;<br />
<br />
&nbsp; &nbsp; <span># Set download request headers</span><br />
&nbsp; &nbsp; <span>proxy_set_header</span> <span>Host</span> <span>$download_host</span>;<br />
&nbsp; &nbsp; <span>proxy_set_header</span> Authorization <span>''</span>;<br />
<br />
&nbsp; &nbsp; <span># The next two lines could be used if your storage </span><br />
&nbsp; &nbsp; <span># backend does not support Content-Disposition </span><br />
&nbsp; &nbsp; <span># headers used to specify file name browsers use </span><br />
&nbsp; &nbsp; <span># when save content to the disk</span><br />
&nbsp; &nbsp; proxy_hide_header Content-Disposition;<br />
&nbsp; &nbsp; add_header Content-Disposition <span>'attachment; filename=&quot;$args&quot;'</span>;<br />
<br />
&nbsp; &nbsp; <span># Do not touch local disks when proxying </span><br />
&nbsp; &nbsp; <span># content to clients</span><br />
&nbsp; &nbsp; proxy_max_temp_file_size <span>0</span>;<br />
<br />
&nbsp; &nbsp; <span># Download the file and send it to client</span><br />
&nbsp; &nbsp; <span>proxy_pass</span> <span>$download_url</span>;<br />
<span>&#125;</span></div></td></tr></tbody></table></div>
<p>After adding this location to our nginx config we could start sending responses with headers like the following:</p>
<div><table cellspacing="0" cellpadding="0"><tbody><tr><td><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div><span># This header will ask nginx to download a file </span><br />
<span># from http://some.site.com/secret/url.ext and send it to user</span><br />
X-Accel-Redirect: /internal_redirect/some.site.com/secret/url.ext<br />
<br />
<span># This header will ask nginx to download a file </span><br />
<span># from http://blah.com/secret/url and send it to user as cool.pdf</span><br />
X-Accel-Redirect: /internal_redirect/blah.com/secret/url?cool.pdf</div></td></tr></tbody></table></div>
<p>Here is an example code you could use in a Rails application to use our internal redirect location:</p>
<div><table cellspacing="0" cellpadding="0"><tbody><tr><td><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br /></div></td><td><div><span>def</span> x_accel_url<span>&#40;</span>url, file_name = <span>nil</span><span>&#41;</span><br />
&nbsp; uri = <span>&quot;/internal_redirect/#{url.gsub('http://', '')}&quot;</span><br />
&nbsp; uri <span>&lt;&lt;</span> <span>&quot;?#{file_name}&quot;</span> <span>if</span> file_name<br />
&nbsp; <span>return</span> uri<br />
<span>end</span><br />
<br />
<span>def</span> download<br />
&nbsp; headers<span>&#91;</span><span>'X-Accel-Redirect'</span><span>&#93;</span> = x_accel_url<span>&#40;</span>some_secret_url, pretty_name<span>&#41;</span><br />
&nbsp; render <span>:nothing</span> <span>=&gt;</span> <span>true</span><br />
<span>end</span></div></td></tr></tbody></table></div>
<p>As you can see, nginx is really powerful tool and when you turn your creativity on you can make it even more powerful. Stay tuned for more <a href="http://kovyrin.net/tag/nginx-fu/">Nginx-Fu</a> posts.</p>

<p><a href="http://feedads.g.doubleclick.net/~a/s4KHA4LgENGAamK4PL09U7Ia3f8/0/da"><img src="http://feedads.g.doubleclick.net/~a/s4KHA4LgENGAamK4PL09U7Ia3f8/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/s4KHA4LgENGAamK4PL09U7Ia3f8/1/da"><img src="http://feedads.g.doubleclick.net/~a/s4KHA4LgENGAamK4PL09U7Ia3f8/1/di" border="0" ismap="true"></img></a></p><div>
<a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=cuG-f1auDlo:LUrpmTqfO4k:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=cuG-f1auDlo:LUrpmTqfO4k:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=cuG-f1auDlo:LUrpmTqfO4k:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=cuG-f1auDlo:LUrpmTqfO4k:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=cuG-f1auDlo:LUrpmTqfO4k:V_sGLiPBpWU" border="0"></img></a>
</div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25095&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=25095&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://feedproxy.google.com/~r/Homo-Adminus/~3/cuG-f1auDlo/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Advanced Squid Caching in Scribd: Cache Invalidation Techniques</title>
		<link>http://feedproxy.google.com/~r/Homo-Adminus/~3/4ywVA01ppFY/?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=advanced-squid-caching-in-scribd-cache-invalidation-techniques</link>
		<comments>http://feedproxy.google.com/~r/Homo-Adminus/~3/4ywVA01ppFY/#comments</comments>
		<pubDate>Sat, 29 May 2010 17:02:17 +0000</pubDate>
		<dc:creator>Alexey Kovyrin</dc:creator>
				<category><![CDATA[Admin-tips]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[HTCP]]></category>
		<category><![CDATA[My Projects]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[invalidation]]></category>
		<category><![CDATA[networks]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[squid]]></category>

		<guid isPermaLink="false">http://kovyrin.net/?p=322</guid>
		<description><![CDATA[Having a reverse-proxy web cache as one of the major infrastructure elements brings many benefits for large web applications: it reduces your application servers load, reduces average response times on your site, etc. But there is one problem every developer experiences when works with such a cache &#8211; cached content invalidation.
It is a complex problem that usually consists of two smaller ones: individual cache elements invalidation (you need to keep an eye on your data changes and invalidate cached pages when related data changes) and full cache purges (sometimes your site layout or page templates change and you need to purge all the cached pages to make sure users will get new visual elements of layout changes). In this post I&#8217;d like to look at a few techniques we use at Scribd to solve cache invalidation problems.

So, the first problem &#8211; ongoing cache invalidation when content changes. This is actually a pretty simple task in squid: you just use HTCP protocol and send CLR requests to your caching farm (we didn&#8217;t find any HTCP protocol implementations so we&#8217;ve implemented our own simple client that supports just one command).
Since we use haproxy to balance our traffic in the cluster it is hard to predict where should we send a purge request. So we fan those out to all cache servers.
To make sure cache purging won&#8217;t slow the site down, especially considering we need to do more that just a simple cache purge (submit documents to search indexes, etc, etc), we just spool a &#8220;document changed&#8221; request to a queue and then have a set of asynchronous processes that do all the work in background.
Next, The Hard Problem &#8211; handling full cache purges w/o killing our backend servers with 5x-10x traffic (our normal hit ratio is ~90-95%).
We&#8217;ve spent a lot of time thinking about this problem and the first idea we came up with was to have a loop process somewhere that would iterate all documents we have cached and purge them one by one&#8230; but that does not seem to be a practical solution when you have tens of millions documents (and few page versions per document) and obviously the solution would not scale with constantly growing documents corpus.
So we kept brainstorming and finally got one idea that works just perfectly for us: what if we&#8217;d be able to take our traffic and define a function f(t) that would return a percentage of the traffic that should be purged at any moment in time. So we did it &#8211; we&#8217;ve implemented a nginx module that would version our cache by assigning every cached page a revision (using a custom HTTP-headers + Vary-caching) and would be able to slowly migrate the cache from one revision to another over a pre-defined period of time.
Having that module we are able to do so called &#8220;slow&#8221; cache purges that could take any time from a few minutes (that still helps to reduce the load spike generated by the hottest content) up to many hours (this is what we normally use) or days (never used this option, but it is definitely possible).
Here is an example 100% cache purge over an 8 hour interval:

 Daily hit ratio graph:


 Weekly hit ratio graph:



As you can see, during those slow purges our cached pages would be slowly updated without putting too much pressure on the backend. Cache hit ratio would slowly degrade and then slowly get back to its normal levels, but with our normal (6-8 hours) purges hit ratio never gets lower that 65-70% which makes it possible for us to save huge amounts of money on not having 90% spare capacity just for the cache purge load surges (we used to have lots of spare application cluster capacity before introducing this approach).



  
]]></description>
			<content:encoded><![CDATA[<p>Having a <a href="http://kovyrin.net/2008/10/25/advanced-squid-caching-for-rails-applications-preface/">reverse-proxy</a> web cache as one of the major infrastructure elements brings many benefits for large web applications: it reduces your application servers load, reduces average response times on your site, etc. But there is one problem every developer experiences when works with such a cache &#8211; <em>cached content invalidation</em>.</p>
<p>It is a complex problem that usually consists of two smaller ones: i<em>ndividual cache elements invalidation</em> (you need to keep an eye on your data changes and invalidate cached pages when related data changes) and <em>full cache purges</em> (sometimes your site layout or page templates change and you need to purge all the cached pages to make sure users will get new visual elements of layout changes). In this post I&#8217;d like to look at a few techniques we use at <a href="http://www.scribd.com/">Scribd</a> to solve cache invalidation problems.</p>
<p><span></span></p>
<hr />So, the <strong>first problem &#8211; ongoing cache invalidation when content changes</strong>. This is actually a pretty simple task in squid: you just use <a href="http://www.htcp.org/">HTCP protocol</a> and send CLR requests to your caching farm (we didn&#8217;t find any HTCP protocol implementations so we&#8217;ve implemented <a href="http://github.com/kovyrin/htcp-ruby">our own simple client</a> that supports just one command).</p>
<p>Since we use <a href="http://haproxy.1wt.eu/">haproxy</a> to balance our traffic in the cluster it is hard to predict where should we send a purge request. So we fan those out to all cache servers.</p>
<p>To make sure cache purging won&#8217;t slow the site down, especially considering we need to do more that just a simple cache purge (submit documents to search indexes, etc, etc), we just spool a &#8220;document changed&#8221; request to a queue and then have a set of <a href="http://github.com/kovyrin/loops">asynchronous processes</a> that do all the work in background.</p>
<p>Next, <strong>The Hard Problem &#8211; handling full cache purges w/o killing our backend servers</strong> with 5x-10x traffic (our normal hit ratio is ~90-95%).</p>
<p>We&#8217;ve spent a lot of time thinking about this problem and the first idea we came up with was to have a loop process somewhere that would iterate all documents we have cached and purge them one by one&#8230; but that does not seem to be a practical solution when you have tens of millions documents (and few page versions per document) and obviously the solution would not scale with constantly growing documents corpus.</p>
<p>So we kept brainstorming and finally got one idea that works just perfectly for us: what if we&#8217;d be able to take our traffic and define a function <em>f(t)</em> that would return a percentage of the traffic that should be purged at any moment in time. So we did it &#8211; we&#8217;ve implemented a nginx module that would version our cache by assigning every cached page a revision (<a href="http://kovyrin.net/2009/07/21/advanced-squid-caching-scribd-logged-in-users-complex-urls/">using a custom HTTP-headers + Vary-caching</a>) and would be able to slowly migrate the cache from one revision to another over a pre-defined period of time.</p>
<p>Having that module we are able to do so called &#8220;slow&#8221; cache purges that could take any time from a few minutes (that still helps to reduce the load spike generated by the hottest content) up to many hours (this is what we normally use) or days (never used this option, but it is definitely possible).</p>
<p>Here is an example 100% cache purge over an 8 hour interval:</p>
<ol>
<li> Daily hit ratio graph:<br />
<a href="http://img.skitch.com/20100529-pkx64g6the9winqcnk6sigiyns.png" rel="shadowbox[post-322];player=img;"><img rel="shadowbox" src="http://img.skitch.com/20100529-pkx64g6the9winqcnk6sigiyns.preview.jpg" alt="day" /></a>
</li>
<li> Weekly hit ratio graph:<br />
<a href="http://img.skitch.com/20100529-nk2hyafgtbw1pc1nrkgbec8st3.png" rel="shadowbox[post-322];player=img;"><img rel="shadowbox" src="http://img.skitch.com/20100529-nk2hyafgtbw1pc1nrkgbec8st3.preview.jpg" alt="week" /></a>
</li>
</ol>
<p>As you can see, during those slow purges our cached pages would be slowly updated without putting too much pressure on the backend. Cache hit ratio would slowly degrade and then slowly get back to its normal levels, but with our normal (6-8 hours) purges hit ratio never gets lower that 65-70% which makes it possible for us to save huge amounts of money on not having 90% spare capacity just for the cache purge load surges (we used to have lots of spare application cluster capacity before introducing this approach).</p>

<p><a href="http://feedads.g.doubleclick.net/~a/-nlVyidsWJg1e-DbtPeODrcR9bY/0/da"><img src="http://feedads.g.doubleclick.net/~a/-nlVyidsWJg1e-DbtPeODrcR9bY/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/-nlVyidsWJg1e-DbtPeODrcR9bY/1/da"><img src="http://feedads.g.doubleclick.net/~a/-nlVyidsWJg1e-DbtPeODrcR9bY/1/di" border="0" ismap="true"></img></a></p><div>
<a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=4ywVA01ppFY:b2ode2vaNL0:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=4ywVA01ppFY:b2ode2vaNL0:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=4ywVA01ppFY:b2ode2vaNL0:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Homo-Adminus?a=4ywVA01ppFY:b2ode2vaNL0:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Homo-Adminus?i=4ywVA01ppFY:b2ode2vaNL0:V_sGLiPBpWU" border="0"></img></a>
</div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=24897&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=24897&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://feedproxy.google.com/~r/Homo-Adminus/~3/4ywVA01ppFY/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
