From: Andrei Elkin Date: November 19 2010 2:52pm Subject: bzr commit into mysql-next-mr.crash-safe branch (andrei.elkin:3213) WL#5569 List-Archive: http://lists.mysql.com/commits/124474 Message-Id: <201011191452.oAJEqC6k009367@mysql1000.dsl.inet.fi> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============1731861084==" --===============1731861084== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///home/andrei/MySQL/BZR/2a-23May/WL/mysql-next-mr-wl5569/ based on revid:andrei.elkin@stripped 3213 Andrei Elkin 2010-11-19 wl#5569 recovery interfaces for wl#5599 implementation. The essence of this patch is to provide GAQ object implimentation and valid life cycle. The checkpoint handler prior to call store methods of wl#5599 is supposed to invoke rli->gaq->move_queue_head(&rli->workers). See a simulation of that near ev->update_pos() of the mail sql thread loop. The checkpoint info is composed as instance of Slave_job_group to reside as rli->gap->lwm. Todo: uncomment + // delete ev; // after ev->update_pos() event is garbage once the real checkpoint has been done. Todo: the real implemention needs to take care of filing Slave_job_group::update_current_binlog as initially so at time of executing Rotate/FD methods. + // experimental checkpoint per each scheduling attempt + // logics of next_event() + + rli->gaq->move_queue_head(&rli->workers); @ sql/log_event.cc Log_event::get_slave_worker_id() got shaped more to the final version with elements necessary to rli->gaq lify cycle. @ sql/log_event.h Log_event::mts_group_cnt is added as a part of GAQ index propagation path from C to W. @ sql/rpl_rli.h Further extension to RLI necessary to the distribution hash function (APH). @ sql/rpl_rli_pdb.cc Implementing circular_buffer_queue::*queue and few other methods incl ulong Slave_committed_queue::move_queue_head() the main concern for checkpoint. @ sql/rpl_rli_pdb.h Extending classes with few new member definitions necessary for GAQ interface / checkpoint / recovery. @ sql/rpl_slave.cc Simulation of the lwm-checkpoint and changes due to rpl_rli_pdb classes extensions. modified: sql/log_event.cc sql/log_event.h sql/rpl_rli.h sql/rpl_rli_pdb.cc sql/rpl_rli_pdb.h sql/rpl_slave.cc === modified file 'sql/log_event.cc' --- a/sql/log_event.cc 2010-11-18 14:00:52 +0000 +++ b/sql/log_event.cc 2010-11-19 14:51:58 +0000 @@ -2201,13 +2201,14 @@ bool Log_event::contains_partition_info( Slave_worker *Log_event::get_slave_worker_id(Relay_log_info const *rli) { + Slave_worker *worker= NULL; /* checking properties and perform corresponding actions */ // g if (contains_partition_info()) { // a lot of things inside `get_slave_worker_id' - Slave_worker *worker= get_slave_worker(get_db(), rli->workers /*, W_c */); + worker= get_slave_worker(get_db(), rli->workers /*, W_c */); const_cast(rli)->last_assigned_worker= worker; } @@ -2217,18 +2218,30 @@ Slave_worker *Log_event::get_slave_worke // insert {NULL, W_s} to yield a new Group_cnt indexed element in GAQ // Group_cnt= rli->gaq->e; // rli->gaq->en_queue({NULL, W_s}); + Slave_job_group gaq_item= + { + //{NULL, log_pos}, + log_pos, + + worker->id, // todo: -> NULL and implement set_dynamic in DA + + const_cast(rli)->mts_total_groups++}; + + rli->gaq->assigned_group_index= rli->gaq->en_queue((void *) &gaq_item); + + DBUG_ASSERT(rli->gaq->assigned_group_index != (ulong) -1); // gaq must have room + // B-event is appended to the Deferred Array associated with GCAP + // TODO: refine contains_partition_info() to not include BEGIN } // T if (ends_group()) { // assert (\exists P_k . W_s \in CGAP) if P_k is present in ev + mts_group_cnt= rli->gaq->assigned_group_index; - // cleanup: CGAP := nil - - // ev->group_cnt := GAQ.Group_cnt, so that - // at procesing *Worker*.WQ.last_group_cnt := ev->group_cnt + // *TODO* cleanup: CGAP := nil } // todo: p. the first p-event clears CGAP. Each p-event is appened to DA. @@ -2288,7 +2301,6 @@ static void * head_queue(Slave_jobs_queu return a job item through a struct which point is supplied via argument. */ static Slave_job_item * de_queue(Slave_jobs_queue *jobs, Slave_job_item *ret) - { if (jobs->e == jobs->s) { @@ -2483,10 +2495,7 @@ int slave_worker_exec_job(Slave_worker * } if (ev->ends_group()) { - w->slave_worker_ends_group(); - - // TODO: GAQ related - // w->last_group_done_index = ev->group_cnt + w->slave_worker_ends_group(ev->mts_group_cnt); } error= ev->do_apply_event(rli); @@ -2499,7 +2508,9 @@ int slave_worker_exec_job(Slave_worker * to remove w_rli w/a */ ev->update_pos(w->w_rli); - delete ev; // after ev->update_pos() event is garbage + + // delete ev; // after ev->update_pos() event is garbage + mysql_mutex_unlock(&w->jobs_lock); /* statistics */ === modified file 'sql/log_event.h' --- a/sql/log_event.h 2010-11-18 14:00:52 +0000 +++ b/sql/log_event.h 2010-11-19 14:51:58 +0000 @@ -992,6 +992,13 @@ public: */ ulong slave_exec_mode; + /** + Index in @c rli->gaq array to indicate a group that this event is purging. + The index is set by C:r to a group terminator event is checked by W at + the event execution. The indexed data represent the Worker progress status. + */ + ulong mts_group_cnt; + #ifdef MYSQL_SERVER THD* thd; === modified file 'sql/rpl_rli.h' --- a/sql/rpl_rli.h 2010-11-18 14:00:52 +0000 +++ b/sql/rpl_rli.h 2010-11-19 14:51:58 +0000 @@ -432,10 +432,13 @@ public: int slave_pending_jobs_max; Slave_worker *last_assigned_worker; // a hint to partitioning func for some events Slave_committed_queue *gaq; + DYNAMIC_ARRAY curr_group_assigned_parts; // CGAP Slave_worker* get_current_worker() const; Slave_worker* set_this_worker(Slave_worker *w) { return this_worker= w; } Slave_worker* this_worker; // used by w_rli. The cental rli has it as NULL. + ulonglong mts_total_groups; // total event groups distributed in current session + ulong least_occupied_worker; // ... todo: APH ... /** Helper function to do after statement completion. === modified file 'sql/rpl_rli_pdb.cc' --- a/sql/rpl_rli_pdb.cc 2010-11-18 14:00:52 +0000 +++ b/sql/rpl_rli_pdb.cc 2010-11-19 14:51:58 +0000 @@ -331,7 +331,7 @@ Slave_worker *get_free_worker(DYNAMIC_AR @c get_slave_worker(). Affected by the being committed group APH tuples are updated. - + @c last_group_done_index member is set to the arg value. for each D_i in CGEP assert (W_id == APH.W_id|P_d == D_i) update APH set U-- where P_d = D_i @@ -341,6 +341,166 @@ Slave_worker *get_free_worker(DYNAMIC_AR CGEP the Worker partition cache is cleaned up. */ -void Slave_worker::slave_worker_ends_group() +void Slave_worker::slave_worker_ends_group(ulong gaq_idx) +{ + last_group_done_index = gaq_idx; +} + + +/** + Class circular_buffer_queue +*/ + +ulong circular_buffer_queue::de_queue(uchar *val) +{ + ulong ret; + if (e == s) + { + DBUG_ASSERT(len == 0); + return (ulong) -1; + } + + ret= e; + get_dynamic(&Q, val, e); + len--; + + // pre boundary cond + if (a == s) + a= e; + e= (e + 1) % s; + + // post boundary cond + if (a == e) + e= s; + + DBUG_ASSERT(e == s || + (len == (a >= e)? (a - e) : + (s + a - e))); + + return ret; +} + +ulong circular_buffer_queue::en_queue(void *item) +{ + ulong ret; + if (a == s) + { + DBUG_ASSERT(a == Q.elements); + return (ulong) -1; + } + + // store + + ret= a; + set_dynamic(&Q, (uchar*) item, ret= a); + + + // pre-boundary cond + if (e == s) + e= a; + + a= (a + 1) % s; + len++; + + // post-boundary cond + if (a == e) + a= s; + DBUG_ASSERT(a == e || + len == (a >= e) ? + (a - e) : (s + a - e)); + return ret; +} + +void* circular_buffer_queue::head_queue() +{ + uchar *ret= NULL; + if (e == s) + { + DBUG_ASSERT(len == 0); + } + else + { + get_dynamic(&Q, (uchar*) ret, e); + } + return (void*) ret; +} + +/** + two index comparision. + + @note The caller makes sure the args are within the valid + range, incl cases the queue is empty or full. + + @return TRUE if the first arg identifies a queue entity ordered + before one defined by the 2nd arg, + FALSE otherwise. +*/ +bool circular_buffer_queue::gt(ulong i, ulong k) +{ + if (i >= e) + if (k >= e) + return i > k; + else + return FALSE; + else + if (k >= e) + return TRUE; + else + return i > k; +} + +/** + The queue is processed from the head item by item + to purge items representing committed groups. + Progress of each Worker is monitored through @c last_done + and @c last_group_done_index. + It's compared first against the polled + to break out of the loop at once if no progress. + + + The caller is expected to be the checkpoint handler. + + A copy of the last discarded item containing + the refreshed value of the committed low-water-mark is stored + into @c lwm member for further caller's processing. + + + @return number of discarded items +*/ +ulong Slave_committed_queue::move_queue_head(DYNAMIC_ARRAY *ws) { + ulong i, cnt= 0; + for (i= e; i != a && !empty();) + { + Slave_worker *w_i; + Slave_job_group g; + ulong l; + get_dynamic(&Q, (uchar *) &g, i); + get_dynamic(ws, (uchar *) &w_i, g.worker_id); + get_dynamic(&last_done, (uchar *) &l, w_i->id); + + DBUG_ASSERT(l <= s); + + if (l == w_i->last_group_done_index) + break; /* no progress case */ + + DBUG_ASSERT(w_i->last_group_done_index >= i || + (((i > a && e > a) || a == s) && (w_i->last_group_done_index < a))); + + if (w_i->last_group_done_index == i || gt(w_i->last_group_done_index, i)) + { + ulong ind= de_queue((uchar*) &lwm); + + DBUG_ASSERT(ind == i); + DBUG_ASSERT(g.total_seqno == lwm.total_seqno); + + set_dynamic(&last_done, (uchar*) &i, w_i->id); + } + else + break; + cnt++; + i= (i + 1) % s; + } + + return cnt; } === modified file 'sql/rpl_rli_pdb.h' --- a/sql/rpl_rli_pdb.h 2010-11-18 14:00:52 +0000 +++ b/sql/rpl_rli_pdb.h 2010-11-19 14:51:58 +0000 @@ -25,24 +25,68 @@ typedef struct slave_job_item void *data; } Slave_job_item; +/** + The class defines a type of queue with a predefined max size that is + implemented using the circular memory buffer. + That is items of the queue are accessed as indexed elements of + the array buffer in a way that when the index value reaches + a max value it wraps around to point to the first buffer element. +*/ class circular_buffer_queue { public: DYNAMIC_ARRAY Q; - ulong s; - ulong a; - ulong e; + ulong s; // the Size of the queue in terms of element + ulong a; // first Available index to append at (next to tail) + ulong e; // the head index volatile ulong len; // it is also queried to compute least occupied circular_buffer_queue(uint el_size, ulong max, uint alloc_inc= 0) : s(max), a(0), e(max), len(0) { + DBUG_ASSERT(s < ULONG_MAX); my_init_dynamic_array(&Q, el_size, s, alloc_inc); - }; - circular_buffer_queue () {}; + } + circular_buffer_queue () {} + ~circular_buffer_queue () { delete_dynamic(&Q); } + + /** + Content of the being dequeued item is copied to the arg-pointer + location. + + @return the queue's array index that the de-queued item + locates at, or + an error encoded in beyond the index legacy range. + */ + ulong de_queue(uchar *); + + /** + return the index where the arg item locates + or an error encoded as a value in beyond of the legacy range + [0, circular_buffer_max_index]. + + Todo: define the range. + */ + ulong en_queue(void *item); + /** + return the value of @c data member of the head of the queue. + */ + void* head_queue(); + bool gt(ulong i, ulong k); // comparision of ordering of two entities + bool empty() { return e == s; } + bool full() { return a == s; } }; +typedef struct st_slave_job_group +{ + //struct event_coordinates coord; + my_off_t pos; // filename in Slave_committed_queue::current_binlog[] + + ulong worker_id; + ulonglong total_seqno; +} Slave_job_group; + /** Group Assigned Queue whose first element identifies first gap in committed sequence. The head of the queue is therefore next to @@ -51,22 +95,43 @@ public: class Slave_committed_queue : public circular_buffer_queue { public: - // Allocation of file_name that is common for all Slave_assigned_job_group:s + + /* Allocation of file_name that is common for all Slave_assigned_job_group:s */ char current_binlog[FN_REFLEN]; - void update_current_binlog(const char *post_rotate); //master's Rotate exec it - Slave_committed_queue (const char *log, uint el_size, ulong max, uint inc= 0) + + /* master's Rot-ev exec */ + void update_current_binlog(const char *post_rotate); + + /* + The last checkpoint time Low-Water-Mark + */ + Slave_job_group lwm; + + /* last time processed indexes for each worker */ + DYNAMIC_ARRAY last_done; + + /* the being assigned group index in GAQ */ + ulong assigned_group_index; + + Slave_committed_queue (const char *log, uint el_size, ulong max, uint n, + uint inc= 0) : circular_buffer_queue(el_size, max, inc) { + uint k; strmake(current_binlog, log, sizeof(current_binlog) - 1); - }; -}; + my_init_dynamic_array(&last_done, sizeof(s), n, 0); + for (k= 0; k < n; k++) + insert_dynamic(&last_done, (uchar*) &s); // empty for each Worker + } + + ~Slave_committed_queue () + { + delete_dynamic(&last_done); + } -typedef struct st_slave_assigned_job_group -{ - struct event_coordinates coord; - ulong worker_id; - ulonglong total_seqno; -} Slave_assigned_job_group; + /* Checkpoint routine refreshes the queue */ + ulong move_queue_head(DYNAMIC_ARRAY *ws); +}; class Slave_jobs_queue : public circular_buffer_queue { @@ -90,9 +155,10 @@ public: // @c last_group_done_index is for recovery, although can be viewed // as statistics as well. // C marks a T-event with the incremented group_cnt that is - // an index in GAQ; W stores it at the event execution. + // an index in GAQ; W stores it at the event execution. // C polls the value periodically to maintain an array // of the indexes in order to progress on GAQ's lwm, see @c next_event(). + // see @c Log_event::group_cnt. volatile ulong last_group_done_index; // it's index in GAQ List data_in_use; // events are still in use by SQL thread @@ -126,7 +192,7 @@ public: size_t get_number_worker_fields(); - void slave_worker_ends_group(); // CGEP walk through to upd APH + void slave_worker_ends_group(ulong); // CGEP walk through to upd APH private: bool read_info(Rpl_info_handler *from); === modified file 'sql/rpl_slave.cc' --- a/sql/rpl_slave.cc 2010-11-18 14:50:54 +0000 +++ b/sql/rpl_slave.cc 2010-11-19 14:51:58 +0000 @@ -2676,6 +2676,15 @@ int apply_event_and_update_pos(Log_event // if (!rli->is_parallel_exec() || ev->only_serial_exec()) error= ev->update_pos(rli); +#if 1 + + // experimental checkpoint per each scheduling attempt + // logics of next_event() + + rli->gaq->move_queue_head(&rli->workers); + +#endif + #ifndef DBUG_OFF DBUG_PRINT("info", ("update_pos error = %d", error)); if (!rli->belongs_to_client()) @@ -2852,7 +2861,7 @@ static int exec_relay_log_event(THD* thd if (thd->variables.binlog_rows_query_log_events) handle_rows_query_log_event(ev, rli); - if (!rli->is_parallel_exec() && + if (!rli->is_parallel_exec() && !ev->only_serial_exec() && ev->get_type_code() != ROWS_QUERY_LOG_EVENT) // mts todo: check this case { @@ -3648,7 +3657,8 @@ int slave_start_workers(Relay_log_info * uint i; int error= 0; - // TODO: CGEP dynarray holds id:s of partitions of the Current being executed Group + // CGAP dynarray holds id:s of partitions of the Current being executed Group + my_init_dynamic_array(&rli->curr_group_assigned_parts, NAME_LEN, SLAVE_INIT_DBS_IN_GROUP, 1); // GAQ queue holds seqno:s of scheduled groups. C polls workers in // @c lwm_checkpoint_period to update GAQ (see @c @next_event()) @@ -3657,8 +3667,9 @@ int slave_start_workers(Relay_log_info * // ::slave_max_pending_jobs is the worst case when all jobs contain // one event and map to one worker. rli->gaq= new Slave_committed_queue(rli->get_group_master_log_name(), - sizeof(Slave_assigned_job_group), - ::slave_max_pending_jobs); + sizeof(Slave_job_group), + ::slave_max_pending_jobs, n); + rli->mts_total_groups= 0; for (i= 0; i < n; i++) { uint k; --===============1731861084== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/andrei.elkin@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: andrei.elkin@stripped # target_branch: file:///home/andrei/MySQL/BZR/2a-23May/WL/mysql-next-\ # mr-wl5569/ # testament_sha1: 88a8dc42deef0452cac90a64160ee1651ea84fd8 # timestamp: 2010-11-19 16:52:12 +0200 # base_revision_id: andrei.elkin@stripped\ # hebusjgdcpo18bs9 # # Begin bundle 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 Fur++B/8XckU4UJB2tyXjA== --===============1731861084==--