55package rdsrefresh
66
77import (
8- "context"
98 "fmt"
109 "runtime"
10+ "strconv"
1111 "strings"
1212
13- "github.com/aws/aws-sdk-go-v2/aws"
14- awsconfig "github.com/aws/aws-sdk-go-v2/config"
15- "github.com/aws/aws-sdk-go-v2/service/ec2"
16- ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
17-
1813 "gitlab.com/postgres-ai/database-lab/v3/pkg/log"
1914)
2015
2116const (
22- // rdsInstanceClassPrefix is stripped to derive the EC2 instance type .
17+ // rdsInstanceClassPrefix is stripped to derive the instance size .
2318 rdsInstanceClassPrefix = "db."
2419
2520 // minParallelJobs is the minimum parallelism level.
2621 minParallelJobs = 1
2722)
2823
29- // EC2API defines the interface for EC2 client operations used for vCPU lookup.
30- type EC2API interface {
31- DescribeInstanceTypes (ctx context.Context , params * ec2.DescribeInstanceTypesInput , optFns ... func (* ec2.Options )) (* ec2.DescribeInstanceTypesOutput , error )
24+ // instanceSizeVCPUs maps AWS instance size suffixes to their typical vCPU count.
25+ // this mapping is consistent across most instance families (m5, m6g, r5, r6g, c5, etc.).
26+ // graviton and intel/amd variants of the same size have the same vCPU count.
27+ var instanceSizeVCPUs = map [string ]int {
28+ "micro" : 1 ,
29+ "small" : 1 ,
30+ "medium" : 2 ,
31+ "large" : 2 ,
32+ "xlarge" : 4 ,
33+ "2xlarge" : 8 ,
34+ "3xlarge" : 12 ,
35+ "4xlarge" : 16 ,
36+ "6xlarge" : 24 ,
37+ "8xlarge" : 32 ,
38+ "9xlarge" : 36 ,
39+ "10xlarge" : 40 ,
40+ "12xlarge" : 48 ,
41+ "16xlarge" : 64 ,
42+ "18xlarge" : 72 ,
43+ "24xlarge" : 96 ,
44+ "32xlarge" : 128 ,
45+ "48xlarge" : 192 ,
46+ "metal" : 96 ,
3247}
3348
3449// ParallelismConfig holds the computed parallelism levels for dump and restore.
@@ -40,8 +55,10 @@ type ParallelismConfig struct {
4055// ResolveParallelism determines the optimal parallelism levels for pg_dump and pg_restore.
4156// dump parallelism is based on the vCPU count of the RDS clone instance class.
4257// restore parallelism is based on the vCPU count of the local machine.
43- func ResolveParallelism (ctx context.Context , cfg * Config ) (* ParallelismConfig , error ) {
44- dumpJobs , err := resolveRDSInstanceVCPUs (ctx , cfg )
58+ // local vCPU detection uses runtime.NumCPU(), which works on Linux
59+ // (the target platform for DBLab Engine).
60+ func ResolveParallelism (cfg * Config ) (* ParallelismConfig , error ) {
61+ dumpJobs , err := resolveRDSInstanceVCPUs (cfg .RDSClone .InstanceClass )
4562 if err != nil {
4663 return nil , fmt .Errorf ("failed to resolve RDS instance vCPUs: %w" , err )
4764 }
@@ -56,79 +73,71 @@ func ResolveParallelism(ctx context.Context, cfg *Config) (*ParallelismConfig, e
5673 }, nil
5774}
5875
59- // resolveRDSInstanceVCPUs looks up the vCPU count for the configured RDS instance class
60- // by querying the EC2 DescribeInstanceTypes API.
61- func resolveRDSInstanceVCPUs (ctx context.Context , cfg * Config ) (int , error ) {
62- ec2Client , err := newEC2Client (ctx , cfg )
76+ // resolveRDSInstanceVCPUs estimates the vCPU count for the given RDS instance class
77+ // by parsing the instance size suffix (e.g. "xlarge" from "db.m5.xlarge").
78+ // the mapping covers standard AWS size naming used across RDS instance families.
79+ // if the size is not recognized, it attempts to parse a numeric multiplier prefix
80+ // (e.g. "2xlarge" → 8 vCPUs).
81+ func resolveRDSInstanceVCPUs (instanceClass string ) (int , error ) {
82+ size , err := extractInstanceSize (instanceClass )
6383 if err != nil {
64- return 0 , fmt . Errorf ( "failed to create EC2 client: %w" , err )
84+ return 0 , err
6585 }
6686
67- return lookupInstanceVCPUs (ctx , ec2Client , cfg .RDSClone .InstanceClass )
68- }
69-
70- func newEC2Client (ctx context.Context , cfg * Config ) (EC2API , error ) {
71- awsCfg , err := awsconfig .LoadDefaultConfig (ctx , awsconfig .WithRegion (cfg .AWS .Region ))
72- if err != nil {
73- return nil , fmt .Errorf ("failed to load AWS config: %w" , err )
87+ if vcpus , ok := instanceSizeVCPUs [size ]; ok {
88+ return vcpus , nil
7489 }
7590
76- var opts []func (* ec2.Options )
77- if cfg .AWS .Endpoint != "" {
78- opts = append (opts , func (o * ec2.Options ) {
79- o .BaseEndpoint = aws .String (cfg .AWS .Endpoint )
80- })
91+ // handle unlisted NUMxlarge sizes by parsing the multiplier
92+ vcpus , err := parseXlargeMultiplier (size )
93+ if err != nil {
94+ return 0 , fmt .Errorf ("unknown instance size %q in class %q" , size , instanceClass )
8195 }
8296
83- return ec2 . NewFromConfig ( awsCfg , opts ... ) , nil
97+ return vcpus , nil
8498}
8599
86- // lookupInstanceVCPUs queries EC2 for the vCPU count of the given RDS instance class.
87- func lookupInstanceVCPUs ( ctx context. Context , client EC2API , rdsInstanceClass string ) ( int , error ) {
88- ec2InstanceType , err := rdsClassToEC2Type ( rdsInstanceClass )
89- if err != nil {
90- return 0 , err
100+ // extractInstanceSize extracts the size component from an RDS instance class.
101+ // for example, "db.m5.xlarge" → "xlarge", "db.r6g.2xlarge" → "2xlarge".
102+ func extractInstanceSize ( instanceClass string ) ( string , error ) {
103+ if ! strings . HasPrefix ( instanceClass , rdsInstanceClassPrefix ) {
104+ return "" , fmt . Errorf ( "invalid RDS instance class %q: expected %q prefix" , instanceClass , rdsInstanceClassPrefix )
91105 }
92106
93- result , err := client .DescribeInstanceTypes (ctx , & ec2.DescribeInstanceTypesInput {
94- InstanceTypes : []ec2types.InstanceType {ec2types .InstanceType (ec2InstanceType )},
95- })
96- if err != nil {
97- return 0 , fmt .Errorf ("failed to describe EC2 instance type %q: %w" , ec2InstanceType , err )
98- }
99-
100- if len (result .InstanceTypes ) == 0 {
101- return 0 , fmt .Errorf ("EC2 instance type %q not found" , ec2InstanceType )
102- }
107+ withoutPrefix := strings .TrimPrefix (instanceClass , rdsInstanceClassPrefix )
103108
104- info := result .InstanceTypes [0 ]
105- if info .VCpuInfo == nil || info .VCpuInfo .DefaultVCpus == nil {
106- return 0 , fmt .Errorf ("vCPU info not available for instance type %q" , ec2InstanceType )
107- }
109+ // format is "family.size", e.g. "m5.xlarge" or "r6g.2xlarge"
110+ parts := strings .SplitN (withoutPrefix , "." , 2 )
108111
109- vcpus := int ( * info . VCpuInfo . DefaultVCpus )
110- if vcpus < minParallelJobs {
111- return minParallelJobs , nil
112+ const expectedParts = 2
113+ if len ( parts ) != expectedParts || parts [ 1 ] == "" {
114+ return "" , fmt . Errorf ( "invalid RDS instance class %q: expected format db.<family>.<size>" , instanceClass )
112115 }
113116
114- return vcpus , nil
117+ return parts [ 1 ] , nil
115118}
116119
117- // rdsClassToEC2Type converts an RDS instance class (e.g. "db.m5.xlarge") to an EC2 instance type ("m5.xlarge").
118- func rdsClassToEC2Type (rdsClass string ) (string , error ) {
119- if ! strings .HasPrefix (rdsClass , rdsInstanceClassPrefix ) {
120- return "" , fmt .Errorf ("invalid RDS instance class %q: expected %q prefix" , rdsClass , rdsInstanceClassPrefix )
120+ // parseXlargeMultiplier handles NUMxlarge patterns not in the static map.
121+ // for example, "5xlarge" → 5 * 4 = 20 vCPUs.
122+ func parseXlargeMultiplier (size string ) (int , error ) {
123+ idx := strings .Index (size , "xlarge" )
124+ if idx <= 0 {
125+ return 0 , fmt .Errorf ("not an xlarge variant: %q" , size )
121126 }
122127
123- ec2Type := strings . TrimPrefix ( rdsClass , rdsInstanceClassPrefix )
124- if ec2Type == "" {
125- return "" , fmt .Errorf ("invalid RDS instance class %q: empty after removing prefix " , rdsClass )
128+ multiplier , err := strconv . Atoi ( size [: idx ] )
129+ if err != nil {
130+ return 0 , fmt .Errorf ("invalid multiplier in %q: %w " , size , err )
126131 }
127132
128- return ec2Type , nil
133+ const vcpusPerXlarge = 4
134+
135+ return multiplier * vcpusPerXlarge , nil
129136}
130137
131138// resolveLocalVCPUs returns the number of logical CPUs available on the local machine.
139+ // uses runtime.NumCPU() which reads from /proc/cpuinfo on Linux
140+ // (the target platform for DBLab Engine).
132141func resolveLocalVCPUs () int {
133142 cpus := runtime .NumCPU ()
134143 if cpus < minParallelJobs {
0 commit comments