diff --git a/api/beta.yaml b/api/beta.yaml index 257a46e84..72129247a 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -2012,6 +2012,11 @@ paths: in: query schema: type: string + - name: resource_multiplier + required: false + in: query + schema: + type: string requestBody: required: true content: @@ -2153,6 +2158,11 @@ paths: in: query schema: type: string + - name: resource_multiplier + required: false + in: query + schema: + type: string requestBody: required: true content: @@ -5090,6 +5100,8 @@ components: type: string verify_jwt: type: boolean + resource_multiplier: + type: string required: - slug - name @@ -5125,6 +5137,8 @@ components: type: string import_map_path: type: string + resource_multiplier: + type: string required: - version - created_at @@ -5164,6 +5178,8 @@ components: type: string import_map_path: type: string + resource_multiplier: + type: string required: - version - created_at diff --git a/internal/functions/serve/templates/main.ts b/internal/functions/serve/templates/main.ts index 154cd1a11..7eb6b4da4 100644 --- a/internal/functions/serve/templates/main.ts +++ b/internal/functions/serve/templates/main.ts @@ -149,7 +149,8 @@ Deno.serve({ console.error(`serving the request with ${servicePath}`); // Ref: https://supabase.com/docs/guides/functions/limits - const memoryLimitMb = 256; + const resourceMultiplier = Math.max(Math.min(parseFloat(functionsConfig[functionName].resourceMultiplier ?? '1'), 4), 1) + const memoryLimitMb = 256 * resourceMultiplier; const workerTimeoutMs = isFinite(WALLCLOCK_LIMIT_SEC) ? WALLCLOCK_LIMIT_SEC * 1000 : 400 * 1000; const noModuleCache = false; const envVarsObj = Deno.env.toObject(); @@ -161,7 +162,7 @@ Deno.serve({ const forceCreate = false; const customModuleRoot = ""; // empty string to allow any local path const cpuTimeSoftLimitMs = 1000; - const cpuTimeHardLimitMs = 2000; + const cpuTimeHardLimitMs = 2000 * resourceMultiplier; // NOTE(Nyannyacha): Decorator type has been set to tc39 by Lakshan's request, // but in my opinion, we should probably expose this to customers at some @@ -187,7 +188,9 @@ Deno.serve({ maybeEntrypoint }); - return await worker.fetch(req); + const res = await worker.fetch(req); + res.headers.set('x-sb-resource-multiplier', resourceMultiplier) + return res } catch (e) { console.error(e); diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 05ed76708..ef918f778 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -3921,6 +3921,22 @@ func NewV1CreateAFunctionRequestWithBody(server string, ref string, params *V1Cr } + if params.ResourceMultiplier != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "resource_multiplier", runtime.ParamLocationQuery, *params.ResourceMultiplier); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } @@ -4159,6 +4175,22 @@ func NewV1UpdateAFunctionRequestWithBody(server string, ref string, functionSlug } + if params.ResourceMultiplier != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "resource_multiplier", runtime.ParamLocationQuery, *params.ResourceMultiplier); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + queryURL.RawQuery = queryValues.Encode() } diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 9334b80b8..ad445cac5 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -824,17 +824,18 @@ type Domain struct { // FunctionResponse defines model for FunctionResponse. type FunctionResponse struct { - CreatedAt int64 `json:"created_at"` - EntrypointPath *string `json:"entrypoint_path,omitempty"` - Id string `json:"id"` - ImportMap *bool `json:"import_map,omitempty"` - ImportMapPath *string `json:"import_map_path,omitempty"` - Name string `json:"name"` - Slug string `json:"slug"` - Status FunctionResponseStatus `json:"status"` - UpdatedAt int64 `json:"updated_at"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version int `json:"version"` + CreatedAt int64 `json:"created_at"` + EntrypointPath *string `json:"entrypoint_path,omitempty"` + Id string `json:"id"` + ImportMap *bool `json:"import_map,omitempty"` + ImportMapPath *string `json:"import_map_path,omitempty"` + Name string `json:"name"` + ResourceMultiplier *string `json:"resource_multiplier,omitempty"` + Slug string `json:"slug"` + Status FunctionResponseStatus `json:"status"` + UpdatedAt int64 `json:"updated_at"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` + Version int `json:"version"` } // FunctionResponseStatus defines model for FunctionResponse.Status. @@ -842,17 +843,18 @@ type FunctionResponseStatus string // FunctionSlugResponse defines model for FunctionSlugResponse. type FunctionSlugResponse struct { - CreatedAt int64 `json:"created_at"` - EntrypointPath *string `json:"entrypoint_path,omitempty"` - Id string `json:"id"` - ImportMap *bool `json:"import_map,omitempty"` - ImportMapPath *string `json:"import_map_path,omitempty"` - Name string `json:"name"` - Slug string `json:"slug"` - Status FunctionSlugResponseStatus `json:"status"` - UpdatedAt int64 `json:"updated_at"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version int `json:"version"` + CreatedAt int64 `json:"created_at"` + EntrypointPath *string `json:"entrypoint_path,omitempty"` + Id string `json:"id"` + ImportMap *bool `json:"import_map,omitempty"` + ImportMapPath *string `json:"import_map_path,omitempty"` + Name string `json:"name"` + ResourceMultiplier *string `json:"resource_multiplier,omitempty"` + Slug string `json:"slug"` + Status FunctionSlugResponseStatus `json:"status"` + UpdatedAt int64 `json:"updated_at"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` + Version int `json:"version"` } // FunctionSlugResponseStatus defines model for FunctionSlugResponse.Status. @@ -1539,10 +1541,11 @@ type V1BackupsResponse struct { // V1CreateFunctionBody defines model for V1CreateFunctionBody. type V1CreateFunctionBody struct { - Body string `json:"body"` - Name string `json:"name"` - Slug string `json:"slug"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` + Body string `json:"body"` + Name string `json:"name"` + ResourceMultiplier *string `json:"resource_multiplier,omitempty"` + Slug string `json:"slug"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` } // V1CreateProjectBody defines model for V1CreateProjectBody. @@ -1768,22 +1771,24 @@ type V1AuthorizeUserParamsCodeChallengeMethod string // V1CreateAFunctionParams defines parameters for V1CreateAFunction. type V1CreateAFunctionParams struct { - Slug *string `form:"slug,omitempty" json:"slug,omitempty"` - Name *string `form:"name,omitempty" json:"name,omitempty"` - VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` - ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` - EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` - ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + Slug *string `form:"slug,omitempty" json:"slug,omitempty"` + Name *string `form:"name,omitempty" json:"name,omitempty"` + VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` + ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` + EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` + ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + ResourceMultiplier *string `form:"resource_multiplier,omitempty" json:"resource_multiplier,omitempty"` } // V1UpdateAFunctionParams defines parameters for V1UpdateAFunction. type V1UpdateAFunctionParams struct { - Slug *string `form:"slug,omitempty" json:"slug,omitempty"` - Name *string `form:"name,omitempty" json:"name,omitempty"` - VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` - ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` - EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` - ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + Slug *string `form:"slug,omitempty" json:"slug,omitempty"` + Name *string `form:"name,omitempty" json:"name,omitempty"` + VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` + ImportMap *bool `form:"import_map,omitempty" json:"import_map,omitempty"` + EntrypointPath *string `form:"entrypoint_path,omitempty" json:"entrypoint_path,omitempty"` + ImportMapPath *string `form:"import_map_path,omitempty" json:"import_map_path,omitempty"` + ResourceMultiplier *string `form:"resource_multiplier,omitempty" json:"resource_multiplier,omitempty"` } // V1GetServicesHealthParams defines parameters for V1GetServicesHealth. diff --git a/pkg/config/config.go b/pkg/config/config.go index b1e7ed570..16157884b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -176,10 +176,11 @@ type ( FunctionConfig map[string]function function struct { - Enabled *bool `toml:"enabled" json:"-"` - VerifyJWT *bool `toml:"verify_jwt" json:"verifyJWT"` - ImportMap string `toml:"import_map" json:"importMapPath,omitempty"` - Entrypoint string `toml:"entrypoint" json:"entrypointPath,omitempty"` + Enabled *bool `toml:"enabled" json:"-"` + VerifyJWT *bool `toml:"verify_jwt" json:"verifyJWT"` + ImportMap string `toml:"import_map" json:"importMapPath,omitempty"` + Entrypoint string `toml:"entrypoint" json:"entrypointPath,omitempty"` + ResourceMultiplier *string `toml:"resource_multiplier" json:"resourceMultiplier,omitempty"` } analytics struct { diff --git a/pkg/function/batch.go b/pkg/function/batch.go index 43f837fc4..f4b5b13b2 100644 --- a/pkg/function/batch.go +++ b/pkg/function/batch.go @@ -44,6 +44,7 @@ func (s *EdgeRuntimeAPI) UpsertFunctions(ctx context.Context, functionConfig con continue } } + resourceMultiplier := function.ResourceMultiplier var body bytes.Buffer if err := s.eszip.Bundle(ctx, function.Entrypoint, function.ImportMap, &body); err != nil { return err @@ -52,9 +53,10 @@ func (s *EdgeRuntimeAPI) UpsertFunctions(ctx context.Context, functionConfig con upsert := func() error { if _, ok := exists[slug]; ok { if resp, err := s.client.V1UpdateAFunctionWithBodyWithResponse(ctx, s.project, slug, &api.V1UpdateAFunctionParams{ - VerifyJwt: function.VerifyJWT, - ImportMapPath: toFileURL(function.ImportMap), - EntrypointPath: toFileURL(function.Entrypoint), + VerifyJwt: function.VerifyJWT, + ImportMapPath: toFileURL(function.ImportMap), + EntrypointPath: toFileURL(function.Entrypoint), + ResourceMultiplier: resourceMultiplier, }, eszipContentType, bytes.NewReader(body.Bytes())); err != nil { return errors.Errorf("failed to update function: %w", err) } else if resp.JSON200 == nil { @@ -62,11 +64,12 @@ func (s *EdgeRuntimeAPI) UpsertFunctions(ctx context.Context, functionConfig con } } else { if resp, err := s.client.V1CreateAFunctionWithBodyWithResponse(ctx, s.project, &api.V1CreateAFunctionParams{ - Slug: &slug, - Name: &slug, - VerifyJwt: function.VerifyJWT, - ImportMapPath: toFileURL(function.ImportMap), - EntrypointPath: toFileURL(function.Entrypoint), + Slug: &slug, + Name: &slug, + VerifyJwt: function.VerifyJWT, + ImportMapPath: toFileURL(function.ImportMap), + EntrypointPath: toFileURL(function.Entrypoint), + ResourceMultiplier: resourceMultiplier, }, eszipContentType, bytes.NewReader(body.Bytes())); err != nil { return errors.Errorf("failed to create function: %w", err) } else if resp.JSON201 == nil {