1
0
Fork 0
mirror of synced 2024-09-20 03:17:30 +12:00

Implement Runtime Sizes instead of individual memory and cpu

This commit is contained in:
Bradley Schofield 2024-07-16 14:26:53 +09:00
parent 68ea4593ad
commit e3b123d559
14 changed files with 238 additions and 264 deletions

View file

@ -3062,8 +3062,8 @@ $projectCollections = array_merge([
'format' => '',
'size' => 512,
'signed' => false,
'required' => true,
'default' => null,
'required' => false,
'default' => 512,
'filters' => [],
],
[
@ -3071,10 +3071,21 @@ $projectCollections = array_merge([
'$id' => ID::custom('cpus'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 1,
'size' => 2,
'signed' => false,
'required' => true,
'default' => null,
'required' => false,
'default' => 1,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('size'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => 's-1vcpu-512mb',
'filters' => [],
],
],

View file

@ -525,6 +525,12 @@ return [
'code' => 408,
],
Exception::FUNCTION_INVALID_RUNTIME_SIZE => [
'name' => Exception::FUNCTION_INVALID_RUNTIME_SIZE,
'description' => "The requested runtime size is either unsupported or isn't included in your plan. If your running self-hosted check the value of the _APP_FUNCTIONS_MEMORY and _APP_FUNCTIONS_CPUS environment variable.",
'code' => 400,
],
/** Builds */
Exception::BUILD_NOT_FOUND => [
'name' => Exception::BUILD_NOT_FOUND,

20
app/config/sizes.php Normal file
View file

@ -0,0 +1,20 @@
<?php
return [
's-1vcpu-512mb' => [
'memory' => 512,
'cpus' => 1
],
's-1vcpu-1gb' => [
'memory' => 1024,
'cpus' => 1
],
's-2vcpu-2gb' => [
'memory' => 2048,
'cpus' => 2
],
's-4vcpu-8gb' => [
'memory' => 8192,
'cpus' => 4
],
];

View file

@ -10,8 +10,7 @@ use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Cpus;
use Appwrite\Functions\Validator\Memory;
use Appwrite\Functions\Validator\RuntimeSize;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
@ -162,8 +161,7 @@ App::post('/v1/functions')
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function template.', true)
->param('memory', 512, new Memory(), 'Memory in MB allocated for the function.', true)
->param('cpus', 1, new Cpus(), 'CPU cores allocated for the function.', true)
->param('size', 's-1vcpu-512mb', fn (array $plan) => new RuntimeSize($plan), 'Runtime size for the function.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
@ -173,7 +171,7 @@ App::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, int $memory, int $cpus, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, string $size, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@ -206,6 +204,12 @@ App::post('/v1/functions')
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
$spec = Config::getParam('runtime-sizes')[$size] ?? [];
if (empty($spec) || empty($spec['memory']) || empty($spec['cpus'])) {
throw new Exception(Exception::FUNCTION_INVALID_RUNTIME_SIZE);
}
$function = $dbForProject->createDocument('functions', new Document([
'$id' => $functionId,
'execute' => $execute,
@ -233,8 +237,9 @@ App::post('/v1/functions')
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'memory' => $memory,
'cpus' => $cpus
'memory' => $spec['memory'],
'cpus' => $spec['cpus'],
'size' => $size
]));
$schedule = Authorization::skip(
@ -448,23 +453,25 @@ App::get('/v1/functions/runtimes')
]), Response::MODEL_RUNTIME_LIST);
});
App::get('/v1/functions/specs')
App::get('/v1/functions/sizes')
->groups(['api', 'functions'])
->desc('Get available function specs')
->desc('Get available function runtime sizes')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getSpecs')
->label('sdk.description', '/docs/references/functions/get-specs.md')
->label('sdk.method', 'getSizes')
->label('sdk.description', '/docs/references/functions/get-sizes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SPECS)
->label('sdk.response.model', Response::MODEL_SIZES)
->inject('response')
->action(function (Response $response) {
->inject('plan')
->action(function (Response $response, array $plan) {
$runtimeSizes = new RuntimeSize($plan);
$response->dynamic(new Document([
'memory' => Memory::getAllowedValues(),
'cpus' => Cpus::getAllowedValues()
]), Response::MODEL_SPECS);
'sizes' => $runtimeSizes->getAllowedSizes(),
]), Response::MODEL_SIZES);
});
App::get('/v1/functions/:functionId')
@ -724,8 +731,7 @@ App::put('/v1/functions/:functionId')
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('memory', 512, new Memory(), 'Memory in MB allocated for the function.', true)
->param('cpus', 1, new Cpus(), 'CPU cores allocated for the function.', true)
->param('size', 's-1vcpu-512mb', fn (array $plan) => new RuntimeSize($plan), 'Runtime size for the function.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
@ -734,7 +740,7 @@ App::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, int $memory, int $cpus, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $size, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@ -834,9 +840,15 @@ App::put('/v1/functions/:functionId')
$live = false;
}
$spec = Config::getParam('runtime-sizes')[$size] ?? [];
if (empty($spec) || empty($spec['memory']) || empty($spec['cpus'])) {
throw new Exception(Exception::FUNCTION_INVALID_RUNTIME_SIZE);
}
// Enforce Cold Start if spec limits change.
if (($function->getAttribute('cpus') !== $cpus ||
$function->getAttribute('memory') !== $memory) && !empty($function->getAttribute('deployment'))) {
if (($function->getAttribute('cpus') !== $spec['cpus'] ||
$function->getAttribute('memory') !== $spec['memory']) && !empty($function->getAttribute('deployment'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$executor->deleteRuntime($project->getId(), $function->getAttribute('deployment'));
@ -865,8 +877,9 @@ App::put('/v1/functions/:functionId')
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'memory' => $memory,
'cpus' => $cpus,
'memory' => $spec['memory'],
'cpus' => $spec['cpus'],
'size' => $size,
'search' => implode(' ', [$functionId, $name, $runtime]),
])));

View file

@ -291,6 +291,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
Config::load('runtime-sizes', __DIR__ . '/config/sizes.php');
/**
* New DB Filters

View file

@ -154,6 +154,7 @@ class Exception extends \Exception
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
public const FUNCTION_INVALID_RUNTIME_SIZE = 'function_invalid_runtime_size';
/** Deployments */
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';

View file

@ -1,92 +0,0 @@
<?php
namespace Appwrite\Functions\Validator;
use Utopia\System\System;
use Utopia\Validator;
const CPU_VALUES = [1, 2, 4, 8, 16];
class Cpus extends Validator
{
private static function filterBelowThreshold(array $inputArray, int $threshold): array
{
return \array_filter($inputArray, function ($value) use ($threshold) {
return $value <= $threshold;
});
}
/**
* Allowed Values.
*
* Get allowed values taking into account the limits set by the environment variables.
*
* @return array
*/
public static function getAllowedValues(): array
{
return self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_CPUS', 4));
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Integer must be a valid memory value of ' . self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_numeric($value)) {
return false;
}
if (!\in_array($value, self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_CPUS', 4)))) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_INTEGER;
}
}

View file

@ -1,92 +0,0 @@
<?php
namespace Appwrite\Functions\Validator;
use Utopia\System\System;
use Utopia\Validator;
const MEMORY_VALUES = [512, 1024, 2048, 4096, 8192, 16384];
class Memory extends Validator
{
private static function filterBelowThreshold(array $inputArray, int $threshold): array
{
return \array_filter($inputArray, function ($value) use ($threshold) {
return $value <= $threshold;
});
}
/**
* Get Allowed Values.
*
* Get allowed values taking into account the limits set by the environment variables.
*
* @return array
*/
public static function getAllowedValues(): array
{
return self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Integer must be a valid memory value of ' . self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_numeric($value)) {
return false;
}
if (!\in_array($value, self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024)))) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_INTEGER;
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace Appwrite\Functions\Validator;
use Utopia\Config\Config;
use Utopia\System\System;
use Utopia\Validator;
class RuntimeSize extends Validator
{
private array $plan;
public function __construct(array $plan)
{
$this->plan = $plan;
}
/**
* Get Allowed Values.
*
* Get allowed values taking into account the limits set by the environment variables.
*
* @return array
*/
public function getAllowedSizes(): array
{
$sizes = Config::getParam('runtime-sizes', []);
$allowedSizes = [];
foreach ($sizes as $size => $values) {
if ($values['cpus'] <= System::getEnv('_APP_FUNCTIONS_CPUS', 1) && $values['memory'] <= System::getEnv('_APP_FUNCTIONS_MEMORY', 512)) {
if (!empty($this->plan) && key_exists('runtimeSizes', $this->plan)) {
if (!\in_array($size, $this->plan['runtimeSizes'])) {
continue;
}
}
$allowedSizes[] = $size;
}
}
return $allowedSizes;
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'String must be a valid size value of ';
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_string($value)) {
return false;
}
if (!\in_array($value, $this->getAllowedSizes())) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}

View file

@ -82,7 +82,7 @@ use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Specs;
use Appwrite\Utopia\Response\Model\Sizes;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Target;
use Appwrite\Utopia\Response\Model\Team;
@ -248,7 +248,7 @@ class Response extends SwooleResponse
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
public const MODEL_HEADERS = 'headers';
public const MODEL_SPECS = 'specs';
public const MODEL_SIZES = 'sizes';
// Proxy
public const MODEL_PROXY_RULE = 'proxyRule';
@ -442,7 +442,7 @@ class Response extends SwooleResponse
->setModel(new UsageFunction())
->setModel(new UsageProject())
->setModel(new Headers())
->setModel(new Specs())
->setModel(new Sizes())
->setModel(new Rule())
->setModel(new TemplateSMS())
->setModel(new TemplateEmail())

View file

@ -157,6 +157,12 @@ class Func extends Model
'default' => 1,
'example' => 2,
])
->addRule('size', [
'type' => self::TYPE_STRING,
'description' => 'Function execution machine size.',
'default' => 's-1vcpu-512mb',
'example' => 's-1vcpu-512mb',
])
;
}

View file

@ -0,0 +1,42 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Sizes extends Model
{
public function __construct()
{
$this
->addRule('sizes', [
'type' => self::TYPE_STRING,
'description' => 'Different types of runtime machine sizes available.',
'default' => '',
'example' => ['s-1vcpu-512mb', 's-1vcpu-1gb', 's-2vcpu-2gb'],
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Sizes';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SIZES;
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Specs extends Model
{
public function __construct()
{
$this
->addRule('cpus', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of CPU cores available.',
'default' => '',
'example' => [1, 2, 4, 8],
'array' => true,
])
->addRule('memory', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of memory available in MB.',
'default' => '',
'example' => [512, 1024, 2048, 4096, 8192, 16384],
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Specs';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SPECS;
}
}