-
-
Notifications
You must be signed in to change notification settings - Fork 48
Revised changes #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revised changes #182
Conversation
|
Thank you very much! I will try to take a look at it tomorrow @gp-lnuff |
|
This fork seems to work for me @gp-lnuff CC @CodeWithDennis |
|
Could storing the results like this cause any performance issues with big trees? @gp-lnuff |
|
Compared to the original algorithm I think the |
|
It turns out the first |
|
I'm done with the benchmarking. I used this <?php
namespace Database\Factories;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category>
*/
class CategoryFactory extends Factory {
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array {
return [
"description" => fake()->uuid(),
"category_id" => Category::factory(),
];
}
}with this <?php
namespace Database\Seeders;
use App\Models\Category;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class CategorySeeder extends Seeder {
/**
* Seed the application's database.
*/
public function run(): void {
$baseDatasetSize = 20;
$baseMaxNesting = 8;
$recursiveCreateRandomChildren = function (Category $category, $upperBound) use (&$recursiveCreateRandomChildren) {
if ($upperBound == 0) {
return;
}
$rand = rand(0, $upperBound);
$categories = Category::factory()->count($rand)->recycle($category)->create();
$categories->each(fn($item) => $recursiveCreateRandomChildren($item, $upperBound - 1));
};
Category::factory($baseDatasetSize)->withoutParents()->create()->each(fn($item) => $recursiveCreateRandomChildren($item, $baseMaxNesting));
}
}To generate around 24'000 records in a hierarchical structure with varying levels of nesting. I initially used much bigger numbers, which led me to write upwards of 4'000'000 records and that led to To benchmark the performance of the algorithm implementation, I used the # More code...
x-data="selectTree({
name: @js($getName()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$getStatePath()}')") }},
options: @php
[$value, $duration] = Benchmark::value(fn() => $getTree());
debug("$duration ms");
echo Js::from($value)->toHtml();
@endphp,
searchable: @js($isSearchable()),
# More code...The results for the 24'000-records tree were as such:
After the initial loading, performing any action on the tree (opening the select, expanding or collapsing a branch) would be noticeably delayed (in the ~1s range), happening on both current and proposed implementation which suggests that it's a performance issue on the javascript side. I should note that at this size for the dataset, using a Here are some other numbers, achieved by tweaking With 7572 results (
With 5178 results (
With 1606 results (
With 631 results (
With 913 results (
With 3160 results (
In conclusion, it looks like the performance of this implementation is worse with larger data sets, but it's close enough that the javascript side will start having issues before the difference between the two becomes noticeable by users. With the added benefit of handling #178 I would say I am satisfied with the performance. Otherwise, I would welcome further optimizations if anyone has suggestions. |
|
Wow.. Great work! — really appreciate the time you put into this! Performance looks fine to me. I’ve run into a similar issue before where large trees can slow things down a bit. The only real way to fix that would be to avoid loading child items until a dropdown is opened — but since that’s not supported by the underlying package, it’s not something we can implement here either. @gp-lnuff Overall, this looks like a solid fix. Hopefully, @Baspa can also take a look and see if it resolves the issues he was facing. |
|
Do you want me to check this? @CodeWithDennis |
Yes please! |
|
Still seems to work for me :) @CodeWithDennis |
Fixes #179 and addresses #178
This approach keeps the original functionality of the
SelectTreeintact by handling the$resultMapbuilding differently.Instead of branching the logic if the initial pass doesn't yield root results, we can use a second
arrayas a cache for the hierarchical relationship and integrate the$resultMapas the iteration comes across the parents, confirming they are in the$results.Making the map this bit* more expensive to build has another added benefit - if the relationship query itself is filtered/scoped, the tree will now correctly treat records whose parents don't show up in the
$resultsas root results, without having to define$modifyQueryUsingor changing$parentNullValue.I wonder if there is a case for only using one query to fetch the results, since designating root results is delegated to the
$resultMap.About that I have a question about intended usage, is the
$parentNullValuesupposed to be a value that another record should never have? I can smell the hint of a couple use cases where by setting$parentNullValueto the current record'sIdor a related record's could let us build trees to pick "deep children of the current record".I'd also like @Baspa to give some input about whether this addresses his concerns thoroughly.
* actual performance difference not tested