ReplicaKillSwitch.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. namespace Drupal\Core\Database;
  3. use Drupal\Component\Datetime\TimeInterface;
  4. use Drupal\Core\Site\Settings;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  7. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  8. use Symfony\Component\HttpKernel\KernelEvents;
  9. /**
  10. * Provides replica server kill switch to ignore it.
  11. */
  12. class ReplicaKillSwitch implements EventSubscriberInterface {
  13. /**
  14. * The settings object.
  15. *
  16. * @var \Drupal\Core\Site\Settings
  17. */
  18. protected $settings;
  19. /**
  20. * The time service.
  21. *
  22. * @var \Drupal\Component\Datetime\TimeInterface
  23. */
  24. protected $time;
  25. /**
  26. * The session.
  27. *
  28. * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
  29. */
  30. protected $session;
  31. /**
  32. * Constructs a ReplicaKillSwitch object.
  33. *
  34. * @param \Drupal\Core\Site\Settings $settings
  35. * The settings object.
  36. * @param \Drupal\Component\Datetime\TimeInterface $time
  37. * The time service.
  38. * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
  39. * The session.
  40. */
  41. public function __construct(Settings $settings, TimeInterface $time, SessionInterface $session) {
  42. $this->settings = $settings;
  43. $this->time = $time;
  44. $this->session = $session;
  45. }
  46. /**
  47. * Denies access to replica database on the current request.
  48. *
  49. * @see https://www.drupal.org/node/2286193
  50. */
  51. public function trigger() {
  52. $connection_info = Database::getConnectionInfo();
  53. // Only set ignore_replica_server if there are replica servers being used,
  54. // which is assumed if there are more than one.
  55. if (count($connection_info) > 1) {
  56. // Five minutes is long enough to allow the replica to break and resume
  57. // interrupted replication without causing problems on the Drupal site
  58. // from the old data.
  59. $duration = $this->settings->get('maximum_replication_lag', 300);
  60. // Set session variable with amount of time to delay before using replica.
  61. $this->session->set('ignore_replica_server', $this->time->getRequestTime() + $duration);
  62. }
  63. }
  64. /**
  65. * Checks and disables the replica database server if appropriate.
  66. *
  67. * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
  68. * The Event to process.
  69. */
  70. public function checkReplicaServer(GetResponseEvent $event) {
  71. // Ignore replica database servers for this request.
  72. //
  73. // In Drupal's distributed database structure, new data is written to the
  74. // master and then propagated to the replica servers. This means there is a
  75. // lag between when data is written to the master and when it is available
  76. // on the replica. At these times, we will want to avoid using a replica
  77. // server temporarily. For example, if a user posts a new node then we want
  78. // to disable the replica server for that user temporarily to allow the
  79. // replica server to catch up.
  80. // That way, that user will see their changes immediately while for other
  81. // users we still get the benefits of having a replica server, just with
  82. // slightly stale data. Code that wants to disable the replica server should
  83. // use the 'database.replica_kill_switch' service's trigger() method to set
  84. // 'ignore_replica_server' session flag to the timestamp after which the
  85. // replica can be re-enabled.
  86. if ($this->session->has('ignore_replica_server')) {
  87. if ($this->session->get('ignore_replica_server') >= $this->time->getRequestTime()) {
  88. Database::ignoreTarget('default', 'replica');
  89. }
  90. else {
  91. $this->session->remove('ignore_replica_server');
  92. }
  93. }
  94. }
  95. /**
  96. * {@inheritdoc}
  97. */
  98. public static function getSubscribedEvents() {
  99. $events[KernelEvents::REQUEST][] = ['checkReplicaServer'];
  100. return $events;
  101. }
  102. }