| 1 | /***************************************************************************** |
| 2 | |
| 3 | Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. |
| 4 | Copyright (c) 2017, 2018, MariaDB Corporation. |
| 5 | |
| 6 | This program is free software; you can redistribute it and/or modify it under |
| 7 | the terms of the GNU General Public License as published by the Free Software |
| 8 | Foundation; version 2 of the License. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 13 | |
| 14 | You should have received a copy of the GNU General Public License along with |
| 15 | this program; if not, write to the Free Software Foundation, Inc., |
| 16 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
| 17 | |
| 18 | *****************************************************************************/ |
| 19 | |
| 20 | /**************************************************//** |
| 21 | @file include/trx0purge.h |
| 22 | Purge old versions |
| 23 | |
| 24 | Created 3/26/1996 Heikki Tuuri |
| 25 | *******************************************************/ |
| 26 | |
| 27 | #ifndef trx0purge_h |
| 28 | #define trx0purge_h |
| 29 | |
| 30 | #include "trx0rseg.h" |
| 31 | #include "que0types.h" |
| 32 | |
| 33 | /** A dummy undo record used as a return value when we have a whole undo log |
| 34 | which needs no purge */ |
| 35 | extern trx_undo_rec_t trx_purge_dummy_rec; |
| 36 | |
| 37 | /********************************************************************//** |
| 38 | Calculates the file address of an undo log header when we have the file |
| 39 | address of its history list node. |
| 40 | @return file address of the log */ |
| 41 | UNIV_INLINE |
| 42 | fil_addr_t |
| 43 | trx_purge_get_log_from_hist( |
| 44 | /*========================*/ |
| 45 | fil_addr_t node_addr); /*!< in: file address of the history |
| 46 | list node of the log */ |
| 47 | /** Prepend the history list with an undo log. |
| 48 | Remove the undo log segment from the rseg slot if it is too big for reuse. |
| 49 | @param[in] trx transaction |
| 50 | @param[in,out] undo undo log |
| 51 | @param[in,out] mtr mini-transaction */ |
| 52 | void |
| 53 | trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr); |
| 54 | /*******************************************************************//** |
| 55 | This function runs a purge batch. |
| 56 | @return number of undo log pages handled in the batch */ |
| 57 | ulint |
| 58 | trx_purge( |
| 59 | /*======*/ |
| 60 | ulint n_purge_threads, /*!< in: number of purge tasks to |
| 61 | submit to task queue. */ |
| 62 | bool truncate); /*!< in: truncate history if true */ |
| 63 | |
| 64 | /** Rollback segements from a given transaction with trx-no |
| 65 | scheduled for purge. */ |
| 66 | class TrxUndoRsegs { |
| 67 | private: |
| 68 | typedef std::vector<trx_rseg_t*, ut_allocator<trx_rseg_t*> > |
| 69 | trx_rsegs_t; |
| 70 | public: |
| 71 | typedef trx_rsegs_t::iterator iterator; |
| 72 | typedef trx_rsegs_t::const_iterator const_iterator; |
| 73 | |
| 74 | /** Default constructor */ |
| 75 | TrxUndoRsegs() {} |
| 76 | /** Constructor */ |
| 77 | TrxUndoRsegs(trx_rseg_t& rseg) |
| 78 | : m_commit(rseg.last_commit), m_rsegs(1, &rseg) {} |
| 79 | /** Constructor */ |
| 80 | TrxUndoRsegs(trx_id_t trx_no, trx_rseg_t& rseg) |
| 81 | : m_commit(trx_no << 1), m_rsegs(1, &rseg) {} |
| 82 | |
| 83 | /** @return the transaction commit identifier */ |
| 84 | trx_id_t trx_no() const { return m_commit >> 1; } |
| 85 | |
| 86 | bool operator!=(const TrxUndoRsegs& other) const |
| 87 | { return m_commit != other.m_commit; } |
| 88 | bool empty() const { return m_rsegs.empty(); } |
| 89 | void erase(iterator& it) { m_rsegs.erase(it); } |
| 90 | iterator begin() { return(m_rsegs.begin()); } |
| 91 | iterator end() { return(m_rsegs.end()); } |
| 92 | const_iterator begin() const { return m_rsegs.begin(); } |
| 93 | const_iterator end() const { return m_rsegs.end(); } |
| 94 | |
| 95 | /** Compare two TrxUndoRsegs based on trx_no. |
| 96 | @param elem1 first element to compare |
| 97 | @param elem2 second element to compare |
| 98 | @return true if elem1 > elem2 else false.*/ |
| 99 | bool operator()(const TrxUndoRsegs& lhs, const TrxUndoRsegs& rhs) |
| 100 | { |
| 101 | return(lhs.m_commit > rhs.m_commit); |
| 102 | } |
| 103 | |
| 104 | private: |
| 105 | /** Copy trx_rseg_t::last_commit */ |
| 106 | trx_id_t m_commit; |
| 107 | /** Rollback segments of a transaction, scheduled for purge. */ |
| 108 | trx_rsegs_t m_rsegs; |
| 109 | }; |
| 110 | |
| 111 | typedef std::priority_queue< |
| 112 | TrxUndoRsegs, |
| 113 | std::vector<TrxUndoRsegs, ut_allocator<TrxUndoRsegs> >, |
| 114 | TrxUndoRsegs> purge_pq_t; |
| 115 | |
| 116 | /** Chooses the rollback segment with the oldest committed transaction */ |
| 117 | struct TrxUndoRsegsIterator { |
| 118 | /** Constructor */ |
| 119 | TrxUndoRsegsIterator(); |
| 120 | /** Sets the next rseg to purge in purge_sys. |
| 121 | Executed in the purge coordinator thread. |
| 122 | @return whether anything is to be purged */ |
| 123 | inline bool set_next(); |
| 124 | |
| 125 | private: |
| 126 | // Disable copying |
| 127 | TrxUndoRsegsIterator(const TrxUndoRsegsIterator&); |
| 128 | TrxUndoRsegsIterator& operator=(const TrxUndoRsegsIterator&); |
| 129 | |
| 130 | /** The current element to process */ |
| 131 | TrxUndoRsegs m_rsegs; |
| 132 | /** Track the current element in m_rsegs */ |
| 133 | TrxUndoRsegs::const_iterator m_iter; |
| 134 | }; |
| 135 | |
| 136 | /* Namespace to hold all the related functions and variables need for truncate |
| 137 | of undo tablespace. */ |
| 138 | namespace undo { |
| 139 | |
| 140 | typedef std::vector<ulint> undo_spaces_t; |
| 141 | typedef std::vector<trx_rseg_t*> rseg_for_trunc_t; |
| 142 | |
| 143 | /** Magic Number to indicate truncate action is complete. */ |
| 144 | const ib_uint32_t s_magic = 76845412; |
| 145 | |
| 146 | /** Truncate Log file Prefix. */ |
| 147 | const char* const s_log_prefix = "undo_" ; |
| 148 | |
| 149 | /** Truncate Log file Extension. */ |
| 150 | const char* const s_log_ext = "trunc.log" ; |
| 151 | |
| 152 | /** Populate log file name based on space_id |
| 153 | @param[in] space_id id of the undo tablespace. |
| 154 | @return DB_SUCCESS or error code */ |
| 155 | dberr_t populate_log_file_name( |
| 156 | ulint space_id, |
| 157 | char*& log_file_name); |
| 158 | |
| 159 | /** Create the truncate log file. |
| 160 | @param[in] space_id id of the undo tablespace to truncate. |
| 161 | @return DB_SUCCESS or error code. */ |
| 162 | dberr_t init(ulint space_id); |
| 163 | |
| 164 | /** Mark completion of undo truncate action by writing magic number to |
| 165 | the log file and then removing it from the disk. |
| 166 | If we are going to remove it from disk then why write magic number ? |
| 167 | This is to safeguard from unlink (file-system) anomalies that will keep |
| 168 | the link to the file even after unlink action is successfull and |
| 169 | ref-count = 0. |
| 170 | @param[in] space_id id of the undo tablespace to truncate.*/ |
| 171 | void done(ulint space_id); |
| 172 | |
| 173 | /** Check if TRUNCATE_DDL_LOG file exist. |
| 174 | @param[in] space_id id of the undo tablespace. |
| 175 | @return true if exist else false. */ |
| 176 | bool is_log_present(ulint space_id); |
| 177 | |
| 178 | /** Track UNDO tablespace mark for truncate. */ |
| 179 | class Truncate { |
| 180 | public: |
| 181 | void create() |
| 182 | { |
| 183 | m_undo_for_trunc = ULINT_UNDEFINED; |
| 184 | m_scan_start = 1; |
| 185 | m_purge_rseg_truncate_frequency = |
| 186 | ulint(srv_purge_rseg_truncate_frequency); |
| 187 | } |
| 188 | |
| 189 | /** Clear the cached rollback segment. Normally done |
| 190 | when purge is about to shutdown. */ |
| 191 | void clear() |
| 192 | { |
| 193 | reset(); |
| 194 | rseg_for_trunc_t temp; |
| 195 | m_rseg_for_trunc.swap(temp); |
| 196 | } |
| 197 | |
| 198 | /** Is tablespace selected for truncate. |
| 199 | @return true if undo tablespace is marked for truncate */ |
| 200 | bool is_marked() const |
| 201 | { |
| 202 | return(!(m_undo_for_trunc == ULINT_UNDEFINED)); |
| 203 | } |
| 204 | |
| 205 | /** Mark the tablespace for truncate. |
| 206 | @param[in] undo_id tablespace for truncate. */ |
| 207 | void mark(ulint undo_id) |
| 208 | { |
| 209 | m_undo_for_trunc = undo_id; |
| 210 | |
| 211 | m_scan_start = (undo_id + 1) |
| 212 | % (srv_undo_tablespaces_active + 1); |
| 213 | if (m_scan_start == 0) { |
| 214 | /* Note: UNDO tablespace ids starts from 1. */ |
| 215 | m_scan_start = 1; |
| 216 | } |
| 217 | |
| 218 | /* We found an UNDO-tablespace to truncate so set the |
| 219 | local purge rseg truncate frequency to 1. This will help |
| 220 | accelerate the purge action and in turn truncate. */ |
| 221 | m_purge_rseg_truncate_frequency = 1; |
| 222 | } |
| 223 | |
| 224 | /** Get the tablespace marked for truncate. |
| 225 | @return tablespace id marked for truncate. */ |
| 226 | ulint get_marked_space_id() const |
| 227 | { |
| 228 | return(m_undo_for_trunc); |
| 229 | } |
| 230 | |
| 231 | /** Add rseg to truncate vector. |
| 232 | @param[in,out] rseg rseg for truncate */ |
| 233 | void add_rseg_to_trunc(trx_rseg_t* rseg) |
| 234 | { |
| 235 | m_rseg_for_trunc.push_back(rseg); |
| 236 | } |
| 237 | |
| 238 | /** Get number of rsegs registered for truncate. |
| 239 | @return return number of rseg that belongs to tablespace mark |
| 240 | for truncate. */ |
| 241 | ulint rsegs_size() const |
| 242 | { |
| 243 | return(m_rseg_for_trunc.size()); |
| 244 | } |
| 245 | |
| 246 | /** Get ith registered rseg. |
| 247 | @param[in] id index of rseg to get. |
| 248 | @return reference to registered rseg. */ |
| 249 | trx_rseg_t* get_ith_rseg(ulint id) |
| 250 | { |
| 251 | ut_ad(id < m_rseg_for_trunc.size()); |
| 252 | return(m_rseg_for_trunc.at(id)); |
| 253 | } |
| 254 | |
| 255 | /** Reset for next rseg truncate. */ |
| 256 | void reset() |
| 257 | { |
| 258 | m_undo_for_trunc = ULINT_UNDEFINED; |
| 259 | m_rseg_for_trunc.clear(); |
| 260 | |
| 261 | /* Sync with global value as we are done with |
| 262 | truncate now. */ |
| 263 | m_purge_rseg_truncate_frequency = static_cast<ulint>( |
| 264 | srv_purge_rseg_truncate_frequency); |
| 265 | } |
| 266 | |
| 267 | /** Get the tablespace id to start scanning from. |
| 268 | @return id of UNDO tablespace to start scanning from. */ |
| 269 | ulint get_scan_start() const |
| 270 | { |
| 271 | return(m_scan_start); |
| 272 | } |
| 273 | |
| 274 | /** Check if the tablespace needs fix-up (based on presence of |
| 275 | DDL truncate log) |
| 276 | @param space_id space id of the undo tablespace to check |
| 277 | @return true if fix up is needed else false */ |
| 278 | bool needs_fix_up(ulint space_id) const |
| 279 | { |
| 280 | return(is_log_present(space_id)); |
| 281 | } |
| 282 | |
| 283 | /** Add undo tablespace to truncate vector. |
| 284 | @param[in] space_id space id of tablespace to |
| 285 | truncate */ |
| 286 | static void add_space_to_trunc_list(ulint space_id) |
| 287 | { |
| 288 | s_spaces_to_truncate.push_back(space_id); |
| 289 | } |
| 290 | |
| 291 | /** Clear the truncate vector. */ |
| 292 | static void clear_trunc_list() |
| 293 | { |
| 294 | s_spaces_to_truncate.clear(); |
| 295 | } |
| 296 | |
| 297 | /** Is tablespace marked for truncate. |
| 298 | @param[in] space_id space id to check |
| 299 | @return true if marked for truncate, else false. */ |
| 300 | static bool is_tablespace_truncated(ulint space_id) |
| 301 | { |
| 302 | return(std::find(s_spaces_to_truncate.begin(), |
| 303 | s_spaces_to_truncate.end(), space_id) |
| 304 | != s_spaces_to_truncate.end()); |
| 305 | } |
| 306 | |
| 307 | /** Was a tablespace truncated at startup |
| 308 | @param[in] space_id space id to check |
| 309 | @return whether space_id was truncated at startup */ |
| 310 | static bool was_tablespace_truncated(ulint space_id) |
| 311 | { |
| 312 | return(std::find(s_fix_up_spaces.begin(), |
| 313 | s_fix_up_spaces.end(), |
| 314 | space_id) |
| 315 | != s_fix_up_spaces.end()); |
| 316 | } |
| 317 | |
| 318 | /** Get local rseg purge truncate frequency |
| 319 | @return rseg purge truncate frequency. */ |
| 320 | ulint get_rseg_truncate_frequency() const |
| 321 | { |
| 322 | return(m_purge_rseg_truncate_frequency); |
| 323 | } |
| 324 | |
| 325 | /* Start writing log information to a special file. |
| 326 | On successfull completion, file is removed. |
| 327 | On crash, file is used to complete the truncate action. |
| 328 | @param space_id space id of undo tablespace |
| 329 | @return DB_SUCCESS or error code. */ |
| 330 | dberr_t start_logging(ulint space_id) |
| 331 | { |
| 332 | return(init(space_id)); |
| 333 | } |
| 334 | |
| 335 | /* Mark completion of logging./ |
| 336 | @param space_id space id of undo tablespace */ |
| 337 | void done_logging(ulint space_id) |
| 338 | { |
| 339 | return(done(space_id)); |
| 340 | } |
| 341 | |
| 342 | private: |
| 343 | /** UNDO tablespace is mark for truncate. */ |
| 344 | ulint m_undo_for_trunc; |
| 345 | |
| 346 | /** rseg that resides in UNDO tablespace is marked for |
| 347 | truncate. */ |
| 348 | rseg_for_trunc_t m_rseg_for_trunc; |
| 349 | |
| 350 | /** Start scanning for UNDO tablespace from this space_id. |
| 351 | This is to avoid bias selection of one tablespace always. */ |
| 352 | ulint m_scan_start; |
| 353 | |
| 354 | /** Rollback segment(s) purge frequency. This is local |
| 355 | value maintained along with global value. It is set to global |
| 356 | value on start but when tablespace is marked for truncate it |
| 357 | is updated to 1 and then minimum value among 2 is used by |
| 358 | purge action. */ |
| 359 | ulint m_purge_rseg_truncate_frequency; |
| 360 | |
| 361 | /** List of UNDO tablespace(s) to truncate. */ |
| 362 | static undo_spaces_t s_spaces_to_truncate; |
| 363 | public: |
| 364 | /** Undo tablespaces that were truncated at startup */ |
| 365 | static undo_spaces_t s_fix_up_spaces; |
| 366 | }; /* class Truncate */ |
| 367 | |
| 368 | }; /* namespace undo */ |
| 369 | |
| 370 | /** The control structure used in the purge operation */ |
| 371 | class purge_sys_t |
| 372 | { |
| 373 | public: |
| 374 | /** signal state changes; os_event_reset() and os_event_set() |
| 375 | are protected by rw_lock_x_lock(latch) */ |
| 376 | MY_ALIGNED(CACHE_LINE_SIZE) |
| 377 | os_event_t event; |
| 378 | /** latch protecting view, m_enabled */ |
| 379 | MY_ALIGNED(CACHE_LINE_SIZE) |
| 380 | rw_lock_t latch; |
| 381 | private: |
| 382 | /** whether purge is enabled; protected by latch and my_atomic */ |
| 383 | int32_t m_enabled; |
| 384 | /** number of pending stop() calls without resume() */ |
| 385 | int32_t m_paused; |
| 386 | public: |
| 387 | que_t* query; /*!< The query graph which will do the |
| 388 | parallelized purge operation */ |
| 389 | MY_ALIGNED(CACHE_LINE_SIZE) |
| 390 | ReadView view; /*!< The purge will not remove undo logs |
| 391 | which are >= this view (purge view) */ |
| 392 | /** Total number of tasks submitted by srv_purge_coordinator_thread. |
| 393 | Not accessed by other threads. */ |
| 394 | ulint n_submitted; |
| 395 | /** Number of completed tasks. Accessed by srv_purge_coordinator |
| 396 | and srv_worker_thread by my_atomic. */ |
| 397 | ulint n_completed; |
| 398 | |
| 399 | /** Iterator to the undo log records of committed transactions */ |
| 400 | struct iterator |
| 401 | { |
| 402 | bool operator<=(const iterator& other) const |
| 403 | { |
| 404 | if (commit < other.commit) return true; |
| 405 | if (commit > other.commit) return false; |
| 406 | return undo_no <= other.undo_no; |
| 407 | } |
| 408 | |
| 409 | /** @return the commit number of the transaction */ |
| 410 | trx_id_t trx_no() const { return commit >> 1; } |
| 411 | void reset_trx_no(trx_id_t trx_no) { commit = trx_no << 1; } |
| 412 | |
| 413 | /** 2 * trx_t::no + old_insert of the committed transaction */ |
| 414 | trx_id_t commit; |
| 415 | /** The record number within the committed transaction's undo |
| 416 | log, increasing, purged from from 0 onwards */ |
| 417 | undo_no_t undo_no; |
| 418 | }; |
| 419 | |
| 420 | /** The tail of the purge queue; the last parsed undo log of a |
| 421 | committed transaction. */ |
| 422 | iterator tail; |
| 423 | /** The head of the purge queue; any older undo logs of committed |
| 424 | transactions may be discarded (history list truncation). */ |
| 425 | iterator head; |
| 426 | /*-----------------------------*/ |
| 427 | bool next_stored; /*!< whether rseg holds the next record |
| 428 | to purge */ |
| 429 | trx_rseg_t* rseg; /*!< Rollback segment for the next undo |
| 430 | record to purge */ |
| 431 | ulint page_no; /*!< Page number for the next undo |
| 432 | record to purge, page number of the |
| 433 | log header, if dummy record */ |
| 434 | ulint offset; /*!< Page offset for the next undo |
| 435 | record to purge, 0 if the dummy |
| 436 | record */ |
| 437 | ulint hdr_page_no; /*!< Header page of the undo log where |
| 438 | the next record to purge belongs */ |
| 439 | ulint hdr_offset; /*!< Header byte offset on the page */ |
| 440 | |
| 441 | |
| 442 | TrxUndoRsegsIterator |
| 443 | rseg_iter; /*!< Iterator to get the next rseg |
| 444 | to process */ |
| 445 | |
| 446 | purge_pq_t purge_queue; /*!< Binary min-heap, ordered on |
| 447 | TrxUndoRsegs::trx_no. It is protected |
| 448 | by the pq_mutex */ |
| 449 | PQMutex pq_mutex; /*!< Mutex protecting purge_queue */ |
| 450 | |
| 451 | undo::Truncate undo_trunc; /*!< Track UNDO tablespace marked |
| 452 | for truncate. */ |
| 453 | |
| 454 | |
| 455 | /** |
| 456 | Constructor. |
| 457 | |
| 458 | Some members may require late initialisation, thus we just mark object as |
| 459 | uninitialised. Real initialisation happens in create(). |
| 460 | */ |
| 461 | |
| 462 | purge_sys_t() : event(NULL), m_enabled(false) {} |
| 463 | |
| 464 | |
| 465 | /** Create the instance */ |
| 466 | void create(); |
| 467 | |
| 468 | /** Close the purge system on shutdown */ |
| 469 | void close(); |
| 470 | |
| 471 | /** @return whether purge is enabled */ |
| 472 | bool enabled() |
| 473 | { |
| 474 | return my_atomic_load32_explicit(&m_enabled, MY_MEMORY_ORDER_RELAXED); |
| 475 | } |
| 476 | /** @return whether purge is enabled */ |
| 477 | bool enabled_latched() |
| 478 | { |
| 479 | ut_ad(rw_lock_own_flagged(&latch, RW_LOCK_FLAG_X | RW_LOCK_FLAG_S)); |
| 480 | return bool(m_enabled); |
| 481 | } |
| 482 | /** @return whether the purge coordinator is paused */ |
| 483 | bool paused() |
| 484 | { return my_atomic_load32_explicit(&m_paused, MY_MEMORY_ORDER_RELAXED); } |
| 485 | /** @return whether the purge coordinator is paused */ |
| 486 | bool paused_latched() |
| 487 | { |
| 488 | ut_ad(rw_lock_own_flagged(&latch, RW_LOCK_FLAG_X | RW_LOCK_FLAG_S)); |
| 489 | return m_paused != 0; |
| 490 | } |
| 491 | |
| 492 | /** Enable purge at startup. Not protected by latch; the main thread |
| 493 | will wait for purge_sys.enabled() in srv_start() */ |
| 494 | void coordinator_startup() |
| 495 | { |
| 496 | ut_ad(!enabled()); |
| 497 | my_atomic_store32_explicit(&m_enabled, true, MY_MEMORY_ORDER_RELAXED); |
| 498 | } |
| 499 | |
| 500 | /** Disable purge at shutdown */ |
| 501 | void coordinator_shutdown() |
| 502 | { |
| 503 | ut_ad(enabled()); |
| 504 | my_atomic_store32_explicit(&m_enabled, false, MY_MEMORY_ORDER_RELAXED); |
| 505 | } |
| 506 | |
| 507 | /** @return whether the purge coordinator thread is active */ |
| 508 | bool running(); |
| 509 | /** Stop purge during FLUSH TABLES FOR EXPORT */ |
| 510 | void stop(); |
| 511 | /** Resume purge at UNLOCK TABLES after FLUSH TABLES FOR EXPORT */ |
| 512 | void resume(); |
| 513 | }; |
| 514 | |
| 515 | /** The global data structure coordinating a purge */ |
| 516 | extern purge_sys_t purge_sys; |
| 517 | |
| 518 | /** Info required to purge a record */ |
| 519 | struct trx_purge_rec_t { |
| 520 | trx_undo_rec_t* undo_rec; /*!< Record to purge */ |
| 521 | roll_ptr_t roll_ptr; /*!< File pointr to UNDO record */ |
| 522 | }; |
| 523 | |
| 524 | #include "trx0purge.ic" |
| 525 | |
| 526 | #endif /* trx0purge_h */ |
| 527 | |