Skip to content

Commit

Permalink
wip: sharding migration
Browse files Browse the repository at this point in the history
Signed-off-by: Robin Appelman <[email protected]>
  • Loading branch information
icewind1991 committed Oct 18, 2024
1 parent b238fc9 commit d007709
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 19 deletions.
10 changes: 7 additions & 3 deletions lib/private/DB/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ public function __construct(
self::SHARD_PRESETS[$name]['shard_key'],
$shardMapper,
self::SHARD_PRESETS[$name]['companion_tables'],
$config['shards']
$config['shards'],
$config['from_primary_key'] ?? 0,

Check failure on line 184 in lib/private/DB/Connection.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidArrayOffset

lib/private/DB/Connection.php:184:5: InvalidArrayOffset: Cannot access value on variable $config using offset value of 'from_primary_key', expecting 'shards' or 'mapper' (see https://psalm.dev/115)
$config['from_shard_key'] ?? 0,

Check failure on line 185 in lib/private/DB/Connection.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidArrayOffset

lib/private/DB/Connection.php:185:5: InvalidArrayOffset: Cannot access value on variable $config using offset value of 'from_shard_key', expecting 'shards', 'mapper' or 'from_primary_key' (see https://psalm.dev/115)
);
}, $shardConfig, $shardNames);
$this->shards = array_combine($shardNames, $this->shards);
Expand All @@ -199,8 +201,10 @@ public function getShardConnections(): array {
if ($this->isShardingEnabled) {
foreach ($this->shards as $shardDefinition) {
foreach ($shardDefinition->getAllShards() as $shard) {

Check failure on line 203 in lib/private/DB/Connection.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

lib/private/DB/Connection.php:203:14: UndefinedClass: Class, interface or enum named OC\DB\QueryBuilder\Sharded\List does not exist (see https://psalm.dev/019)
/** @var ConnectionAdapter $connection */
$connections[] = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
if ($shard !== ShardDefinition::MIGRATION_SHARD) {
/** @var ConnectionAdapter $connection */
$connections[] = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
}
}
}
}
Expand Down
24 changes: 14 additions & 10 deletions lib/private/DB/QueryBuilder/Sharded/AutoIncrementHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

namespace OC\DB\QueryBuilder\Sharded;

use OC\DB\QueryBuilder\QueryBuilder;
use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\IMemcache;
use OCP\IMemcacheTTL;

Expand Down Expand Up @@ -134,17 +136,19 @@ private function getNextInner(ShardDefinition $shardDefinition): ?int {
* Get the maximum primary key value from the shards
*/
private function getMaxFromDb(ShardDefinition $shardDefinition): int {
$max = 0;
$max = $shardDefinition->fromFileId;
$query = $this->shardConnectionManager->getConnection($shardDefinition, 0)->getQueryBuilder();
$query->select($shardDefinition->primaryKey)
->from($shardDefinition->table)
->orderBy($shardDefinition->primaryKey, 'DESC')
->setMaxResults(1);
foreach ($shardDefinition->getAllShards() as $shard) {

Check failure on line 145 in lib/private/DB/QueryBuilder/Sharded/AutoIncrementHandler.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedClass

lib/private/DB/QueryBuilder/Sharded/AutoIncrementHandler.php:145:12: UndefinedClass: Class, interface or enum named OC\DB\QueryBuilder\Sharded\List does not exist (see https://psalm.dev/019)
$connection = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
$query = $connection->getQueryBuilder();
$query->select($shardDefinition->primaryKey)
->from($shardDefinition->table)
->orderBy($shardDefinition->primaryKey, 'DESC')
->setMaxResults(1);
$result = $query->executeQuery()->fetchOne();
if ($result) {
$max = max($max, $result);
if ($shard !== ShardDefinition::MIGRATION_SHARD) {
$connection = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
$result = $query->executeQuery($connection)->fetchOne();
if ($result) {
$max = max($max, $result);
}
}
}
return $max;
Expand Down
12 changes: 11 additions & 1 deletion lib/private/DB/QueryBuilder/Sharded/ShardConnectionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OC\DB\ConnectionFactory;
use OC\SystemConfig;
use OCP\IDBConnection;
use OCP\Server;

/**
* Keeps track of the db connections to the various shards
Expand All @@ -28,8 +29,17 @@ public function __construct(

public function getConnection(ShardDefinition $shardDefinition, int $shard): IDBConnection {
$connectionKey = $shardDefinition->table . '_' . $shard;
if (!isset($this->connections[$connectionKey])) {

if (isset($this->connections[$connectionKey])) {
return $this->connections[$connectionKey];
}

if ($shard === ShardDefinition::MIGRATION_SHARD) {
$this->connections[$connectionKey] = \OC::$server->get(IDBConnection::class);
} elseif (isset($shardDefinition->shards[$shard])) {
$this->connections[$connectionKey] = $this->createConnection($shardDefinition->shards[$shard]);
} else {
throw new \InvalidArgumentException("invalid shard key $shard only " . count($shardDefinition->shards) . ' configured');
}

return $this->connections[$connectionKey];
Expand Down
23 changes: 19 additions & 4 deletions lib/private/DB/QueryBuilder/Sharded/ShardDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@

namespace OC\DB\QueryBuilder\Sharded;

use lib\DB\QueryBuilder\Partitioned\JoinConditionTest;
use OCP\DB\QueryBuilder\Sharded\IShardMapper;

/**
* Configuration for a shard setup
*/
class ShardDefinition {
// we reserve the bottom byte of the primary key for the initial shard, so the total shard count is limited to what we can fit there
public const MAX_SHARDS = 256;
// additionally, shard id 255 is reserved for migration purposes
public const MAX_SHARDS = 255;
public const MIGRATION_SHARD = 255;

public const PRIMARY_KEY_MASK = 0x7F_FF_FF_FF_FF_FF_FF_00;
public const PRIMARY_KEY_SHARD_MASK = 0x00_00_00_00_00_00_00_FF;
Expand All @@ -37,8 +40,10 @@ public function __construct(
public array $companionKeys,
public string $shardKey,
public IShardMapper $shardMapper,
public array $companionTables = [],
public array $shards = [],
public array $companionTables,
public array $shards,
public int $fromFileId,
public int $fromStorageId,
) {
if (count($this->shards) >= self::MAX_SHARDS) {
throw new \Exception('Only allowed maximum of ' . self::MAX_SHARDS . ' shards allowed');
Expand All @@ -53,11 +58,21 @@ public function hasTable(string $table): bool {
}

public function getShardForKey(int $key): int {
if ($key < $this->fromStorageId) {
return self::MIGRATION_SHARD;
}
return $this->shardMapper->getShardForKey($key, count($this->shards));
}

/**
* @return List<int>

Check failure on line 68 in lib/private/DB/QueryBuilder/Sharded/ShardDefinition.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedDocblockClass

lib/private/DB/QueryBuilder/Sharded/ShardDefinition.php:68:13: UndefinedDocblockClass: Docblock-defined class, interface or enum named OC\DB\QueryBuilder\Sharded\List does not exist (see https://psalm.dev/200)
*/
public function getAllShards(): array {
return array_keys($this->shards);
if ($this->fromStorageId !== 0) {
return array_merge(array_keys($this->shards), [self::MIGRATION_SHARD]);
} else {
return array_keys($this->shards);
}
}

public function isKey(string $column): bool {
Expand Down
3 changes: 3 additions & 0 deletions lib/private/DB/QueryBuilder/Sharded/ShardQueryRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public function getShards(bool $allShards, array $shardKeys): ?array {
private function getLikelyShards(array $primaryKeys): array {
$shards = [];
foreach ($primaryKeys as $primaryKey) {
if ($primaryKey < $this->shardDefinition->fromFileId&& !in_array(ShardDefinition::MIGRATION_SHARD, $shards)) {
$shards[] = ShardDefinition::MIGRATION_SHARD;
}
$encodedShard = $primaryKey & ShardDefinition::PRIMARY_KEY_SHARD_MASK;
if ($encodedShard < count($this->shardDefinition->shards) && !in_array($encodedShard, $shards)) {
$shards[] = $encodedShard;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private function getQueryBuilder(string $table, string $shardColumn, string $pri
return new ShardedQueryBuilder(
$this->connection->getQueryBuilder(),
[
new ShardDefinition($table, $primaryColumn, [], $shardColumn, new RoundRobinShardMapper(), $companionTables, []),
new ShardDefinition($table, $primaryColumn, [], $shardColumn, new RoundRobinShardMapper(), $companionTables, [], 0),
],
$this->createMock(ShardConnectionManager::class),
$this->autoIncrementHandler,
Expand Down

0 comments on commit d007709

Please sign in to comment.