6
6
"fmt"
7
7
"net"
8
8
"strings"
9
+ "time"
9
10
10
11
"github.com/go-logr/logr"
11
12
"github.com/go-sql-driver/mysql"
@@ -16,6 +17,16 @@ import (
16
17
kedautil "github.com/kedacore/keda/v2/pkg/util"
17
18
)
18
19
20
+ var (
21
+ // A map that holds MySQL connection pools, keyed by connection string
22
+ connectionPools * kedautil.RefMap [string , * sql.DB ]
23
+ )
24
+
25
+ func init () {
26
+ // Initialize the global connectionPools map
27
+ connectionPools = kedautil .NewRefMap [string , * sql.DB ]()
28
+ }
29
+
19
30
type mySQLScaler struct {
20
31
metricType v2.MetricTargetType
21
32
metadata * mySQLMetadata
@@ -34,6 +45,12 @@ type mySQLMetadata struct {
34
45
QueryValue float64 `keda:"name=queryValue, order=triggerMetadata"`
35
46
ActivationQueryValue float64 `keda:"name=activationQueryValue, order=triggerMetadata, default=0"`
36
47
MetricName string `keda:"name=metricName, order=triggerMetadata, optional"`
48
+
49
+ // Connection pool settings
50
+ UseGlobalConnPools bool `keda:"name=useGlobalConnPools, order=triggerMetadata, optional"`
51
+ MaxOpenConns int `keda:"name=maxOpenConns, order=triggerMetadata, optional"`
52
+ MaxIdleConns int `keda:"name=maxIdleConns, order=triggerMetadata, optional"`
53
+ ConnMaxIdleTime int `keda:"name=connMaxIdleTime, order=triggerMetadata, optional"` // seconds
37
54
}
38
55
39
56
// NewMySQLScaler creates a new MySQL scaler
@@ -50,10 +67,19 @@ func NewMySQLScaler(config *scalersconfig.ScalerConfig) (Scaler, error) {
50
67
return nil , fmt .Errorf ("error parsing MySQL metadata: %w" , err )
51
68
}
52
69
53
- conn , err := newMySQLConnection (meta , logger )
70
+ // Create MySQL connection, if useGlobalConnPools is set to true, it will use
71
+ // the global connection pool for the given connection string, otherwise it
72
+ // will create a new local connection pool for the given connection string
73
+ var conn * sql.DB
74
+ if meta .UseGlobalConnPools {
75
+ conn , err = getConnectionPool (meta , logger )
76
+ } else {
77
+ conn , err = newMySQLConnection (meta , logger )
78
+ }
54
79
if err != nil {
55
- return nil , fmt .Errorf ("error establishing MySQL connection: %w" , err )
80
+ return nil , fmt .Errorf ("error creating MySQL connection: %w" , err )
56
81
}
82
+
57
83
return & mySQLScaler {
58
84
metricType : metricType ,
59
85
metadata : meta ,
@@ -96,6 +122,40 @@ func metadataToConnectionStr(meta *mySQLMetadata) string {
96
122
return connStr
97
123
}
98
124
125
+ // getConnectionPool will check if the connection pool has already been
126
+ // created for the given connection string and return it. If it has not
127
+ // been created, it will create a new connection pool and store it in the
128
+ // connectionPools map.
129
+ func getConnectionPool (meta * mySQLMetadata , logger logr.Logger ) (* sql.DB , error ) {
130
+ connStr := metadataToConnectionStr (meta )
131
+ // Try to load an existing pool and increment its reference count if found
132
+ if pool , ok := connectionPools .Load (connStr ); ok {
133
+ err := connectionPools .AddRef (connStr )
134
+ if err != nil {
135
+ logger .Error (err , "Error increasing connection pool reference count" )
136
+ return nil , err
137
+ }
138
+
139
+ return pool , nil
140
+ }
141
+
142
+ // If pool does not exist, create a new one and store it in RefMap
143
+ newPool , err := newMySQLConnection (meta , logger )
144
+ if err != nil {
145
+ return nil , err
146
+ }
147
+ err = connectionPools .Store (connStr , newPool , func (db * sql.DB ) error {
148
+ logger .Info ("Closing MySQL connection pool" , "connectionString" , connStr )
149
+ return db .Close ()
150
+ })
151
+ if err != nil {
152
+ logger .Error (err , "Error storing connection pool in RefMap" )
153
+ return nil , err
154
+ }
155
+
156
+ return newPool , nil
157
+ }
158
+
99
159
// newMySQLConnection creates MySQL db connection
100
160
func newMySQLConnection (meta * mySQLMetadata , logger logr.Logger ) (* sql.DB , error ) {
101
161
connStr := metadataToConnectionStr (meta )
@@ -104,14 +164,35 @@ func newMySQLConnection(meta *mySQLMetadata, logger logr.Logger) (*sql.DB, error
104
164
logger .Error (err , fmt .Sprintf ("Found error when opening connection: %s" , err ))
105
165
return nil , err
106
166
}
167
+
107
168
err = db .Ping ()
108
169
if err != nil {
109
170
logger .Error (err , fmt .Sprintf ("Found error when pinging database: %s" , err ))
110
171
return nil , err
111
172
}
173
+
174
+ setConnectionPoolConfiguration (meta , db )
175
+
112
176
return db , nil
113
177
}
114
178
179
+ // setConnectionPoolConfiguration configures the MySQL connection pool settings
180
+ // based on the parameters provided in mySQLMetadata. If a setting is zero, it
181
+ // is left at its default value.
182
+ func setConnectionPoolConfiguration (meta * mySQLMetadata , db * sql.DB ) {
183
+ if meta .MaxOpenConns > 0 {
184
+ db .SetMaxOpenConns (meta .MaxOpenConns )
185
+ }
186
+
187
+ if meta .MaxIdleConns > 0 {
188
+ db .SetMaxIdleConns (meta .MaxIdleConns )
189
+ }
190
+
191
+ if meta .ConnMaxIdleTime > 0 {
192
+ db .SetConnMaxIdleTime (time .Duration (meta .ConnMaxIdleTime ) * time .Second )
193
+ }
194
+ }
195
+
115
196
// parseMySQLDbNameFromConnectionStr returns dbname from connection string
116
197
// in it is not able to parse it, it returns "dbname" string
117
198
func parseMySQLDbNameFromConnectionStr (connectionString string ) string {
@@ -123,13 +204,30 @@ func parseMySQLDbNameFromConnectionStr(connectionString string) string {
123
204
return "dbname"
124
205
}
125
206
126
- // Close disposes of MySQL connections
127
- func (s * mySQLScaler ) Close (context.Context ) error {
128
- err := s .connection .Close ()
129
- if err != nil {
130
- s .logger .Error (err , "Error closing MySQL connection" )
207
+ // Close disposes of MySQL connections, closing either the global pool if used
208
+ // or the local connection pool
209
+ func (s * mySQLScaler ) Close (ctx context.Context ) error {
210
+ if s .metadata .UseGlobalConnPools {
211
+ if err := s .closeGlobalPool (ctx ); err != nil {
212
+ return fmt .Errorf ("error closing MySQL connection: %w" , err )
213
+ }
214
+ } else {
215
+ if err := s .connection .Close (); err != nil {
216
+ return fmt .Errorf ("error closing MySQL connection: %w" , err )
217
+ }
218
+ }
219
+
220
+ return nil
221
+ }
222
+
223
+ // closeGlobalPool closes all MySQL connections in the global pool
224
+ func (s * mySQLScaler ) closeGlobalPool (_ context.Context ) error {
225
+ connStr := metadataToConnectionStr (s .metadata )
226
+ if err := connectionPools .RemoveRef (connStr ); err != nil {
227
+ s .logger .Error (err , "Error decreasing connection pool reference count" )
131
228
return err
132
229
}
230
+
133
231
return nil
134
232
}
135
233
0 commit comments