Skip to content

Commit 75fd4b3

Browse files
author
Dmitry Lenev
committed
WL#8355 "Improve scalability by partitioning LOCK_grant lock."
This patch tries to solve scalability bottleneck caused by LOCK_grant for some workloads (e.g. Sysbench POINT_SELECT/InnoDB). The idea is to partition LOCK_grant rwlock into several partitions and to make read lock requests to acquire read lock on only one partition (write lock requests lock still have to lock all partitions). The specific partition to be used is determined by thread id. As result concurrent acquisitions of read lock by different threads are likely to use different partitions reducing negative effects of cache invalidation/concurrent atomic operations associated with LOCK_grant rwlock. New class class implementing rwlock partitioned by THD/thread id was added. Code in ACL subsystem was adjusted to use object of this class instead of LOCK_grant rwlock.
1 parent 1598cc4 commit 75fd4b3

File tree

13 files changed

+407
-69
lines changed

13 files changed

+407
-69
lines changed

mysql-test/suite/perfschema/r/func_mutex.result

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ where NAME = 'wait/synch/mutex/sql/LOCK_open';
99
NAME
1010
wait/synch/mutex/sql/LOCK_open
1111
select NAME from performance_schema.rwlock_instances
12-
where NAME = 'wait/synch/rwlock/sql/LOCK_grant';
12+
where NAME = 'wait/synch/rwlock/sql/LOCK_grant' limit 1;
1313
NAME
1414
wait/synch/rwlock/sql/LOCK_grant
1515
DROP TABLE IF EXISTS t1;

mysql-test/suite/perfschema/r/server_init.result

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,6 @@ where name like "wait/synch/mutex/sql/tz_LOCK";
120120
count(name)
121121
1
122122
select count(name) from rwlock_instances
123-
where name like "wait/synch/rwlock/sql/LOCK_grant";
124-
count(name)
125-
1
126-
select count(name) from rwlock_instances
127123
where name like "wait/synch/rwlock/sql/LOCK_sys_init_connect";
128124
count(name)
129125
1

mysql-test/suite/perfschema/t/func_mutex.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ flush status;
2121
select NAME from performance_schema.mutex_instances
2222
where NAME = 'wait/synch/mutex/sql/LOCK_open';
2323
select NAME from performance_schema.rwlock_instances
24-
where NAME = 'wait/synch/rwlock/sql/LOCK_grant';
24+
where NAME = 'wait/synch/rwlock/sql/LOCK_grant' limit 1;
2525

2626
--disable_warnings
2727
DROP TABLE IF EXISTS t1;

mysql-test/suite/perfschema/t/server_init.test

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,6 @@ select count(name) from mutex_instances
123123

124124
# Verify that these global rwlocks have been properly initilized in sql
125125

126-
select count(name) from rwlock_instances
127-
where name like "wait/synch/rwlock/sql/LOCK_grant";
128-
129126
select count(name) from rwlock_instances
130127
where name like "wait/synch/rwlock/sql/LOCK_sys_init_connect";
131128

sql/auth/partitioned_rwlock.h

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#ifndef PARTITIONED_RWLOCK_INCLUDED
2+
#define PARTITIONED_RWLOCK_INCLUDED
3+
4+
/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation; version 2 of the License.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
18+
19+
#include "mysql/psi/mysql_thread.h"
20+
21+
/**
22+
Implementation of read-write lock partitioned by thread id.
23+
24+
This rwlock provides better scalability in read-heavy environments by
25+
employing the following simple trick:
26+
*) Read lock is acquired only on one of its partitions. The specific
27+
partition is chosen according to thread id.
28+
*) Write lock is acquired on all partitions.
29+
30+
This way concurrent request for read lock made by different threads
31+
have a good chance not to disturb each other by doing cache invalidation
32+
and atomic operations. As result scalability in this scenario improves.
33+
OTOH acquisition of write lock becomes more expensive. So this rwlock
34+
is not supposed to be used in cases when number of write requests is
35+
significant.
36+
*/
37+
38+
class Partitioned_rwlock
39+
{
40+
public:
41+
Partitioned_rwlock() {}
42+
43+
/**
44+
@param parts Number of partitions.
45+
@param psi_key P_S instrumentation key to use for rwlock instances
46+
for partitions.
47+
*/
48+
bool init(uint parts, PSI_rwlock_key psi_key)
49+
{
50+
m_parts= parts;
51+
if (!(m_locks_array= new (std::nothrow) mysql_rwlock_t[m_parts]))
52+
return true;
53+
for (uint i= 0 ; i < m_parts; ++i)
54+
mysql_rwlock_init(psi_key, &m_locks_array[i]);
55+
return false;
56+
}
57+
void destroy()
58+
{
59+
for (uint i= 0 ; i < m_parts; ++i)
60+
mysql_rwlock_destroy(&m_locks_array[i]);
61+
delete [] m_locks_array;
62+
}
63+
void wrlock()
64+
{
65+
for (uint i= 0 ; i < m_parts; ++i)
66+
mysql_rwlock_wrlock(&m_locks_array[i]);
67+
}
68+
void wrunlock()
69+
{
70+
for (uint i= 0 ; i < m_parts; ++i)
71+
mysql_rwlock_unlock(&m_locks_array[i]);
72+
}
73+
void rdlock(uint thread_id)
74+
{
75+
mysql_rwlock_rdlock(&m_locks_array[thread_id%m_parts]);
76+
}
77+
/*
78+
One should use the same thread number for releasing read lock
79+
as was used for acquiring it,
80+
*/
81+
void rdunlock(uint thread_id)
82+
{
83+
mysql_rwlock_unlock(&m_locks_array[thread_id%m_parts]);
84+
}
85+
86+
private:
87+
mysql_rwlock_t* m_locks_array;
88+
uint m_parts;
89+
90+
Partitioned_rwlock(const Partitioned_rwlock&); // Non-copyable
91+
Partitioned_rwlock& operator=(const Partitioned_rwlock&); // Non-copyable
92+
};
93+
94+
95+
/**
96+
Read lock guard class for Partitioned_rwlock. Supports early unlocking.
97+
*/
98+
99+
class Partitioned_rwlock_read_guard
100+
{
101+
public:
102+
/**
103+
Acquires read lock on partitioned rwlock on behalf of thread.
104+
Automatically release lock in destructor.
105+
*/
106+
Partitioned_rwlock_read_guard(Partitioned_rwlock *rwlock, uint thread_id)
107+
: m_rwlock(rwlock), m_thread_id(thread_id)
108+
{
109+
m_rwlock->rdlock(m_thread_id);
110+
}
111+
112+
~Partitioned_rwlock_read_guard()
113+
{
114+
if (m_rwlock)
115+
m_rwlock->rdunlock(m_thread_id);
116+
}
117+
118+
/** Release read lock. Optional method for early unlocking. */
119+
void unlock()
120+
{
121+
m_rwlock->rdunlock(m_thread_id);
122+
m_rwlock= NULL;
123+
}
124+
125+
private:
126+
/**
127+
Pointer to partitioned rwlock which was acquired. NULL if lock was
128+
released early so destructor should not do anything.
129+
*/
130+
Partitioned_rwlock *m_rwlock;
131+
/**
132+
Id of thread on which behalf lock was acquired and which is to be used for
133+
unlocking.
134+
*/
135+
uint m_thread_id;
136+
137+
// Non-copyable
138+
Partitioned_rwlock_read_guard(const Partitioned_rwlock_read_guard&);
139+
Partitioned_rwlock_read_guard& operator=(const
140+
Partitioned_rwlock_read_guard&);
141+
};
142+
143+
144+
/**
145+
Write lock guard class for Partitioned_rwlock. Supports early unlocking.
146+
*/
147+
148+
class Partitioned_rwlock_write_guard
149+
{
150+
public:
151+
/**
152+
Acquires write lock on partitioned rwlock.
153+
Automatically release it in destructor.
154+
*/
155+
explicit Partitioned_rwlock_write_guard(Partitioned_rwlock *rwlock)
156+
: m_rwlock(rwlock)
157+
{
158+
m_rwlock->wrlock();
159+
}
160+
161+
~Partitioned_rwlock_write_guard()
162+
{
163+
if (m_rwlock)
164+
m_rwlock->wrunlock();
165+
}
166+
167+
/** Release write lock. Optional method for early unlocking. */
168+
void unlock()
169+
{
170+
m_rwlock->wrunlock();
171+
m_rwlock= NULL;
172+
}
173+
174+
private:
175+
/**
176+
Pointer to partitioned rwlock which was acquired. NULL if lock was
177+
released early so destructor should not do anything.
178+
*/
179+
Partitioned_rwlock *m_rwlock;
180+
181+
// Non-copyable
182+
Partitioned_rwlock_write_guard(const Partitioned_rwlock_write_guard&);
183+
Partitioned_rwlock_write_guard& operator=(const
184+
Partitioned_rwlock_write_guard&);
185+
};
186+
187+
#endif /* PARTITIONED_RWLOCK_INCLUDED */

sql/auth/sql_auth_cache.cc

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ bool initialized=0;
6565
bool allow_all_hosts=1;
6666
uint grant_version=0; /* Version of priv tables */
6767
my_bool validate_user_plugins= TRUE;
68+
/**
69+
Flag to track if rwlocks in ACL subsystem were initialized.
70+
Necessary because acl_free() can be called in some error scenarios
71+
without prior call to acl_init().
72+
*/
73+
bool rwlocks_initialized= false;
74+
75+
const uint LOCK_GRANT_PARTITIONS= 32;
76+
Partitioned_rwlock LOCK_grant;
6877

6978
#define FIRST_NON_YN_FIELD 26
7079

@@ -1333,6 +1342,9 @@ my_bool acl_init(bool dont_read_acl_tables)
13331342
&my_charset_utf8_bin);
13341343

13351344
mysql_rwlock_init(key_rwlock_proxy_users, &proxy_users_rwlock);
1345+
LOCK_grant.init(LOCK_GRANT_PARTITIONS, key_rwlock_LOCK_grant);
1346+
rwlocks_initialized= true;
1347+
13361348
/*
13371349
cache built-in native authentication plugins,
13381350
to avoid hash searches and a global mutex lock on every connect
@@ -1922,6 +1934,13 @@ void acl_free(bool end)
19221934
plugin_unlock(0, native_password_plugin);
19231935
delete acl_cache;
19241936
acl_cache=0;
1937+
1938+
if (rwlocks_initialized)
1939+
{
1940+
LOCK_grant.destroy();
1941+
mysql_rwlock_destroy(&proxy_users_rwlock);
1942+
rwlocks_initialized= false;
1943+
}
19251944
}
19261945
}
19271946

@@ -2357,7 +2376,8 @@ static my_bool grant_reload_procs_priv(THD *thd)
23572376
DBUG_RETURN(TRUE);
23582377
}
23592378

2360-
mysql_rwlock_wrlock(&LOCK_grant);
2379+
Partitioned_rwlock_write_guard lock(&LOCK_grant);
2380+
23612381
/* Save a copy of the current hash if we need to undo the grant load */
23622382
old_proc_priv_hash= proc_priv_hash;
23632383
old_func_priv_hash= func_priv_hash;
@@ -2375,7 +2395,6 @@ static my_bool grant_reload_procs_priv(THD *thd)
23752395
my_hash_free(&old_proc_priv_hash);
23762396
my_hash_free(&old_func_priv_hash);
23772397
}
2378-
mysql_rwlock_unlock(&LOCK_grant);
23792398

23802399
DBUG_RETURN(return_val);
23812400
}
@@ -2427,7 +2446,8 @@ my_bool grant_reload(THD *thd)
24272446
goto end;
24282447
}
24292448

2430-
mysql_rwlock_wrlock(&LOCK_grant);
2449+
LOCK_grant.wrlock();
2450+
24312451
old_column_priv_hash= column_priv_hash;
24322452

24332453
/*
@@ -2450,7 +2470,9 @@ my_bool grant_reload(THD *thd)
24502470
my_hash_free(&old_column_priv_hash);
24512471
free_root(&old_mem,MYF(0));
24522472
}
2453-
mysql_rwlock_unlock(&LOCK_grant);
2473+
2474+
LOCK_grant.wrunlock();
2475+
24542476
close_acl_tables(thd);
24552477

24562478
/*
@@ -2460,9 +2482,9 @@ my_bool grant_reload(THD *thd)
24602482
if (grant_reload_procs_priv(thd))
24612483
return_val= 1;
24622484

2463-
mysql_rwlock_wrlock(&LOCK_grant);
2485+
LOCK_grant.wrlock();
24642486
grant_version++;
2465-
mysql_rwlock_unlock(&LOCK_grant);
2487+
LOCK_grant.wrunlock();
24662488

24672489
end:
24682490
close_acl_tables(thd);

sql/auth/sql_auth_cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "hash_filo.h" // HASH, hash_filo
2525
#include "records.h" // READ_RECORD
2626
#include "read_write_lock.h" // Write_lock, Read_lock, lock_at
27+
#include "partitioned_rwlock.h" // Partitioned_rwlock
2728

2829
#include "prealloced_array.h"
2930

@@ -273,6 +274,7 @@ extern HASH acl_check_hosts;
273274
extern mysql_rwlock_t proxy_users_rwlock;
274275
extern bool allow_all_hosts;
275276
extern uint grant_version; /* Version of priv tables */
277+
extern Partitioned_rwlock LOCK_grant;
276278

277279
GRANT_NAME *name_hash_search(HASH *name_hash,
278280
const char *host,const char* ip,

0 commit comments

Comments
 (0)