Skip to content

Commit b9aa397

Browse files
authored
Merge pull request #48795 from nextcloud/sharding-existing
feat: support migrating an instance to sharding
2 parents 7465e38 + 0a77ba9 commit b9aa397

File tree

6 files changed

+52
-19
lines changed

6 files changed

+52
-19
lines changed

lib/private/DB/Connection.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public function __construct(
160160
$this->_config->setSQLLogger($debugStack);
161161
}
162162

163-
/** @var array<string, array{shards: array[], mapper: ?string}> $shardConfig */
163+
/** @var array<string, array{shards: array[], mapper: ?string, from_primary_key: ?int, from_shard_key: ?int}> $shardConfig */
164164
$shardConfig = $this->params['sharding'] ?? [];
165165
$shardNames = array_keys($shardConfig);
166166
$this->shards = array_map(function (array $config, string $name) {
@@ -180,7 +180,9 @@ public function __construct(
180180
self::SHARD_PRESETS[$name]['shard_key'],
181181
$shardMapper,
182182
self::SHARD_PRESETS[$name]['companion_tables'],
183-
$config['shards']
183+
$config['shards'],
184+
$config['from_primary_key'] ?? 0,
185+
$config['from_shard_key'] ?? 0,
184186
);
185187
}, $shardConfig, $shardNames);
186188
$this->shards = array_combine($shardNames, $this->shards);
@@ -199,8 +201,10 @@ public function getShardConnections(): array {
199201
if ($this->isShardingEnabled) {
200202
foreach ($this->shards as $shardDefinition) {
201203
foreach ($shardDefinition->getAllShards() as $shard) {
202-
/** @var ConnectionAdapter $connection */
203-
$connections[] = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
204+
if ($shard !== ShardDefinition::MIGRATION_SHARD) {
205+
/** @var ConnectionAdapter $connection */
206+
$connections[] = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
207+
}
204208
}
205209
}
206210
}

lib/private/DB/QueryBuilder/Sharded/AutoIncrementHandler.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private function getNextInner(ShardDefinition $shardDefinition): ?int {
108108
}
109109

110110
// discard the encoded initial shard
111-
$current = $this->getMaxFromDb($shardDefinition) >> 8;
111+
$current = $this->getMaxFromDb($shardDefinition);
112112
$next = max($current, self::MIN_VALID_KEY) + 1;
113113
if ($cache->cas($shardDefinition->table, 'empty-placeholder', $next)) {
114114
return $next;
@@ -131,19 +131,22 @@ private function getNextInner(ShardDefinition $shardDefinition): ?int {
131131
}
132132

133133
/**
134-
* Get the maximum primary key value from the shards
134+
* Get the maximum primary key value from the shards, note that this has already stripped any embedded shard id
135135
*/
136136
private function getMaxFromDb(ShardDefinition $shardDefinition): int {
137-
$max = 0;
137+
$max = $shardDefinition->fromFileId;
138+
$query = $this->shardConnectionManager->getConnection($shardDefinition, 0)->getQueryBuilder();
139+
$query->select($shardDefinition->primaryKey)
140+
->from($shardDefinition->table)
141+
->orderBy($shardDefinition->primaryKey, 'DESC')
142+
->setMaxResults(1);
138143
foreach ($shardDefinition->getAllShards() as $shard) {
139144
$connection = $this->shardConnectionManager->getConnection($shardDefinition, $shard);
140-
$query = $connection->getQueryBuilder();
141-
$query->select($shardDefinition->primaryKey)
142-
->from($shardDefinition->table)
143-
->orderBy($shardDefinition->primaryKey, 'DESC')
144-
->setMaxResults(1);
145-
$result = $query->executeQuery()->fetchOne();
145+
$result = $query->executeQuery($connection)->fetchOne();
146146
if ($result) {
147+
if ($result > $shardDefinition->fromFileId) {
148+
$result = $result >> 8;
149+
}
147150
$max = max($max, $result);
148151
}
149152
}

lib/private/DB/QueryBuilder/Sharded/ShardConnectionManager.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,17 @@ public function __construct(
2828

2929
public function getConnection(ShardDefinition $shardDefinition, int $shard): IDBConnection {
3030
$connectionKey = $shardDefinition->table . '_' . $shard;
31-
if (!isset($this->connections[$connectionKey])) {
31+
32+
if (isset($this->connections[$connectionKey])) {
33+
return $this->connections[$connectionKey];
34+
}
35+
36+
if ($shard === ShardDefinition::MIGRATION_SHARD) {
37+
$this->connections[$connectionKey] = \OC::$server->get(IDBConnection::class);
38+
} elseif (isset($shardDefinition->shards[$shard])) {
3239
$this->connections[$connectionKey] = $this->createConnection($shardDefinition->shards[$shard]);
40+
} else {
41+
throw new \InvalidArgumentException("invalid shard key $shard only " . count($shardDefinition->shards) . ' configured');
3342
}
3443

3544
return $this->connections[$connectionKey];

lib/private/DB/QueryBuilder/Sharded/ShardDefinition.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
class ShardDefinition {
1717
// 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
18-
public const MAX_SHARDS = 256;
18+
// additionally, shard id 255 is reserved for migration purposes
19+
public const MAX_SHARDS = 255;
20+
public const MIGRATION_SHARD = 255;
1921

2022
public const PRIMARY_KEY_MASK = 0x7F_FF_FF_FF_FF_FF_FF_00;
2123
public const PRIMARY_KEY_SHARD_MASK = 0x00_00_00_00_00_00_00_FF;
@@ -37,8 +39,10 @@ public function __construct(
3739
public array $companionKeys,
3840
public string $shardKey,
3941
public IShardMapper $shardMapper,
40-
public array $companionTables = [],
41-
public array $shards = [],
42+
public array $companionTables,
43+
public array $shards,
44+
public int $fromFileId,
45+
public int $fromStorageId,
4246
) {
4347
if (count($this->shards) >= self::MAX_SHARDS) {
4448
throw new \Exception('Only allowed maximum of ' . self::MAX_SHARDS . ' shards allowed');
@@ -53,11 +57,21 @@ public function hasTable(string $table): bool {
5357
}
5458

5559
public function getShardForKey(int $key): int {
60+
if ($key < $this->fromStorageId) {
61+
return self::MIGRATION_SHARD;
62+
}
5663
return $this->shardMapper->getShardForKey($key, count($this->shards));
5764
}
5865

66+
/**
67+
* @return list<int>
68+
*/
5969
public function getAllShards(): array {
60-
return array_keys($this->shards);
70+
if ($this->fromStorageId !== 0) {
71+
return array_merge(array_keys($this->shards), [self::MIGRATION_SHARD]);
72+
} else {
73+
return array_keys($this->shards);
74+
}
6175
}
6276

6377
public function isKey(string $column): bool {

lib/private/DB/QueryBuilder/Sharded/ShardQueryRunner.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public function getShards(bool $allShards, array $shardKeys): ?array {
5555
private function getLikelyShards(array $primaryKeys): array {
5656
$shards = [];
5757
foreach ($primaryKeys as $primaryKey) {
58+
if ($primaryKey < $this->shardDefinition->fromFileId && !in_array(ShardDefinition::MIGRATION_SHARD, $shards)) {
59+
$shards[] = ShardDefinition::MIGRATION_SHARD;
60+
}
5861
$encodedShard = $primaryKey & ShardDefinition::PRIMARY_KEY_SHARD_MASK;
5962
if ($encodedShard < count($this->shardDefinition->shards) && !in_array($encodedShard, $shards)) {
6063
$shards[] = $encodedShard;

tests/lib/DB/QueryBuilder/Sharded/SharedQueryBuilderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private function getQueryBuilder(string $table, string $shardColumn, string $pri
3939
return new ShardedQueryBuilder(
4040
$this->connection->getQueryBuilder(),
4141
[
42-
new ShardDefinition($table, $primaryColumn, [], $shardColumn, new RoundRobinShardMapper(), $companionTables, []),
42+
new ShardDefinition($table, $primaryColumn, [], $shardColumn, new RoundRobinShardMapper(), $companionTables, [], 0, 0),
4343
],
4444
$this->createMock(ShardConnectionManager::class),
4545
$this->autoIncrementHandler,

0 commit comments

Comments
 (0)