Replies: 3 comments 3 replies
-
This sounds sensible to me (though I suspect that 150k records is unusual). |
Beta Was this translation helpful? Give feedback.
-
I did a quick & dirty microtime profiling experiment with my use case. Namely, I updated the /**
* Sync the intermediate tables with a list of IDs or collection of models.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param bool $detaching
* @return array
*/
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->getCurrentlyAttachedPivots();
$records = $this->formatRecordsList($this->parseIds($ids));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// array of the new IDs given to the method which will complete the sync.
if ($detaching) {
$detach = array_diff($current->pluck($this->relatedPivotKey)->all(), array_keys($records));
if (count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = $this->castKeys($detach);
}
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current->pluck($this->relatedPivotKey, $this->relatedPivotKey)->all(), false)
);
// Once we have finished attaching or detaching the records, we will see if we
// have done any attaching or detaching, and if we have we will touch these
// relationships if they are configured to touch on any database updates.
if (count($changes['attached']) ||
count($changes['updated']) ||
count($changes['detached'])) {
$this->touchIfTouching();
}
return $changes;
} /**
* Attach all of the records that aren't in the given current records.
*
* @param array $records
* @param array $current
* @param bool $touch
* @return array
*/
protected function attachNew(array $records, array $current, $touch = true)
{
$changes = ['attached' => [], 'updated' => []];
foreach ($records as $id => $attributes) {
// If the ID is not in the list of existing pivot IDs, we will insert a new pivot
// record, otherwise, we will just update this existing record on this joining
// table, so that the developers will easily update these records pain free.
if (! array_key_exists($id, $current)) {
$this->attach($id, $attributes, $touch);
$changes['attached'][] = $this->castKey($id);
}
// Now we'll try to update an existing pivot record with the attributes that were
// given to the method. If the model is actually updated we will add it to the
// list of updated pivot records so we return them back out to the consumer.
elseif (count($attributes) > 0 &&
$this->updateExistingPivot($id, $attributes, $touch)) {
$changes['updated'][] = $this->castKey($id);
}
}
return $changes;
} Before the change, |
Beta Was this translation helpful? Give feedback.
-
Sidenote: @krembo spoke to the performance issues with |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Proposed Refactor
The current
attachNew
implementation uses PHP'sin_array
to check if an ID is already present in the $current array.This leads to O(N) complexity for each check, where N is the size of the $current array.
To optimize, we can convert the
$current
array to an associative array where keys are the IDs.This will allow us to use PHP's
array_key_exists
for O(1) hash lookups which will improve performance, especially for large datasets.Context
In developing my Laravel application locally, I ran the code below for a model associated with 150k pivot table records.
$someModel->somePivotRelation->syncWithoutDetaching( * Collection of 12k records *);
This caused a server timeout using Laravel's defaults. To fix the timeout, I used tinker to manually detach most of the model's associated pivot table records to match the length of the
sync
input (12k records), which resolved the issue.I was curious what caused the slowdown, so I dug into the
InteractsWithPivot
table.Beta Was this translation helpful? Give feedback.
All reactions