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