Skip to content

Conversation

@ItzNotABug
Copy link
Member

@ItzNotABug ItzNotABug commented Oct 12, 2025

Optimize relationship writes with bulk operations.

Adds bulk insert/update methods to avoid N+1 queries when creating relationships. Junction table links now use batch inserts, and one-to-many/many-to-one relationships use SQL UPDATE with IN clauses instead of individual updates. Includes updateManyByIds() adapter method and benchmarks.

Controlled via DB_RELATIONSHIP_BULK_WRITES env var (enabled by default) for before/after tests in benchmarks.

@ItzNotABug ItzNotABug self-assigned this Oct 12, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 12, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch optimize-rels

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

];

if (!isset($levels[$level])) {
fwrite(STDERR, "Invalid level: {$level}\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you can use our lib commands used for CLI as
Console::error()

Copy link
Member

@abnegate abnegate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can go a few steps further, using a work queue like we now do for reads. Then regardless of whether it's string IDs or Documents, or how many levels, we can write every relationship level as an array instead of falling back to one-by-one. Will make the logic a lot simpler if we only have one path as well

Comment on lines +113 to +128
/**
* Check if bulk relationship write optimizations are enabled.
* Controlled via environment variable DB_RELATIONSHIP_BULK_WRITES (default: enabled).
*
* @return bool
*/
private function shouldUseRelationshipBulkWrites(): bool
{
$val = getenv('DB_RELATIONSHIP_BULK_WRITES');
if ($val === false || $val === '') {
return true;
}
$val = strtolower((string)$val);
return !in_array($val, ['0', 'false', 'off'], true);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove, if we're going to add a better way, it can just be default

Comment on lines +4479 to +4480
if ($attrValue instanceof Document ||
(is_array($attrValue) && !empty($attrValue) && isset($attrValue[0]) && $attrValue[0] instanceof Document)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't handle the case of nested string IDs

Comment on lines +2158 to +2162
$sql = "
UPDATE {$this->getSQLTable($collectionId)}
SET {$setSQL}
WHERE {$this->quote('_uid')} IN ({$inSQL}){$tenantClause}
";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the scenario where we want to set the same values on many documents but not use updateDocuments?

Comment on lines +4486 to +4487
if ($hasNestedRelationships) {
// Use original method for nested relationships - handles everything including links
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this defeat most of the benefit? Will we only get improvements for a single level relationship where all are strings IDs?

Comment on lines +4509 to +4510
// Ensure rich docs (without nesting) one-by-one
foreach ($richDocsNoNesting as $relatedDoc) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are "rich docs"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks should go in the bin dir as CLI tasks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants