Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
This is a follow-up from
We now need to add the code to ignore the slave if a user has just added content. Here's an initial patch that will check the user's session and then ignore the slave servers.
Comment | File | Size | Author |
---|---|---|---|
#95 | ignore_slave_4.patch | 2.06 KB | wonder95 |
#93 | ignore_slave_4.patch | 2.15 KB | wonder95 |
#91 | ignore_slave_4.patch | 2.15 KB | wonder95 |
#89 | 314358-89.patch | 1.82 KB | mikey_p |
#86 | ignore_slave_4.patch | 2.24 KB | wonder95 |
Comments
Comment #1
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedUpdate: now implements hook_comment and hook_nodeapi to set the session variable. The maximal lag time is a variable without UI.
Comment #2
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedA similar patch runs since almost a year on drupal.org. The only functional difference is that we do only ignore the slave for the very next page request.
Comment #3
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedOne could of course argue that this could also be in a contributed module. That would be ok with me.
Comment #4
Crell CreditAttribution: Crell commentedThe changes to hook_nodeapi() broke this. I approve of the approach, however, and I'd rather see this in core.
Hm, I wonder if hook_comment() should be broken out as well... :-)
Comment #5
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedAs requested, I've updated the patch. This patch nicely exposes two problems with the change to the nodeapi hook:
1) Code duplication: the fact that insert and update do the same are not that uncommon. For longer parts of common code you'll need to introduce yet another function.
2) Other hooks (here: hook_comment) haven't been converted.
Comment #6
Dries CreditAttribution: Dries commentedThis will need extensive comments before it can be considered.
Comment #7
Crell CreditAttribution: Crell commentedReroll with more descriptive comments in system_init(). Dries, if you want something else or comments elsewhere instead, you'll need to say what and where. Between that block and the docblock for Database::ignoreTarget() I think we're probably OK.
Comment #8
David StraussSubscribing.
Comment #9
moshe weitzman CreditAttribution: moshe weitzman commentedLooks simple and desireable to me. Comments are clear.
Comment #10
keith.smith CreditAttribution: keith.smith commentedOne very small note -- per the patches in #349504: Conform sentence spacing in code comments., code comments in core should only be separated by single spaces.
Comment #11
Dries CreditAttribution: Dries commentedTo me, it feels more logical, if, from an API point of view, we'd pass that information to the SQL query instead of implementing various hooks. That is, when doing an insert or updated in the comment table, why don't we pass a parameter to db_query()? I'd like to see us discuss some more.
Comment #12
David StraussThe industry-standard method of ensuring replication is to post a magic value to the database (INSERT or UPDATE) after performing the changes and simultaneously send the magic value to the client using a cookie or session. On the next page, check for your magic value against your request's assigned slave server. If you find the magic value, you know the slave is at least as up to date as it needs to be. If you do not find the value, read from the master.
My preferred way to handle the issue is both more complicated and more scalable: try to store the data necessary to effect the user's change in the session or (ideally) cookie. Then, when they load the next page, use PHP or (ideally) Javascript to integrate the user's change if it isn't part of the data read from the slave server. This gives users quick, satisfying feedback on their activity by exploiting how the only that client needs to immediately see the results of its interaction. Other site users can wait for the replication delay.
Comment #13
Crell CreditAttribution: Crell commentedI have to disagree with both of you.
Dries, what exactly do you mean? We're already passing information into db_query(), specifically, "try to use a slave server if available" (the target option key). What else would we pass in? "try to use a slave server if available and it's been less than X seconds since the last write by this process"? That puts a lot more work on the query author to figure out, and I don't even know what the implementation would look like internally.
David, your first suggestion involves an extra query for every slave query we run to see if it's safe to use, as well as an extra query for every write. That is a non-trivial number of new queries, as well as a non-trivial amount of work for module authors. I see lots of bugs cropping up from that if anyone tries to use a slave server.
As for saving the updated data to the session or a cookie only to bypass the database entirely, that's even worse from a module developer's point of view. You want to cache every node that gets saved in the database in order to check it on the next half-dozen page views? That still does nothing for Views or other aggregate queries, too. Those will need to pull from the master database either way, but only for that user.
And relying on Javascript for that logic is a non-starter, for obvious reasons.
This patch minimizes the work for module authors while providing a global kill switch (that we can handle mostly in core) to temporarily disable the slave for that user. There are, as a result, no extra queries run, and that user gets all up to date data for however long the site admin sets the timeout to (based, presumably, on empirical data of the replication delay on his server farm). I still haven't seen another implementation that provides this little hassle for module developers.
Comment #14
David StraussI said it was the industry standard. I didn't say it was right. ;-)
Comment #15
Crell CreditAttribution: Crell commentedSo we've discussed more, and found no better approach than what's listed here. Dries, unless you have a specific objection to this approach I'd ask that we move forward with it, as replication is really not useful without a disable mechanism (or acknowledgement that data could be very stale if you run replication).
Comment #16
moshe weitzman CreditAttribution: moshe weitzman commentedNeeds to use drupal_set_session() now. Should be a straightforward search/replace for $_SESSION ... We should actively unset() this if possible. For unset, we can access $_SESSION directly.
Comment #17
wonder95 CreditAttribution: wonder95 commentedMade the mods suggested by Moshe to use drupal_set_session().
Comment #18
moshe weitzman CreditAttribution: moshe weitzman commentedThanks for the change. Back to RTBC. Probably should wait for testbot's final thumbs up before commit.
Comment #20
wonder95 CreditAttribution: wonder95 commentedPer discussion with Crell and dmitrig01, modified call to hook_comment() into separate functions since $op no longer exists for hook_comment and created third function for setting session variable.
Comment #21
wonder95 CreditAttribution: wonder95 commentedChanging status to code needs review for testing.
Comment #23
drewish CreditAttribution: drewish commentedPHPDocs should start with /** rather than /*.
need to put a space after the commas before REQUEST_TIME.
Why does comment do
drupal_set_ignore_slave();
and node dodrupal_set_session('ignore_slave_server',REQUEST_TIME + variable_get('maximal_replication_lag', 10));
? it seems like they should both call the same function.
Comment #24
mikey_p CreditAttribution: mikey_p commentedWhat about moving drupal_set_ignore_slave into database.inc and calling it db_ignore_target and adding a parameter for the target/key to be ignored (could default to 'default' and 'slave') ?
My only other thought was maybe session.inc but it seems better fit for database.inc.
Comment #25
wonder95 CreditAttribution: wonder95 commentedMy thoughts, too. Here is a re-rolled patch that adds db_set_ignore_slave() to database.inc and modifies the insert and update hooks in comment and mode modules to use it. I did it on Windows and ran it through dos2unix, so there shouldn't be any syntax errors this time (hopefully).
Comment #26
drewish CreditAttribution: drewish commentedseems like it could use a test or two...
Comment #27
Crell CreditAttribution: Crell commentedA test would be good if possible, but I'm not entirely sure how we'd write it. Probably as a pair of JSON callbacks. Hm.
The comment block in system.module should be updated to mention the new utility function instead of talking about $_SESSION directly. Otherwise this looks OK. (I am tempted to push something into the Database class and make db_set_ignore_slave() a dumb wrapper, but I think that would put too much non-DB API code into the DB API proper. So let's leave it as is for now.)
Comment #28
wonder95 CreditAttribution: wonder95 commentedPatch has been re-rolled to add reference to db_set_ignore_slave() in comments in system.module.
Comment #29
Dries CreditAttribution: Dries commentedThe last patch looks good.
Three comments:
1. I'd improve the documentation a bit. Most people are not familiar with replication lag. I'd explain it in 1-2 sentences -- really crisp but enough to give them a clue and to google for details.
2. The variable_get() makes me a bit nervous. If I do replication to another data center, I'll have a different lag than when I do replication to a local server. I might have different lag times for different slave servers?
3. Is there a function to temporary disable a slave that seems to be down? For example, I have 3 slaves and slave #2 is down. If Drupal discovers this (i.e. it gets a time-out), can it take slave #2 out of rotation for a while using this function? I understand that this is not part of this patch, but it might keeping this in mind when doing the API design.
Comment #30
Crell CreditAttribution: Crell commentedIf you're doing live replication to a local server and a server in another state, I don't understand why you'd be using both of them as hot slaves. I admit I've never setup a replicated environment, but I don't understand why you'd want to do that in the first place. :-) If you did, you'd just set the timeout to a higher value, enough to catch all of your active slave servers. (If you're just doing backups via replication, then you wouldn't tell Drupal about your remote slave server.) So I think we're covered here.
Right now there's no handling for temporary DB outage. killes had opened another issue about that at one point but not much has happened there. Definitely a separate issue from this one; not sure how or if that can be addressed. Probably something we'd want to bring in some "big iron" people to give suggestions on. Someone go find IBM. :-)
So I think this is ready as soon as the docs beef up a bit, then. If this doesn't get in by DCDC, I'm making it a priority for the code sprint.
Comment #31
David StraussI think slave failure is best handled using clustering systems like Heartbeat 2 that transparently move service IPs to other functional systems within the cluster.
Drupal might be able to handle failing slave servers, but where would we store the persistent data about slave state, in the database? That would be inefficient. If a memory-based cache is installed, that might do the trick.
Comment #32
wonder95 CreditAttribution: wonder95 commentedUpdated patch with added comments in system.module to explain replication lag.
Comment #33
Crell CreditAttribution: Crell commentedComment looks good to me.
Comment #34
mmcdougall CreditAttribution: mmcdougall commentedin system.module patch above , isn't the following if condition exactly backwards?
i.e. should change
to
I've been testing this for consumersearch.com (d5!) and ran into this.
Comment #35
Crell CreditAttribution: Crell commentedHm. It is possible that you are correct. The value in the session is the timestamp until which we want to skip the slave server. So we ignore the slave server if the request time is less than the value in the session. Yeah, I think you are right. :-)
I understand that this patch has been backported to run on Drupal.org. Can any of the d.o admins weigh in on how well it's working? I really do want to get this into HEAD soon so that we can figure out what queries to flag.
Comment #36
David StraussHere's the patch we're running on d.o:
http://vcs.fourkitchens.com/drupal/6-patched/revision/27
Comment #37
wonder95 CreditAttribution: wonder95 commentedMade change above to match backported D5 patch:
Comment #39
wonder95 CreditAttribution: wonder95 commentedRe-rolled to account for change in core of hook names from hook_nodeapi_insert() and hook_nodeapi_update() to hook_node_insert() and hook_node_update().
Comment #40
wonder95 CreditAttribution: wonder95 commentedComment #41
Damien Tournoud CreditAttribution: Damien Tournoud commentedThe node module implementing hook_node_(update|insert)() and the comment module implementing hook_comment_(update|insert)()? Sorry, but that looks *very* ugly. There *has* to be a better way to do that.
Comment #42
David StraussReposting what I said in IRC:
"[An equivalent of this patch is] running on d.o, and it isn't causing any new problems. Unfortunately, the people who reported the problems we created [the patch] to solve haven't clearly indicated whether [the original problem is] solved. I believe the problem should be theoretically solved."
Comment #43
Damien Tournoud CreditAttribution: Damien Tournoud commentedBy definition, the node module control the hook_node_*() hooks, and the comment module control the hook_comment_*() hooks. Why not placing those calls just after the call to these hooks, instead of as a hook implementation itself?
Plus, a comment before the call would be great. It's not that clear what an isolated call to db_set_ignore_slave() does.
Comment #44
David StraussI personally prefer for modules to factor even their own functionality into their own hooks when possible.
Comment #45
moshe weitzman CreditAttribution: moshe weitzman commentedIt is somewhat common in Drupal for the a module to implement its own hooks. I don't find it inelegant at all. It keeps the original function smaller. See user_user(), user_user_operations(), ...
Comment #46
Damien Tournoud CreditAttribution: Damien Tournoud commentedHere is the bottom of node_save(). Why not putting the call to db_set_ignore_slave() there, where it will be both elegant and straightforward to understand?
Comment #47
Crell CreditAttribution: Crell commentedDamien, can you roll an updated patch then?
Comment #48
wonder95 CreditAttribution: wonder95 commentedI chatted with Damien in #drupal today, and he isn't planning on rolling a patch for this (he was just making a suggestion). Should we go ahead with this as is, or implement his suggestion and re-roll the patch?
Comment #49
drewish CreditAttribution: drewish commentedi agree that generally modules should use their own hooks but in this case the function being called seems to me like it should be part of node/comment_save() rather than done via hook.
Comment #50
David Strauss@drewish You may have a point. This is sort of an over-arching concern of node and comment saving rather than a hook-like concentration on the module's own data.
Comment #51
wonder95 CreditAttribution: wonder95 commentedHere is a re-rolled patch that adds db_set_ignore_slave() to the end of node_save() and comment_save() and removes the insert and update hooks for nodes and comments.
Comment #53
wonder95 CreditAttribution: wonder95 commentedAnother re-roll to add periods to the ends of the comments for db_set_ignore_slave() in node_save() and comment_save() (per Crell's request).
Comment #55
andypostsome bots were broken
Comment #56
Dries CreditAttribution: Dries commentedThis looks good. I'd rename db_set_ignore_slave to db_ignore_slave().
I don't see why/when we'd want to pass the duration using a parameter. Developers don't know the underlying system or architecture, so they can't decide to change the 10 to 12, IMO. That is always up to the people running the site because they are the only ones that really know. Because setting the $duration as a parameter overwrites the variable_get(), this is a dangerous parameter -- it takes away control from the system administrator.
Comment #57
Dries CreditAttribution: Dries commentedTagging and tracking.
Comment #58
David StraussI agree with Dries. There's no way for a developer to usefully override the site-wide configuration.
Setting to "needs work" because the default should be more than 10 seconds. I'd set it to 300. A small/medium site doesn't need the slave so much that the extra 290-second delay has a real impact on performance. A large site will be either need the longer delay or will be explicitly tuning the setting. Five minutes is long enough to allow the slave to break and resume interrupted replication without causing problems on the Drupal site from the old data.
Comment #59
wonder95 CreditAttribution: wonder95 commentedUpdated patch with three changes:
In regards to the fact that there is no way to set the delay, what about adding a field to the admin UI somewhere to allow the admin to set the $duration value?
Comment #61
David StraussThere's no reason to offer an admin interface for this option. Configuring slave servers already has to happen in settings.php.
Comment #62
David StraussThe duration argument still needs to go for db_ignore_slave().
Comment #63
wonder95 CreditAttribution: wonder95 commentedRe-rolled with default value removed from db_ignore_slave().
I used TortoiseCVS to create, so here's hoping it works...
Comment #65
David StraussLooks more like a failure in the test bot.
Comment #66
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedtrying again
Comment #68
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedwell, apparently, there is really a problem.
Comment #69
wonder95 CreditAttribution: wonder95 commentedFound the problem. In db_ignore_slave(), I had removed the $duration parameter, so it didn't like the fact that we were checking for something that wasn't there:
Changed it in three places:
to this:
Comment #70
wonder95 CreditAttribution: wonder95 commentedFound the problem. In db_ignore_slave(), I had removed the $duration parameter, so it didn't like the fact that we were checking for something that wasn't there:
Changed it in three places:
to this:
Comment #71
wonder95 CreditAttribution: wonder95 commentedHmmm, looks like something was wrong with the last attachment. Attaching again.
Comment #72
Dries CreditAttribution: Dries commented-
+ //set session variable with amount of time to delay before using slave
has a code style issue.- Tiny request. Let's add a small comment to document our choice of 300. I think we can borrow from David's comment in #58. Could be helpful to guilde operations people.
One last re-roll? :)
Comment #73
wonder95 CreditAttribution: wonder95 commentedRe-rolled with Dries' requested changes.
Comment #74
Dries CreditAttribution: Dries commentedCommitted to CVS HEAD. Yay.
Comment #75
Damien Tournoud CreditAttribution: Damien Tournoud commentedA minor but sensible nitpick: there is no point in cluttering the session if there is no slave server to start with. Let's check for that in db_ignore_slave().
Comment #76
Dries CreditAttribution: Dries commentedThe cookie should only be set when a comment or node is updated/inserted -- it shouldn't have dramatic performance impact. But yes, let's get that fixed -- it could give a small performance win to clean that up.
Comment #77
wonder95 CreditAttribution: wonder95 commentedAdded step to db_ignore_slave() to check for more than one db being used before setting session variable.
Comment #78
Crell CreditAttribution: Crell commentedIt looks like this patch doesn't account for the fact that the rest of the patch already went in. :-) We just need the tweak to db_ignore_slave() itself. Also, I think we can probably static-cache count($connection_info). Although you can add servers at runtime, that's a crazy edge case that has yet to be used outside of unit tests anyway. This is a micro-performance patch, so let's mind the micro-performance. :-)
Comment #79
mikey_p CreditAttribution: mikey_p commentedJust a note, this is currently in the bottom of database.inc in the defgroup for legacy stuff. I'd recommend moving it to between _db_error_page and db_fetch_object.
Comment #80
wonder95 CreditAttribution: wonder95 commentedHere are a couple static cache options (thanks to mikey_p for the help):
and
Which one is preferable?
Comment #81
Crell CreditAttribution: Crell commentedWell the second one doesn't look like it would actually work. :-) You should also use the new drupal_static() function. That said, the fastest approach is probably to cache $use_slave and then just if ($use_slave) { ... }
Comment #82
wonder95 CreditAttribution: wonder95 commentedUpdated patch with the following changes:
Comment #83
Damien Tournoud CreditAttribution: Damien Tournoud commentedFrankly, who cares about the micro-performance of db_ignore_slave()? The difference between using drupal_static() or just doing a count(Database::getConnectionInfo()) should not be measurable anyway. I vote for removing the static cache.
Comment #85
wonder95 CreditAttribution: wonder95 commentedOK, but does it hurt to leave the static cache in? It would seem better to err on the side of speed, no matter how tiny it is. Of course, the trade-off is it also means a few more lines of code. I'll let people more knowledgeable than me decide, though.
Just my $.02.
Comment #86
wonder95 CreditAttribution: wonder95 commentedRe-submitting with correct path to database.inc in the patch (TortoiseCVS is so inconsistent with that).
Comment #87
Dries CreditAttribution: Dries commentedI'm with DamZ, the static caching is unnecessary.
Comment #88
Dries CreditAttribution: Dries commentedI'm with DamZ, the static caching is unnecessary.
Comment #89
mikey_p CreditAttribution: mikey_p commentedMove out of @ingroup database-legacy.
I guess this is more of our future legacy?
Comment #90
Dries CreditAttribution: Dries commentedWe lost the slave checking in #89.
Comment #91
wonder95 CreditAttribution: wonder95 commentedSlave checking added back in, and function moved out of legacy group.
Comment #93
wonder95 CreditAttribution: wonder95 commentedOk, let's try that again, this time will all necessary closing braces.
Comment #94
Crell CreditAttribution: Crell commentedEverything except the $connection_info line is indented one level too deep. I don't know why. Otherwise it looks fine to me. Let's fix the spacing and RTBC.
Comment #95
wonder95 CreditAttribution: wonder95 commented@Crell, I was testing you to make sure you were on your toes. :-)
Here is is with proper indentation.
Comment #96
wonder95 CreditAttribution: wonder95 commentedOops, forgot to set to "needs review".
Comment #97
Crell CreditAttribution: Crell commentedNow we're in business.
Comment #98
Dries CreditAttribution: Dries commentedCommitted to CVS HEAD. Thanks.