How to correctly remove a module in a staged environment? Some modules have deïnstallation routines. Which typically remove databasetables for that module, variables from the variable table and locales introduced by that module. These routines live in the .install of that module. 

Therefore, they cannot be ran without that module being present. So here are our current steps. My question is: can this be done simpler and more effectively? Say I remove foo_bar module.


In the RCS, prepare a new release, where: 

All css and theme-overrides that use or build on top of- foo_bar are removed.
All css and theme-overrides for modules depending on foo_bar are removed.

Push that release to acceptance. Test the deïnstallation (from admin/modules) with a very recent copy of the production database.
If all goes well, deploy the new codebase to production, and deïnstall foo_bar and its dependencies there. This will invoke the uninstall in the various modules, cleaning up the database. 
In the RCS (git), prepare a new release where the code is actually removed. 
Deploy that to acceptance where we test if nothing accidentally depended on this (some ugly modules or theme functions include files directly from other modules. Most notably CSS, JS or image-files).
If accepted, deploy new release to production. production now has a clean database and a clean codebase.


Problem that I can't see how to solve, is that this always needs two releases. Since in Drupal a release requires the site to be offline, this means two times downtime just to remove one module. It also requires two release procedures, which, in professional hosting environments can be very expensive, time-consuming or frustrating. 

If we remove the module from codebase in the first iteration, we cannot run the uninstall hooks, keeping many lint in the database; not just a few tables, but mostly variables and locales. 
If we don't remove the module from codebase, that means the codebase will grow with stale, unused code; this gives no performance overhead, but does make maintaining the code harder and harder.

How do you deal with this?

[edit: added note about deployment being a tough procedure, often]
Be very careful about keeping your database and code in sync; as you mention in your question, the modules to be uninstalled need to stay in the code base until their uninstall hooks are run on the live database.  This is a limitation of Drupal that a git pull workflow alone is not going to solve.

I would recommend that instead of attempting to adjust your process, you instead look for ways to reduce the downtime required to process your updates.  I would recommend setting up a ying / yang multisite staging environment to solve this problem.  n.b. I have not used the scripts contained in the preceding link; you may wish to set things up differently, following the same idea that you can swap your live and stage sites during deployment.

Continue to follow the same procedure you outlined in your question with the following adjustments:

a. Sync from dev to stage (yang) as usual. Test by doing an uninstall of the modules to be removed followed by code removal, etc.  Git workflow notes: create a tag or note the hashid of the different states of your code: all modules in place prior to uninstall, code modules removed, your overrides &amp; c. removed, etc. as needed.  Perhaps only two references are needed.

b. When testing is complete and accepted, restore the code on stage (yang) to the state of live (ying).

c. Prepare the live (ying) site for update by disabling any user's ability to change content on the system. An sql update to the permission table will usually do here. At this point, users will still be able to read content on the live site, but will get a permission denied error if they try to update content. (If you are cool, perhaps you could change the permission denied handler to print an appropriate notice that the function is temporarily unavailable).

d. Now push the live (ying) database back to stage (yang) database, excluding the permissions table from the update.

e. Repeat step a. again.  If you have your hashtags handy, it should be easy to restore to the state where the modules to be removed exist, run the uninstall hooks on the database, and then advance back to the state of the code where your items from step 1 are merged back in.

f. You are now ready to swap ying and yang. Do this by adjusting your Apache configuration directives. Note that if you do an /etc/init.d/apache restart, some connections may be dropped, but /etc/init.d/apache reload will allow for a clean swap.

g. Live is now 'yang'; the permissions table is unmodified here, so users can create content.  If you automate steps e. and f., the time unavailable should be very low.

h. Push live (yang) back to stage (ying), both code and database -- or push from dev, as needed. You now have a clean environment ready for your next iteration.