Files
pgidler/main.go

183 lines
4.4 KiB
Go
Raw Permalink Normal View History

2023-06-23 09:36:08 +02:00
/*
Copyright © 2023 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Commentary:
This little program implements:
https://aws.amazon.com/blogs/database/performance-impact-of-idle-postgresql-connections/
It opens 1k concurrent postgres connections, does a couple of
selects and then stops doing anything further, thus creating
hanging idle sessions.
*/
package main
import (
"context"
"database/sql"
"fmt"
"log"
2025-11-05 19:55:35 +01:00
"os"
2023-06-23 09:36:08 +02:00
"sync"
"time"
_ "github.com/lib/pq"
flag "github.com/spf13/pflag"
)
2025-11-05 19:55:35 +01:00
const version = "v0.0.2"
2023-06-23 09:36:08 +02:00
type Tableschema struct {
Name string
}
const Maxloop int = 100
const Getschema string = "SELECT table_schema||'.'||table_name as relname from information_schema.tables WHERE table_schema='information_schema';"
const Gettable string = "SELECT * FROM information_schema.columns LIMIT 1;"
const StartTransaction string = "BEGIN TRANSACTION"
const HostConnection string = "user=%s dbname=%s password=%s"
const NetConnection string = "user=%s dbname=%s password=%s host=%s port=%d"
var wg sync.WaitGroup
func main() {
var optPasswd string
var optUser string
var optDatabase string
var optServer string
var optPort int
var optMaxconnections int
var optTimeout int
2025-11-05 19:55:35 +01:00
var optIdleTransaction, shversion bool
2023-06-23 09:36:08 +02:00
var ctx context.Context
2025-11-05 19:50:20 +01:00
var cancel context.CancelFunc
2023-06-23 09:36:08 +02:00
var conn string
flag.StringVarP(&optPasswd, "password", "p", "", "Password of the database user")
flag.StringVarP(&optUser, "user", "u", "postgres", "Database user")
flag.StringVarP(&optDatabase, "database", "d", "postgres", "Database")
flag.StringVarP(&optServer, "server", "s", "localhost", "Server")
flag.IntVarP(&optMaxconnections, "client", "c", 500, "Number of concurrent users")
flag.IntVarP(&optPort, "port", "P", 5432, "TCP Port")
2025-11-05 19:55:35 +01:00
flag.IntVarP(&optTimeout, "timeout", "t", 0, "Whether to stop the clients after N seconds")
2023-06-23 09:36:08 +02:00
flag.BoolVarP(&optIdleTransaction, "idletransaction", "i", false, "Wether to stay in idle in transaction state")
2025-11-05 19:55:35 +01:00
flag.BoolVarP(&shversion, "version", "v", false, "show version")
2023-06-23 09:36:08 +02:00
flag.Parse()
2025-11-05 19:55:35 +01:00
if shversion {
fmt.Printf("pgidler version %s\n", version)
os.Exit(0)
}
2023-06-23 09:36:08 +02:00
if optServer != "" {
conn = fmt.Sprintf(NetConnection, optUser, optDatabase, optPasswd, optServer, optPort)
} else {
conn = fmt.Sprintf(HostConnection, optUser, optDatabase, optPasswd)
}
// first do a connection test
db, err := sql.Open("postgres", conn)
if err != nil {
log.Fatal(err)
}
2025-11-05 19:50:20 +01:00
if err := db.Close(); err != nil {
log.Fatal(err)
}
2023-06-23 09:36:08 +02:00
log.Printf("DB Connection works, firing up %d clients\n", optMaxconnections)
if optTimeout > 0 {
2025-11-05 19:50:20 +01:00
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(optTimeout)*time.Second)
2023-06-23 09:36:08 +02:00
log.Printf("Clients will be killed after %d seconds", optTimeout)
} else {
ctx = context.TODO()
log.Println("Clients will run endlessly, abort with C-c")
}
wg.Add(optMaxconnections)
for m := 0; m < optMaxconnections; m++ {
go dbClient(ctx, conn, optIdleTransaction)
}
wg.Wait()
2025-11-05 19:50:20 +01:00
if cancel != nil {
cancel()
}
2023-06-23 09:36:08 +02:00
}
func dbClient(ctx context.Context, conn string, idle bool) {
defer wg.Done()
db, err := sql.Open("postgres", conn)
if err != nil {
log.Fatal(err)
}
if idle {
_, err := db.Exec(StartTransaction)
if err != nil {
log.Fatal(err)
}
}
rows, err := db.Query(Getschema)
if err != nil {
log.Fatal(err)
}
2025-11-05 19:50:20 +01:00
defer func() {
if err := rows.Close(); err != nil {
log.Fatal(err)
}
}()
2023-06-23 09:36:08 +02:00
//log.Println("Got rows")
for rows.Next() {
var T Tableschema
if err := rows.Scan(&T.Name); err != nil {
log.Fatal(err)
}
2025-11-05 19:50:20 +01:00
2023-06-23 09:36:08 +02:00
//log.Printf("Got table %s\n", T.Name)
for i := 0; i < Maxloop; i++ {
rows, err := db.Query(Gettable)
if err != nil {
log.Fatal(err)
}
2025-11-05 19:50:20 +01:00
// ignore result
if err := rows.Close(); err != nil {
log.Fatal(err)
}
2023-06-23 09:36:08 +02:00
}
}
//log.Println("Got tables")
// block this thread forever or timeout
//select {}
select {
case <-time.After(1 * time.Second):
case <-ctx.Done():
}
}