|
| 1 | +SUMMARY |
| 2 | +------- |
| 3 | + |
| 4 | +Django\_replicated is a Django `database |
| 5 | +router <http://docs.djangoproject.com/en/dev/topics/db/multi-db/#topics-db-multi-db-routing>`_ |
| 6 | +designed to support more or less automatic master-slave replication. It |
| 7 | +keeps an internal state that depends on user intent to read or to write |
| 8 | +into a database. Depending on this state it automatically uses the right |
| 9 | +database (master or slave) for all SQL operations. |
| 10 | + |
| 11 | +INSTALLATION |
| 12 | +------------ |
| 13 | + |
| 14 | +1. Install django\_replicated distribution using "python setup.py |
| 15 | + install". |
| 16 | + |
| 17 | +2. In settings.py configure your master and slave databases in a |
| 18 | + standard way: |
| 19 | + |
| 20 | + :: |
| 21 | + |
| 22 | + DATABASES { |
| 23 | + 'default': { |
| 24 | + # ENGINE, HOST, etc. |
| 25 | + }, |
| 26 | + 'slave1': { |
| 27 | + # ENGINE, HOST, etc. |
| 28 | + }, |
| 29 | + 'slave2': { |
| 30 | + # ENGINE, HOST, etc. |
| 31 | + }, |
| 32 | + } |
| 33 | + |
| 34 | +3. Teach django\_replicated which databases are slaves: |
| 35 | + |
| 36 | + :: |
| 37 | + |
| 38 | + DATABASE_SLAVES = ['slave1', 'slave2'] |
| 39 | + |
| 40 | + The 'default' database is always treated as master. |
| 41 | + |
| 42 | +4. Configure a replication router: |
| 43 | + |
| 44 | + :: |
| 45 | + |
| 46 | + DATABASE_ROUTERS = ['django_replicated.ReplicationRouter'] |
| 47 | + |
| 48 | +5. Configure timeout to exclude a database from the available list after |
| 49 | + an unsuccessful ping: |
| 50 | + |
| 51 | + :: |
| 52 | + |
| 53 | + DATABASE_DOWNTIME = 20 |
| 54 | + |
| 55 | + The default downtime value is 60 seconds. |
| 56 | + |
| 57 | +USAGE |
| 58 | +----- |
| 59 | + |
| 60 | +Django\_replicated routes SQL queries into different databases based not |
| 61 | +only on their type (insert/update/delete vs. select) but also on its own |
| 62 | +current state. This is done to avoid situation when in a single logical |
| 63 | +operations you're doing both writes and reads. If all writes would go |
| 64 | +into one database and reads would be from another one then you won't |
| 65 | +have a consistent view of the world because of two reasons: |
| 66 | + |
| 67 | +- when using transactions the result of writes won't be replicated into |
| 68 | + a slave until commit |
| 69 | +- even in a non-transactional environment there's always a certain lag |
| 70 | + between updates in a master and in slaves |
| 71 | + |
| 72 | +Django\_replicated expects you to define what these logical operations |
| 73 | +are doing: writing/reading or only reading. Then it will try to use |
| 74 | +slave databases only for purely reading operations. |
| 75 | + |
| 76 | +To define this there are several methods. |
| 77 | + |
| 78 | +Middleware |
| 79 | +~~~~~~~~~~ |
| 80 | + |
| 81 | +If your project is built in accordance to principles of HTTP where GET |
| 82 | +requests don't cause changes in the system (unless by side effects) then |
| 83 | +most of the work is done by simply using a middleware : |
| 84 | + |
| 85 | +:: |
| 86 | + |
| 87 | + MIDDLEWARE_CLASSES = [ |
| 88 | + ... |
| 89 | + 'django_replicated.middleware.ReplicationMiddleware', |
| 90 | + ... |
| 91 | + ] |
| 92 | + |
| 93 | +The middleware sets replication state to use slaves during handling of |
| 94 | +GET and HEAD requests and to use a master otherwise. |
| 95 | + |
| 96 | +While this is usually enough there are cases when DB access is not |
| 97 | +controlled explicitly by your business logic. Good examples are implicit |
| 98 | +creation of sessions on first access, writing some bookkeeping info, |
| 99 | +implicit registration of a user account somewhere inside the system. |
| 100 | +These things can happen at arbitrary moments of time, including during |
| 101 | +GET requests. |
| 102 | + |
| 103 | +Generally django\_replicated handles this by always providing a master |
| 104 | +databases for write operations. If this is not enough (say you still |
| 105 | +want to read a newly created session and want to make sure that it will |
| 106 | +be read from a master) you can always instruct Django ORM to `use a |
| 107 | +certain |
| 108 | +database <http://docs.djangoproject.com/en/dev/topics/db/multi-db/#manually-selecting-a-database>`_. |
| 109 | + |
| 110 | +Decorators |
| 111 | +~~~~~~~~~~ |
| 112 | + |
| 113 | +If your system doesn't depend on the method of HTTP request to do writes |
| 114 | +and reads you can use decorators to wrap individual views into master or |
| 115 | +slave replication modes: |
| 116 | + |
| 117 | +:: |
| 118 | + |
| 119 | + from django_replicated.decorators import use_master, use_slave |
| 120 | + |
| 121 | + @use_master |
| 122 | + def my_view(request, ...): |
| 123 | + # master database used for all db operations during |
| 124 | + # execution of the view (if not explicitly overridden). |
| 125 | + |
| 126 | + @use_slave |
| 127 | + def my_view(request, ...): |
| 128 | + # same with slave connection |
| 129 | + |
| 130 | +GET after POST |
| 131 | +~~~~~~~~~~~~~~ |
| 132 | + |
| 133 | +There is a special case that needs addressing when working with |
| 134 | +asynchronous replication scheme. Replicas can lag behind a master |
| 135 | +database on receiving updates. In practice this mean that after |
| 136 | +submitting a POST form that redirects to a page with updated data this |
| 137 | +page may be requested from a slave replica that wasn't updated yet. And |
| 138 | +the user will have an impression that the submit didn't work. |
| 139 | + |
| 140 | +To overcome this problem both ReplicationMiddleware and decorators |
| 141 | +support special technique where handling of a GET request resulting from |
| 142 | +a redirect after a POST is explicitly routed to a master database. |
| 143 | + |
| 144 | +Global overrides |
| 145 | +~~~~~~~~~~~~~~~~ |
| 146 | + |
| 147 | +In some cases it might be necessary to override how the middleware |
| 148 | +chooses a target database based on the HTTP request method. For example |
| 149 | +you might want to route certain POST requests to a slave if you know |
| 150 | +that the request handler doesn't do any writes. The settings variable |
| 151 | +``REPLICATED_VIEWS_OVERRIDES`` holds the mapping of view names |
| 152 | +(urlpatterns names) or view import paths to database names: |
| 153 | + |
| 154 | +:: |
| 155 | + |
| 156 | + REPLICATED_VIEWS_OVERRIDES = { |
| 157 | + 'api-store-event': 'slave', |
| 158 | + 'app.views.do_smthg': 'master', |
| 159 | + } |
| 160 | + |
| 161 | +Disabling state switching |
| 162 | +~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 163 | + |
| 164 | +There are cases when you want to disable switching of replication modes |
| 165 | +entirely. A most common example is testing your code with tests that use |
| 166 | +non-commiting transactions to preserve data between testcases. Each test |
| 167 | +is called with a default master database which it uses to load fixtures. |
| 168 | +Then if any code that the test calls will switch replication to the |
| 169 | +slave mode, it won't see any fixture data in a test slave database |
| 170 | +because the master never commits. |
| 171 | + |
| 172 | +You can disable mode switching for such cases: |
| 173 | + |
| 174 | +:: |
| 175 | + |
| 176 | + from django_replicated import utils |
| 177 | + utils.disable_state_change() |
| 178 | + |
| 179 | +(There's also a similar ``enalble_state_change()`` function.) |
0 commit comments