Skip to content

Commit 5d4af7c

Browse files
authored
fix: Stop locking catalog for every resources. (#80)
* fix: Stop locking catalog for every resources. Historically, this provider takes a (golang) lock on a database for every resource of this database. So only one create/update/delete of resource by database can be done at the same time. (As it's a RWLock, read of resources can be parallelized) Since #5 refactoring, I mistakenly change the lock which now takes a write lock even for read operations (basically the plan part of Terraform). So provider was slower since v1.10 We could fix this and take a read lock for read operations as before but actually I don't see the point of this lock. For me, most operations could be done at the same time. Most of the request are now done in a transaction, the only risk is that a transaction will fail to commit if multiple resources modifies the same database line. That's the case for `postgresql_default_privileges`, one `pg_default_acl` row could represents multiple resources, so this PR takes a lock on the owner role to avoid that. This should make the provider way faster. Fix #48
1 parent 4571914 commit 5d4af7c

File tree

5 files changed

+58
-15
lines changed

5 files changed

+58
-15
lines changed

postgresql/config.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,6 @@ type Client struct {
141141
config Config
142142

143143
databaseName string
144-
145-
// PostgreSQL lock on pg_catalog. Many of the operations that Terraform
146-
// performs are not permitted to be concurrent. Unlike traditional
147-
// PostgreSQL tables that use MVCC, many of the PostgreSQL system
148-
// catalogs look like tables, but are not in-fact able to be
149-
// concurrently updated.
150-
catalogLock sync.RWMutex
151144
}
152145

153146
// NewClient returns client config for the specified database.

postgresql/helpers.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ func PGResourceFunc(fn func(*DBConnection, *schema.ResourceData) error) func(*sc
1414
return func(d *schema.ResourceData, meta interface{}) error {
1515
client := meta.(*Client)
1616

17-
client.catalogLock.Lock()
18-
defer client.catalogLock.Unlock()
19-
2017
db, err := client.Connect()
2118
if err != nil {
2219
return err
@@ -30,9 +27,6 @@ func PGResourceExistsFunc(fn func(*DBConnection, *schema.ResourceData) (bool, er
3027
return func(d *schema.ResourceData, meta interface{}) (bool, error) {
3128
client := meta.(*Client)
3229

33-
client.catalogLock.Lock()
34-
defer client.catalogLock.Unlock()
35-
3630
db, err := client.Connect()
3731
if err != nil {
3832
return false, err
@@ -447,3 +441,19 @@ func getRoleOID(db QueryAble, role string) (int, error) {
447441
}
448442
return oid, nil
449443
}
444+
445+
// Lock a role and all his members to avoid concurrent updates on some resources
446+
func pgLockRole(txn *sql.Tx, role string) error {
447+
if _, err := txn.Exec("SELECT pg_advisory_xact_lock(oid::bigint) FROM pg_roles WHERE rolname = $1", role); err != nil {
448+
return fmt.Errorf("could not get advisory lock for role %s: %w", role, err)
449+
}
450+
451+
if _, err := txn.Exec(
452+
"SELECT pg_advisory_xact_lock(member::bigint) FROM pg_auth_members JOIN pg_roles ON roleid = pg_roles.oid WHERE rolname = $1",
453+
role,
454+
); err != nil {
455+
return fmt.Errorf("could not get advisory lock for members of role %s: %w", role, err)
456+
}
457+
458+
return nil
459+
}

postgresql/resource_postgresql_database.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ func createDatabase(db *DBConnection, d *schema.ResourceData) error {
122122

123123
var err error
124124
if owner != "" {
125+
// Take a lock on db currentUser to avoid multiple database creation at the same time
126+
// It can fail if they grant the same owner to current at the same time as it's not done in transaction.
127+
lockTxn, err := startTransaction(db.client, "")
128+
if err := pgLockRole(lockTxn, currentUser); err != nil {
129+
return err
130+
}
131+
defer deferredRollback(lockTxn)
132+
125133
// Needed in order to set the owner of the db if the connection user is not a
126134
// superuser
127135
ownerGranted, err := grantRoleMembership(db, owner, currentUser)
@@ -225,6 +233,12 @@ func resourcePostgreSQLDatabaseDelete(db *DBConnection, d *schema.ResourceData)
225233
var dropWithForce string
226234
var err error
227235
if owner != "" {
236+
lockTxn, err := startTransaction(db.client, "")
237+
if err := pgLockRole(lockTxn, currentUser); err != nil {
238+
return err
239+
}
240+
defer deferredRollback(lockTxn)
241+
228242
// Needed in order to set the owner of the db if the connection user is not a
229243
// superuser
230244
ownerGranted, err := grantRoleMembership(db, owner, currentUser)
@@ -433,6 +447,12 @@ func setDBOwner(db *DBConnection, d *schema.ResourceData) error {
433447
}
434448
currentUser := db.client.config.getDatabaseUsername()
435449

450+
lockTxn, err := startTransaction(db.client, "")
451+
if err := pgLockRole(lockTxn, currentUser); err != nil {
452+
return err
453+
}
454+
defer deferredRollback(lockTxn)
455+
436456
//needed in order to set the owner of the db if the connection user is not a superuser
437457
ownerGranted, err := grantRoleMembership(db, owner, currentUser)
438458
if err != nil {

postgresql/resource_postgresql_default_privileges.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ func resourcePostgreSQLDefaultPrivilegesCreate(db *DBConnection, d *schema.Resou
114114
}
115115
defer deferredRollback(txn)
116116

117+
if err := pgLockRole(txn, owner); err != nil {
118+
return err
119+
}
120+
117121
// Needed in order to set the owner of the db if the connection user is not a superuser
118122
if err := withRolesGranted(txn, []string{owner}, func() error {
119123

@@ -156,6 +160,10 @@ func resourcePostgreSQLDefaultPrivilegesDelete(db *DBConnection, d *schema.Resou
156160
}
157161
defer deferredRollback(txn)
158162

163+
if err := pgLockRole(txn, owner); err != nil {
164+
return err
165+
}
166+
159167
// Needed in order to set the owner of the db if the connection user is not a superuser
160168
if err := withRolesGranted(txn, []string{owner}, func() error {
161169
return revokeRoleDefaultPrivileges(txn, d)
@@ -176,6 +184,10 @@ func readRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
176184
pgSchema := d.Get("schema").(string)
177185
objectType := d.Get("object_type").(string)
178186

187+
if err := pgLockRole(txn, owner); err != nil {
188+
return err
189+
}
190+
179191
roleOID, err := getRoleOID(txn, role)
180192
if err != nil {
181193
return err
@@ -208,7 +220,6 @@ func readRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
208220
// and the specified object type (defaclobjtype).
209221

210222
var privileges pq.ByteaArray
211-
212223
if err := txn.QueryRow(
213224
query, queryArgs...,
214225
).Scan(&privileges); err != nil {

postgresql/resource_postgresql_role.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,17 @@ func resourcePostgreSQLRoleCreate(db *DBConnection, d *schema.ResourceData) erro
311311
}
312312

313313
func resourcePostgreSQLRoleDelete(db *DBConnection, d *schema.ResourceData) error {
314+
roleName := d.Get(roleNameAttr).(string)
315+
314316
txn, err := startTransaction(db.client, "")
315317
if err != nil {
316318
return err
317319
}
318320
defer deferredRollback(txn)
319321

320-
roleName := d.Get(roleNameAttr).(string)
322+
if err := pgLockRole(txn, roleName); err != nil {
323+
return err
324+
}
321325

322326
if !d.Get(roleSkipReassignOwnedAttr).(bool) {
323327
if err := withRolesGranted(txn, []string{roleName}, func() error {
@@ -584,6 +588,11 @@ func resourcePostgreSQLRoleUpdate(db *DBConnection, d *schema.ResourceData) erro
584588
}
585589
defer deferredRollback(txn)
586590

591+
oldName, _ := d.GetChange(roleNameAttr)
592+
if err := pgLockRole(txn, oldName.(string)); err != nil {
593+
return err
594+
}
595+
587596
if err := setRoleName(txn, d); err != nil {
588597
return err
589598
}

0 commit comments

Comments
 (0)