Skip to content

Commit

Permalink
Merge pull request #417 from HendrikPrinsZA/feature/upserts
Browse files Browse the repository at this point in the history
Experiment A
  • Loading branch information
HendrikPrinsZA authored Sep 19, 2024
2 parents 7801489 + a6a7c33 commit 6fef077
Show file tree
Hide file tree
Showing 14 changed files with 3,697 additions and 1,922 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/github-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v5
- name: Setup node
uses: actions/setup-node@v4
with:
Expand Down
171 changes: 171 additions & 0 deletions app/Challenges/A/ExperimentA.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php

namespace App\Challenges\A;

use App\KataChallenge;
use App\Models\ExperimentARecord;
use Fiber;

class ExperimentA extends KataChallenge
{
protected const MAX_INTERATIONS = 50;

public function inserts(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);

ExperimentARecord::upsertPrototype($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function updates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);
$this->insertChunked($records);

$records = collect($records)->map(fn ($record) => array_merge($record, [
'update_field_1' => 'Updated 1',
'update_field_2' => 'Updated 2',
'update_field_3' => 'Updated 3',
]))->toArray();
ExperimentARecord::upsertPrototype($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function partialUpdates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);

$partialRecords = array_slice($records, 0, ceil($iteration / 2));
$this->insertChunked($partialRecords);

ExperimentARecord::upsertPrototype($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function parallelPartialUpdates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);

$partialRecords = array_slice($records, 0, ceil($iteration / 2));
$this->insertChunked($partialRecords);

$chunks = array_chunk($records, ceil(count($records) / 3));
$fiberList = [];
foreach ($chunks as $chunk) {
$fiber = new Fiber(function () use ($chunk) {
return ExperimentARecord::upsertPrototype($chunk, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);
});

$fiber->start();
$fiberList[] = $fiber;
}

while ($fiberList) {
foreach ($fiberList as $idx => $fiber) {
if ($fiber->isTerminated()) {
$fiber->getReturn();

unset($fiberList[$idx]);

continue;
}

$fiber->resume();
}
}

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

protected function truncateAndMakeFreshRecords(int $iteration): array
{
$this->truncate();

$records = [];
for ($index = 0; $index < $iteration * 100; $index++) {
$records[] = [
'unique_field_1' => sprintf('unique_field_1-%d', $index),
'unique_field_2' => sprintf('unique_field_2-%d', $index),
'unique_field_3' => sprintf('unique_field_3-%d', $index),
'update_field_1' => sprintf('Original %d', $index),
'update_field_2' => sprintf('Original %d', $index),
'update_field_3' => sprintf('Original %d', $index),
];
}

return $records;
}

protected function truncate(): void
{
ExperimentARecord::truncate();
}

protected function insertChunked(array $records): void
{
$chunkSize = 1000;
$chunks = array_chunk($records, $chunkSize);

foreach ($chunks as $chunk) {
ExperimentARecord::insert($chunk);

usleep(3000); // 3ms
}
}

protected function upsertChunked(array $records, array $uniqueFields, array $updateFields): void
{
$chunkSize = 1000;
$chunks = array_chunk($records, $chunkSize);

foreach ($chunks as $chunk) {
ExperimentARecord::upsert($chunk, $uniqueFields, $updateFields);

usleep(3000); // 3ms
}
}
}
118 changes: 118 additions & 0 deletions app/Challenges/B/ExperimentA.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace App\Challenges\B;

use App\Challenges\A\ExperimentA as AExperimentA;
use App\Models\ExperimentARecord;
use Fiber;

class ExperimentA extends AExperimentA
{
public function inserts(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);
$this->upsertChunked($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function updates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);
$this->insertChunked($records);

$records = collect($records)->map(fn ($record) => array_merge($record, [
'update_field_1' => 'Updated 1',
'update_field_2' => 'Updated 2',
'update_field_3' => 'Updated 3',
]))->toArray();
$this->upsertChunked($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function partialUpdates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);

$partialRecords = array_slice($records, 0, ceil($iteration / 2));
$this->insertChunked($partialRecords);

$this->upsertChunked($records, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}

public function parallelPartialUpdates(int $iteration): string
{
$records = $this->truncateAndMakeFreshRecords($iteration);

$partialRecords = array_slice($records, 0, ceil($iteration / 2));
$this->insertChunked($partialRecords);

$chunks = array_chunk($records, ceil(count($records) / 3));
$fiberList = [];
foreach ($chunks as $chunk) {
$fiber = new Fiber(function () use ($chunk) {
return $this->upsertChunked($chunk, [
'unique_field_1', 'unique_field_2', 'unique_field_3',
], [
'update_field_1', 'update_field_2', 'update_field_3',
]);
});

$fiber->start();
$fiberList[] = $fiber;
}

while ($fiberList) {
foreach ($fiberList as $idx => $fiber) {
if ($fiber->isTerminated()) {
unset($fiberList[$idx]);

continue;
}

$fiber->resume();
}
}

$response = ExperimentARecord::query()
->select('update_field_3')
->get()
->map(fn ($record) => md5($record->update_field_3))
->toArray();

return md5(implode('|', $response));
}
}
40 changes: 26 additions & 14 deletions app/KataRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use PDOException;
use ReflectionClass;
use ReflectionMethod;
use Symfony\Component\Console\Helper\ProgressBar;
Expand Down Expand Up @@ -94,6 +95,10 @@ public function run(): array
'Variable',
'Value',
], [
[
'Mode (LK_RUN_MODE)',
config('laravel-kata.mode')->name,
],
[
'Seconds',
config('laravel-kata.max-seconds'),
Expand Down Expand Up @@ -498,7 +503,7 @@ protected function runChallengeMethod(
}

/** @var KataChallenge $instance */
$instance = new $targetClass();
$instance = new $targetClass;
$maxIterations = $instance->getMaxIterations();
$maxSeconds = $instance->getMaxSeconds();
$instance = null;
Expand Down Expand Up @@ -542,19 +547,26 @@ protected function runChallengeMethodMaxMode(
return Cache::get($cacheKey);
}

$response = match ($kataRunnerIterationMode) {
KataRunnerIterationMode::MAX_ITERATIONS => $this->runChallengeMethodMaxIterations(
$reflectionMethod,
$maxIterations
),
KataRunnerIterationMode::MAX_SECONDS => $this->runChallengeMethodMaxSeconds(
$reflectionMethod,
$maxSeconds
),
KataRunnerIterationMode::XDEBUG_PROFILE => $this->profile(
$reflectionMethod,
)
};
try {
$response = match ($kataRunnerIterationMode) {
KataRunnerIterationMode::MAX_ITERATIONS => $this->runChallengeMethodMaxIterations(
$reflectionMethod,
$maxIterations
),
KataRunnerIterationMode::MAX_SECONDS => $this->runChallengeMethodMaxSeconds(
$reflectionMethod,
$maxSeconds
),
KataRunnerIterationMode::XDEBUG_PROFILE => $this->profile(
$reflectionMethod,
)
};
} catch (PDOException $exception) {
throw new PDOException(
sprintf('[%s] %s: %s', $exception->getCode(), $exception::class, Str::limit($exception->getMessage(), 512)),
previous: $exception
);
}

if (config('laravel-kata.experimental.cache-results')) {
Cache::set($cacheKey, $response);
Expand Down
Loading

0 comments on commit 6fef077

Please sign in to comment.