mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5924a7bd02 | ||
|
|
8fee018c18 | ||
|
|
1592135fab | ||
|
|
b21937ca3f | ||
|
|
f0380ef4f2 | ||
|
|
6fb5fb5676 | ||
|
|
8e6961a4dd | ||
|
|
1e968bda14 | ||
|
|
0201509c71 | ||
|
|
7a51b0e937 | ||
|
|
7701904376 | ||
|
|
7de0897335 | ||
|
|
a46e12f9ff | ||
|
|
3f0fcb40d0 | ||
|
|
0abfae8ca1 | ||
|
|
27249c236b | ||
|
|
9ac55e8806 | ||
|
|
848b997a5f | ||
|
|
da5addf2f2 | ||
|
|
7bd16180c3 | ||
|
|
e291d81be3 | ||
|
|
6842286a3a | ||
|
|
545d86d994 | ||
|
|
7708859d7d | ||
|
|
601569805f | ||
|
|
d7d98d9866 | ||
|
|
b7d7f43aef | ||
|
|
b0fe5f3e90 | ||
|
|
209cf6be1e | ||
|
|
1f46d390b1 | ||
|
|
59c02ddf84 | ||
|
|
1e2e82593f | ||
|
|
7e2ae34624 | ||
|
|
d70dd6a6bc | ||
|
|
e30952c48b | ||
|
|
3b53fc7cc4 | ||
|
|
7cfdde3d91 | ||
|
|
45e8314b49 | ||
|
|
df8e95a0a1 | ||
|
|
70f1a3eaf8 | ||
|
|
462b30b721 | ||
|
|
70102a18f7 | ||
|
|
ae57ac6781 | ||
|
|
108745e1a8 | ||
|
|
9ccb26d090 | ||
|
|
e25dd11049 | ||
|
|
c6a054f88d | ||
|
|
4a8befd110 |
@@ -70,6 +70,6 @@ _APP_USAGE_STATS=enabled
|
||||
_APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_REGION=default
|
||||
_APP_REGION=nyc1
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
|
||||
+6
-1
@@ -306,6 +306,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/patch-create-missing-schedules && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/volume-sync && \
|
||||
chmod +x /usr/local/bin/edge-sync && \
|
||||
chmod +x /usr/local/bin/usage && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
@@ -324,7 +325,11 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/worker-webhooks
|
||||
chmod +x /usr/local/bin/worker-webhooks && \
|
||||
chmod +x /usr/local/bin/worker-sync-out && \
|
||||
chmod +x /usr/local/bin/worker-sync-in
|
||||
|
||||
|
||||
|
||||
# Letsencrypt Permissions
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
@@ -17,6 +17,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
Authorization::disable();
|
||||
@@ -144,6 +145,10 @@ CLI::setResource('queueForFunctions', function (Group $pools) {
|
||||
return new Func($pools->get('queue')->pop()->getResource());
|
||||
}, ['pools']);
|
||||
|
||||
CLI::setResource('queueForCacheSyncOut', function (Group $pools) {
|
||||
return new Client('v1-sync-out', $pools->get('queue')->pop()->getResource());
|
||||
}, ['pools']);
|
||||
|
||||
CLI::setResource('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
@@ -18,6 +18,71 @@ $auth = Config::getParam('auth', []);
|
||||
*/
|
||||
|
||||
$collections = [
|
||||
'syncs' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('syncs'),
|
||||
'name' => 'Syncs',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('region'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 50,
|
||||
'required' => true,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('target'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'size' => 50,
|
||||
'required' => true,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('keys'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
'signed' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('payload'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
'databases' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('databases'),
|
||||
|
||||
+21
-2
@@ -2,8 +2,27 @@
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'name' => 'Default',
|
||||
'name' => 'default',
|
||||
'default' => true,
|
||||
'disabled' => false,
|
||||
]
|
||||
'domain' => '',
|
||||
],
|
||||
'nyc1' => [
|
||||
'name' => 'New york',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
'spg1' => [
|
||||
'name' => 'Singapore',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
'fra1' => [
|
||||
'name' => 'Frankfurt',
|
||||
'default' => false,
|
||||
'disabled' => false,
|
||||
'domain' => '172.17.0.1',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -199,4 +199,17 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'syncs' => [
|
||||
'key' => 'edge',
|
||||
'name' => 'edge',
|
||||
'subtitle' => 'Appwrite\'s cache edge sync Endpoint',
|
||||
'description' => 'Cache edge sync Endpoint',
|
||||
'controller' => 'api/edge.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
];
|
||||
|
||||
+1
-1
Submodule app/console updated: 43891a526e...b1a81a390a
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::init()
|
||||
->groups(['edge'])
|
||||
->inject('request')
|
||||
->action(function (Request $request) {
|
||||
|
||||
$token = $request->getHeader('authorization');
|
||||
$token = str_replace(["Bearer"," "], "", $token);
|
||||
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 600, 10);
|
||||
try {
|
||||
$payload = $jwt->decode($token);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
App::post('/v1/edge/sync')
|
||||
->desc('Purge cache keys')
|
||||
->groups(['edge'])
|
||||
->label('scope', 'public')
|
||||
->param('keys', '', new ArrayList(new Text(100), 1000), 'Cache keys. an array containing alphanumerical cache keys')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('queueForCacheSyncIn')
|
||||
->action(function (array $keys, Request $request, Response $response, Client $queueForCacheSyncIn) {
|
||||
|
||||
if (empty($keys)) {
|
||||
throw new Exception(Exception::KEY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queueForCacheSyncIn
|
||||
->enqueue([
|
||||
'keys' => $keys
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys
|
||||
]), Response::MODEL_EDGE_SYNC);
|
||||
});
|
||||
@@ -41,6 +41,7 @@ Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
App::init()
|
||||
->groups(['api', 'auth', 'avatars', 'functions', 'account', 'projects', 'storage', 'teams', 'users'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
||||
+27
-8
@@ -39,6 +39,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Utopia\App;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\ID;
|
||||
@@ -854,12 +855,15 @@ App::setResource('mails', fn() => new Mail());
|
||||
App::setResource('deletes', fn() => new Delete());
|
||||
App::setResource('database', fn() => new EventDatabase());
|
||||
App::setResource('messaging', fn() => new Phone());
|
||||
App::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
App::setResource('queueForFunctions', function (Group $pools) {
|
||||
return new Func($pools->get('queue')->pop()->getResource());
|
||||
}, ['pools']);
|
||||
App::setResource('queueForCacheSyncOut', function (Group $pools) {
|
||||
return new Client('v1-sync-out', $pools->get('queue')->pop()->getResource());
|
||||
}, ['pools']);
|
||||
App::setResource('queueForCacheSyncIn', function (Group $pools) {
|
||||
return new Client('v1-sync-in', $pools->get('queue')->pop()->getResource());
|
||||
}, ['pools']);
|
||||
App::setResource('queueForFunctions', function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('usage', function ($register) {
|
||||
return new Stats($register->get('statsd'));
|
||||
}, ['register']);
|
||||
@@ -1074,7 +1078,7 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
App::setResource('cache', function (Group $pools) {
|
||||
App::setResource('cache', function (Group $pools, Client $queueForCacheSyncOut) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
@@ -1085,9 +1089,24 @@ App::setResource('cache', function (Group $pools) {
|
||||
->getResource()
|
||||
;
|
||||
}
|
||||
$cache = new Cache(new Sharding($adapters));
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['pools']);
|
||||
$cache->on(cache::EVENT_SAVE, function ($key) use ($queueForCacheSyncOut) {
|
||||
$queueForCacheSyncOut
|
||||
->enqueue([
|
||||
'key' => $key
|
||||
]);
|
||||
});
|
||||
|
||||
$cache->on(cache::EVENT_PURGE, function ($key) use ($queueForCacheSyncOut) {
|
||||
$queueForCacheSyncOut
|
||||
->enqueue([
|
||||
'key' => $key
|
||||
]);
|
||||
});
|
||||
|
||||
return $cache;
|
||||
}, ['pools', 'queueForCacheSyncOut']);
|
||||
|
||||
App::setResource('deviceLocal', function () {
|
||||
return new Local();
|
||||
|
||||
@@ -195,6 +195,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
*/
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument, $logError) {
|
||||
$payload = [];
|
||||
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ if (empty(App::getEnv('QUEUE'))) {
|
||||
throw new Exception('Please configure "QUEUE" environemnt variable.');
|
||||
}
|
||||
|
||||
|
||||
$workerNumber =1;
|
||||
$adapter = new Swoole($connection, $workerNumber, App::getEnv('QUEUE'));
|
||||
$server = new Server($adapter);
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../worker.php';
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Queue\Message;
|
||||
|
||||
$server->job()
|
||||
->inject('message')
|
||||
->inject('cache')
|
||||
->action(function (Message $message, Cache $cache) {
|
||||
$time = DateTime::now();
|
||||
|
||||
$cache->setListenersStatus(false);
|
||||
|
||||
foreach ($message->getPayload()['keys'] ?? [] as $key) {
|
||||
Console::log("[{$time}] Purging {$key}");
|
||||
$cache->purge($key);
|
||||
}
|
||||
|
||||
$cache->setListenersStatus(true);
|
||||
});
|
||||
|
||||
$server
|
||||
->workerStart()
|
||||
->action(function () {
|
||||
Console::success("In [" . App::getEnv('_APP_REGION', 'nyc1') . "] edge cache purging worker Started");
|
||||
});
|
||||
|
||||
$server->start();
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../worker.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Timer;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Queue\Message;
|
||||
|
||||
$regions = array_filter(
|
||||
Config::getParam('regions', []),
|
||||
fn ($region) => App::getEnv('_APP_REGION') !== $region
|
||||
&& $region !== 'default',
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$stack = [
|
||||
'regions' => $regions,
|
||||
'keys' => [],
|
||||
];
|
||||
$failures = [];
|
||||
|
||||
const CHUNK_MAX_KEYS = 500;
|
||||
const MAX_CURL_SEND_ATTEMPTS = 4;
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $token
|
||||
* @param array $stack
|
||||
* @return array
|
||||
*/
|
||||
function call(string $url, string $token, array $stack): array
|
||||
{
|
||||
$payload = [];
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($stack));
|
||||
|
||||
for ($attempts = 0; $attempts < MAX_CURL_SEND_ATTEMPTS; $attempts++) {
|
||||
$response = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$payload = [
|
||||
'status' => $status,
|
||||
'payload' => json_decode($response, true)
|
||||
];
|
||||
|
||||
if ($status === 200) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws Exception|\Exception
|
||||
*/
|
||||
function handle($dbForConsole, $regions, $stack): void
|
||||
{
|
||||
|
||||
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 600, 10);
|
||||
$token = $jwt->encode([]);
|
||||
|
||||
foreach ($regions as $code => $region) {
|
||||
$time = DateTime::now();
|
||||
$response = call($region['domain'] . '/v1/edge/sync', $token, ['keys' => $stack]);
|
||||
if ($response['status'] !== Response::STATUS_CODE_OK) {
|
||||
Console::error("[{$time}] Request to {$code} has failed");
|
||||
|
||||
$dbForConsole->createDocument('sync', new Document([
|
||||
'region' => App::getEnv('_APP_REGION'),
|
||||
'target' => $code,
|
||||
'keys' => $stack,
|
||||
'status' => $response['status'],
|
||||
'payload' => $response['payload'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server->job()
|
||||
->inject('message')
|
||||
->action(function (Message $message) use (&$stack, &$failures) {
|
||||
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (!empty($payload['keys'])) {
|
||||
$regions = array_filter(
|
||||
Config::getParam('regions', []),
|
||||
fn ($region) => $payload['region'] === $region,
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
$failures[] = [
|
||||
'regions' => $regions,
|
||||
'keys' => $payload['keys']
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($payload['key'])) {
|
||||
if (!in_array($payload['key'], $stack['keys'] ?? [])) {
|
||||
$stack['keys'][] = $payload['key'];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$server
|
||||
->workerStart()
|
||||
->inject('dbForConsole')
|
||||
->action(function ($dbForConsole) use (&$stack, &$failures) {
|
||||
|
||||
Timer::tick(5000, function () use ($dbForConsole, &$stack, &$failures) {
|
||||
$time = DateTime::now();
|
||||
|
||||
if (empty($stack['keys']) && count($failures) === 0) {
|
||||
Console::info("[{$time}] Stack is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($failures) > 0) {
|
||||
$i = 0;
|
||||
while ($i < count($failures)) {
|
||||
$failure = array_shift($failures);
|
||||
Console::info("[{$time}] ReSending " . count($failure['keys']) . " to " . key($failure['regions']));
|
||||
handle($dbForConsole, $failure['regions'], $failure['keys']);
|
||||
$i++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$chunk = array_slice($stack['keys'], 0, CHUNK_MAX_KEYS);
|
||||
array_splice($stack['keys'], 0, CHUNK_MAX_KEYS);
|
||||
Console::log("[{$time}] Sending " . count($chunk) . " remains " . count($stack['keys']));
|
||||
handle($dbForConsole, $stack['regions'], $chunk);
|
||||
$chunk = [];
|
||||
});
|
||||
Console::success("Out [" . App::getEnv('_APP_REGION') . "] edge cache purging worker Started");
|
||||
});
|
||||
|
||||
$server->start();
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
/usr/src/code/vendor/bin/phpbench run --config /usr/src/code/phpbench.json --report appwrite $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php edge-sync $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
QUEUE=v1-sync-in php /usr/src/code/app/workers/sync-in.php $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
QUEUE=v1-sync-out php /usr/src/code/app/workers/sync-out.php $@
|
||||
+24
-3
@@ -12,7 +12,19 @@
|
||||
"scripts": {
|
||||
"test": "vendor/bin/phpunit",
|
||||
"lint": "vendor/bin/phpcs",
|
||||
"format": "vendor/bin/phpcbf"
|
||||
"format": "vendor/bin/phpcbf",
|
||||
"benchmark": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --"
|
||||
],
|
||||
"benchmark-tag": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --tag=baseline --"
|
||||
],
|
||||
"benchmark-compare": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"vendor/bin/phpbench run --report=appwrite --ref=baseline --"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -24,6 +36,7 @@
|
||||
"psr-4": {
|
||||
"Tests\\E2E\\": "tests/e2e",
|
||||
"Tests\\Unit\\": "tests/unit",
|
||||
"Tests\\Benchmarks\\": "tests/benchmarks",
|
||||
"Appwrite\\Tests\\": "tests/extensions"
|
||||
}
|
||||
},
|
||||
@@ -46,7 +59,7 @@
|
||||
"utopia-php/abuse": "0.16.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.17.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cache": "dev-feat-redis-sync as 0.8.1",
|
||||
"utopia-php/cli": "0.14.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.28.*",
|
||||
@@ -66,6 +79,7 @@
|
||||
"utopia-php/storage": "0.13.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.0",
|
||||
"utopia-php/dsn": "0.1.0",
|
||||
"resque/php-resque": "1.3.6",
|
||||
"matomo/device-detector": "6.0.0",
|
||||
"dragonmantank/cron-expression": "3.3.1",
|
||||
@@ -87,7 +101,14 @@
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"swoole/ide-helper": "4.8.9",
|
||||
"textalk/websocket": "1.5.7"
|
||||
"textalk/websocket": "1.5.7",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
|
||||
"symfony/console": "^5.0",
|
||||
"symfony/filesystem": "^5.0",
|
||||
"symfony/finder": "^5.0",
|
||||
"symfony/options-resolver": "^5.0",
|
||||
"symfony/process": "^5.0"
|
||||
},
|
||||
"provide": {
|
||||
"ext-phpiredis": "*"
|
||||
|
||||
Generated
+1535
-58
File diff suppressed because it is too large
Load Diff
+100
-1
@@ -54,7 +54,7 @@ services:
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
VITE_CONSOLE_MODE: self-hosted
|
||||
ports:
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
- appwrite
|
||||
@@ -86,6 +86,7 @@ services:
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/local/dev
|
||||
|
||||
#- ./vendor/utopia-php/cache:/usr/src/code/vendor/utopia-php/cache
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
@@ -255,6 +256,69 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-sync-out:
|
||||
entrypoint: worker-sync-out
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-sync-out
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- ./vendor/utopia-php/pools:/usr/src/code/vendor/utopia-php/pools
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-sync-in:
|
||||
entrypoint: worker-sync-in
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-sync-in
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
#- ./vendor/utopia-php/cache:/usr/src/code/vendor/utopia-php/cache
|
||||
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REGION
|
||||
|
||||
appwrite-worker-webhooks:
|
||||
entrypoint: worker-webhooks
|
||||
<<: *x-logging
|
||||
@@ -582,6 +646,41 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
|
||||
appwrite-edge-sync:
|
||||
entrypoint: edge-sync
|
||||
<<: *x-logging
|
||||
container_name: appwrite-edge-sync
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- ./vendor/utopia-php/cli:/usr/src/code/vendor/utopia-php/cli
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_SYNC_EDGE_INTERVAL
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_CONNECTIONS_QUEUE
|
||||
- _APP_REGION
|
||||
- _APP_WORKER_PER_CORE
|
||||
|
||||
appwrite-usage:
|
||||
entrypoint: usage
|
||||
<<: *x-logging
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
|
||||
"runner.path": "tests/benchmarks",
|
||||
"runner.file_pattern": "*Bench.php",
|
||||
"runner.bootstrap": "app/init.php",
|
||||
"runner.revs": 2000,
|
||||
"runner.iterations": 3,
|
||||
"runner.retry_threshold": 5,
|
||||
"runner.warmup": 1,
|
||||
"report.generators": {
|
||||
"appwrite": {
|
||||
"extends": "aggregate",
|
||||
"cols": ["benchmark", "subject", "set" ,"revs", "its", "worst", "best", "mean"],
|
||||
"break": ["benchmark"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use Appwrite\Platform\Tasks\Usage;
|
||||
use Appwrite\Platform\Tasks\Vars;
|
||||
use Appwrite\Platform\Tasks\Version;
|
||||
use Appwrite\Platform\Tasks\VolumeSync;
|
||||
use Appwrite\Platform\Tasks\EdgeSync;
|
||||
|
||||
class Tasks extends Service
|
||||
{
|
||||
@@ -35,6 +36,7 @@ class Tasks extends Service
|
||||
->addAction(Migrate::getName(), new Migrate())
|
||||
->addAction(SDKs::getName(), new SDKs())
|
||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(Specs::getName(), new Specs());
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(EdgeSync::getName(), new EdgeSync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
|
||||
class EdgeSync extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'edge-sync';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Schedules edge sync tasks')
|
||||
->inject('pools')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForCacheSyncOut')
|
||||
->callback(fn (Group $pools, Database $dbForConsole, Client $queueForCacheSyncOut) => $this->action($pools, $dbForConsole, $queueForCacheSyncOut));
|
||||
}
|
||||
|
||||
public function action(Group $pools, Database $dbForConsole, Client $queueForCacheSyncOut): void
|
||||
{
|
||||
Console::title('Edge-sync V1');
|
||||
Console::success(APP_NAME . ' Edge-sync v1 has started');
|
||||
|
||||
$interval = (int) App::getEnv('_APP_SYNC_EDGE_INTERVAL', '180');
|
||||
Console::loop(function () use ($interval, $dbForConsole, $queueForCacheSyncOut) {
|
||||
|
||||
$time = DateTime::now();
|
||||
$count = 0;
|
||||
$chunk = 0;
|
||||
$limit = 50;
|
||||
$sum = $limit;
|
||||
|
||||
Console::success("[{$time}] New task every {$interval} seconds");
|
||||
|
||||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $dbForConsole->find('syncs', [
|
||||
Query::equal('region', [App::getEnv('_APP_REGION')]),
|
||||
Query::limit($limit)
|
||||
]);
|
||||
|
||||
$sum = count($results);
|
||||
if ($sum > 0) {
|
||||
foreach ($results as $document) {
|
||||
Console::info("[{$time}] Enqueueing keys chunk {$count} to {$document->getAttribute('target')}");
|
||||
$queueForCacheSyncOut
|
||||
->enqueue([
|
||||
'region' => $document->getAttribute('target'),
|
||||
'keys' => $document->getAttribute('keys')
|
||||
]);
|
||||
|
||||
$dbForConsole->deleteDocument('syncs', $document->getId());
|
||||
$count++;
|
||||
}
|
||||
} else {
|
||||
Console::info("[{$time}] No cache keys where found.");
|
||||
}
|
||||
}
|
||||
}, $interval);
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,7 @@ use Appwrite\Utopia\Response\Model\UsageProject;
|
||||
use Appwrite\Utopia\Response\Model\UsageStorage;
|
||||
use Appwrite\Utopia\Response\Model\UsageUsers;
|
||||
use Appwrite\Utopia\Response\Model\Variable;
|
||||
use Appwrite\Utopia\Response\Model\EdgeSync;
|
||||
|
||||
/**
|
||||
* @method Response setStatusCode(int $code = 200)
|
||||
@@ -203,6 +204,7 @@ class Response extends SwooleResponse
|
||||
public const MODEL_DOMAIN_LIST = 'domainList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
public const MODEL_VARIABLE_LIST = 'variableList';
|
||||
public const MODEL_EDGE_SYNC = 'edgeSync';
|
||||
|
||||
// Health
|
||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
||||
@@ -341,6 +343,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new UsageFunctions())
|
||||
->setModel(new UsageFunction())
|
||||
->setModel(new UsageProject())
|
||||
->setModel(new EdgeSync())
|
||||
// Verification
|
||||
// Recovery
|
||||
// Tests (keep last)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class EdgeSync extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this
|
||||
->addRule('keys', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Cache keys array to be purged.',
|
||||
'default' => '',
|
||||
'example' => '["cache-console:_metadata:users", "cache-console:_metadata:buckets"]',
|
||||
])
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'EdgeSync';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_EDGE_SYNC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Scopes;
|
||||
|
||||
use PhpBench\Attributes\AfterMethods;
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use Tests\E2E\Scopes\Scope as E2EScope;
|
||||
|
||||
#[BeforeMethods(['setUp'])]
|
||||
#[AfterMethods(['tearDown'])]
|
||||
abstract class Scope extends E2EScope
|
||||
{
|
||||
protected $endpoint = 'http://localhost/v1';
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Databases;
|
||||
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use PhpBench\Attributes\ParamProviders;
|
||||
use Tests\Benchmarks\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
abstract class Base extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
|
||||
protected static string $databaseId;
|
||||
protected static string $collectionId;
|
||||
protected static string $documentId;
|
||||
|
||||
#[BeforeMethods(['createDatabase', 'createCollection'])]
|
||||
public function benchDocumentCreate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_POST, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'The Matrix',
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($this->getUser()['$id'])),
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
#[ParamProviders(['provideCounts'])]
|
||||
#[BeforeMethods(['createDatabase', 'createCollection', 'createDocuments'])]
|
||||
public function benchDocumentReadList(array $params)
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(' . $params['documents'] . ')'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createDatabase', 'createCollection', 'createDocuments'])]
|
||||
public function benchDocumentRead()
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/documents/' . static::$documentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createDatabase', 'createCollection', 'createDocuments'])]
|
||||
public function benchDocumentUpdate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_PATCH, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/documents/' . static::$documentId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'title' => 'The Matrix Reloaded',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function provideCounts(): array
|
||||
{
|
||||
return [
|
||||
'1 Document' => ['documents' => 1],
|
||||
'10 Documents' => ['documents' => 10],
|
||||
'100 Documents' => ['documents' => 100],
|
||||
];
|
||||
}
|
||||
|
||||
public function createDatabase(array $params = [])
|
||||
{
|
||||
$database = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Database'
|
||||
]);
|
||||
static::$databaseId = $database['body']['$id'];
|
||||
}
|
||||
|
||||
public function createCollection(array $params = [])
|
||||
{
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . static::$databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Movies',
|
||||
'documentSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($this->getUser()['$id'])),
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
],
|
||||
]);
|
||||
static::$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create attribute
|
||||
$this->client->call(Client::METHOD_POST, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'title',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
// Wait for attribute to be ready
|
||||
sleep(2);
|
||||
}
|
||||
|
||||
public function createDocuments(array $params = [])
|
||||
{
|
||||
$count = $params['documents'] ?? 1;
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases/' . static::$databaseId . '/collections/' . static::$collectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'title' => 'Captain America' . $i,
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($this->getUser()['$id'])),
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
]
|
||||
]);
|
||||
|
||||
static::$documentId = $response['body']['$id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Databases;
|
||||
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
|
||||
class DatabasesCustomClientBench extends Base
|
||||
{
|
||||
use SideClient;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Databases;
|
||||
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class DatabasesCustomServerBench extends Base
|
||||
{
|
||||
use SideServer;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Functions;
|
||||
|
||||
use CURLFile;
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use Tests\Benchmarks\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
abstract class Base extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
|
||||
protected static string $functionId;
|
||||
protected static string $deploymentId;
|
||||
protected static string $executionId;
|
||||
|
||||
#[BeforeMethods(['createFunction', 'prepareDeployment', 'createDeployment', 'patchDeployment'])]
|
||||
public function benchExecutionCreate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_POST, '/functions/' . static::$functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
public function createFunction()
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_POST, '/functions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'runtime' => 'php-8.0',
|
||||
'timeout' => 10,
|
||||
'execute' => [Role::users()->toString()]
|
||||
]);
|
||||
static::$functionId = $response['body']['$id'];
|
||||
}
|
||||
|
||||
public function prepareDeployment()
|
||||
{
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
Console::execute(
|
||||
'cd ' . realpath(__DIR__ . "/../../../resources/functions/php") . " && \
|
||||
tar --exclude code.tar.gz -czf code.tar.gz .",
|
||||
'',
|
||||
$stdout,
|
||||
$stderr
|
||||
);
|
||||
}
|
||||
|
||||
public function createDeployment()
|
||||
{
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions/php/code.tar.gz');
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/functions/' . static::$functionId . '/deployments', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile(
|
||||
$code,
|
||||
'application/x-gzip',
|
||||
\basename($code)
|
||||
),
|
||||
]);
|
||||
|
||||
static::$deploymentId = $response['body']['$id'];
|
||||
|
||||
while (true) {
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/' . static::$functionId . '/deployments/' . static::$deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
|
||||
$status = $response['body']['status'] ?? '';
|
||||
|
||||
switch ($status) {
|
||||
case '':
|
||||
case 'processing':
|
||||
case 'building':
|
||||
usleep(200);
|
||||
break;
|
||||
case 'ready':
|
||||
break 2;
|
||||
case 'failed':
|
||||
throw new \Exception('Failed to build function');
|
||||
}
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
public function patchDeployment()
|
||||
{
|
||||
$this->client->call(Client::METHOD_PATCH, '/functions/' . static::$functionId . '/deployments/' . static::$deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Functions;
|
||||
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
|
||||
class FunctionsCustomClientBench extends Base
|
||||
{
|
||||
use SideClient;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Functions;
|
||||
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use PhpBench\Attributes\Iterations;
|
||||
use PhpBench\Attributes\Revs;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class FunctionsCustomServerBench extends Base
|
||||
{
|
||||
use SideServer;
|
||||
|
||||
#[Revs(1)]
|
||||
#[Iterations(1)]
|
||||
#[BeforeMethods(['createFunction', 'prepareDeployment'])]
|
||||
public function benchDeploymentCreate()
|
||||
{
|
||||
$this->createDeployment();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Storage;
|
||||
|
||||
use CURLFile;
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use PhpBench\Attributes\ParamProviders;
|
||||
use Tests\Benchmarks\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
abstract class Base extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
|
||||
protected static string $bucketId;
|
||||
protected static string $fileId;
|
||||
|
||||
#[BeforeMethods(['createBucket'])]
|
||||
public function benchFileCreate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_POST, '/storage/buckets/' . static::$bucketId . '/files', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'fileId' => ID::unique(),
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($this->getUser()['$id'])),
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
],
|
||||
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
]);
|
||||
}
|
||||
|
||||
#[ParamProviders(['provideCounts'])]
|
||||
#[BeforeMethods(['createBucket', 'createFiles'])]
|
||||
public function benchFileReadList(array $params)
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/storage/buckets/' . static::$bucketId . '/files', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(' . $params['files'] . ')'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createBucket', 'createFiles'])]
|
||||
public function benchFileRead()
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/storage/buckets/' . static::$bucketId . '/files/' . static::$fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createBucket', 'createFiles'])]
|
||||
public function benchFileUpdate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_PUT, '/storage/buckets/' . static::$bucketId . '/files/' . static::$fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Updated name',
|
||||
'permissions' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function provideCounts(): array
|
||||
{
|
||||
return [
|
||||
'10 Files' => ['files' => 10],
|
||||
'100 Files' => ['files' => 100],
|
||||
];
|
||||
}
|
||||
|
||||
public function createBucket(array $params = [])
|
||||
{
|
||||
// Create bucket
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket',
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($this->getUser()['$id'])),
|
||||
Permission::write(Role::user($this->getUser()['$id'])),
|
||||
],
|
||||
'fileSecurity' => true
|
||||
]);
|
||||
static::$bucketId = $bucket['body']['$id'];
|
||||
}
|
||||
|
||||
public function createFiles(array $params = [])
|
||||
{
|
||||
$count = $params['files'] ?? 1;
|
||||
|
||||
// Create files
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$response = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . static::$bucketId . '/files', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
]);
|
||||
|
||||
static::$fileId = $response['body']['$id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Storage;
|
||||
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
|
||||
class StorageCustomClientBench extends Base
|
||||
{
|
||||
use SideClient;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Storage;
|
||||
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class StorageCustomServerBench extends Base
|
||||
{
|
||||
use SideServer;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Benchmarks\Services\Users;
|
||||
|
||||
use PhpBench\Attributes\BeforeMethods;
|
||||
use PhpBench\Attributes\ParamProviders;
|
||||
use Tests\Benchmarks\Scopes\Scope;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class UserCustomServerBench extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
protected static string $userId;
|
||||
|
||||
public function benchUserCreate()
|
||||
{
|
||||
$id = ID::unique();
|
||||
|
||||
$this->client->call(Client::METHOD_POST, '/users', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $id,
|
||||
'email' => 'test' . $id . '@example.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
}
|
||||
|
||||
#[ParamProviders(['provideCounts'])]
|
||||
#[BeforeMethods(['createUsers'])]
|
||||
public function benchUserReadList(array $params)
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/users', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(' . $params['users'] . ')'],
|
||||
]);
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createUsers'])]
|
||||
public function benchUserRead()
|
||||
{
|
||||
$this->client->call(Client::METHOD_GET, '/users/' . static::$userId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
}
|
||||
|
||||
#[BeforeMethods(['createUsers'])]
|
||||
public function benchUserUpdate()
|
||||
{
|
||||
$this->client->call(Client::METHOD_PUT, '/users/' . static::$userId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'New Name',
|
||||
]);
|
||||
}
|
||||
|
||||
public function createUsers(array $params = [])
|
||||
{
|
||||
$count = $params['documents'] ?? 1;
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$id = ID::unique();
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $id,
|
||||
'email' => 'test' . $id . '@example.com',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
static::$userId = $response['body']['$id'];
|
||||
}
|
||||
}
|
||||
|
||||
public function provideCounts(): array
|
||||
{
|
||||
return [
|
||||
'1 User' => ['users' => 1],
|
||||
'10 Users' => ['users' => 10],
|
||||
'100 Users' => ['users' => 100],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import http from 'k6/http';
|
||||
import { check } from 'k6';
|
||||
import { Counter } from 'k6/metrics';
|
||||
|
||||
// A simple counter for http requests
|
||||
export const requests = new Counter('http_reqs');
|
||||
|
||||
// you can specify stages of your test (ramp up/down patterns) through the options object
|
||||
// target is the number of VUs you are aiming for
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ target: 50, duration: '1m' },
|
||||
// { target: 15, duration: '1m' },
|
||||
// { target: 0, duration: '1m' },
|
||||
],
|
||||
thresholds: {
|
||||
requests: ['count < 100'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const config = {
|
||||
headers: {
|
||||
'X-Appwrite-Key': '24356eb021863f81eb7dd77c7750304d0464e141cad6e9a8befa1f7d2b066fde190df3dab1e8d2639dbb82ee848da30501424923f4cd80d887ee40ad77ded62763ee489448523f6e39667f290f9a54b2ab8fad131a0bc985e6c0f760015f7f3411e40626c75646bb19d2bb2f7bf2f63130918220a206758cbc48845fd725a695',
|
||||
'X-Appwrite-Project': '60479fe35d95d'
|
||||
}}
|
||||
|
||||
const resDb = http.get('http://localhost:9501/', config);
|
||||
|
||||
check(resDb, {
|
||||
'status is 200': (r) => r.status === 200,
|
||||
});
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// k6 run tests/benchmarks/ws.js
|
||||
|
||||
import { URL } from 'https://jslib.k6.io/url/1.0.0/index.js';
|
||||
import ws from 'k6/ws';
|
||||
import { check } from 'k6';
|
||||
|
||||
export let options = {
|
||||
stages: [
|
||||
{
|
||||
duration: '10s',
|
||||
target: 500
|
||||
},
|
||||
{
|
||||
duration: '1m',
|
||||
target: 500
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default function () {
|
||||
// const url = new URL('wss://appwrite-realtime.monitor-api.com/v1/realtime');
|
||||
// url.searchParams.append('project', '604249e6b1a9f');
|
||||
const url = new URL('ws://localhost/v1/realtime');
|
||||
url.searchParams.append('project', 'console');
|
||||
url.searchParams.append('channels[]', 'files');
|
||||
|
||||
const res = ws.connect(url.toString(), function (socket) {
|
||||
let connection = false;
|
||||
let checked = false;
|
||||
let payload = null;
|
||||
socket.on('open', () => {
|
||||
connection = true;
|
||||
});
|
||||
|
||||
socket.on('message', (data) => {
|
||||
payload = data;
|
||||
checked = true;
|
||||
});
|
||||
|
||||
socket.setTimeout(function () {
|
||||
check(payload, {
|
||||
'connection opened': (r) => connection,
|
||||
'message received': (r) => checked,
|
||||
'channels are right': (r) => r === JSON.stringify({
|
||||
"type": "connected",
|
||||
"data": {
|
||||
"channels": [
|
||||
"files"
|
||||
],
|
||||
"user": null
|
||||
}
|
||||
})
|
||||
})
|
||||
socket.close();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
check(res, { 'status is 101': (r) => r && r.status === 101 });
|
||||
}
|
||||
@@ -21,7 +21,7 @@ abstract class Scope extends TestCase
|
||||
*/
|
||||
protected $endpoint = 'http://localhost/v1';
|
||||
|
||||
protected function setUp(): void
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->client = new Client();
|
||||
|
||||
@@ -30,7 +30,7 @@ abstract class Scope extends TestCase
|
||||
;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->client = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user